diff --git a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java
index 6039ce304cda..1cc6f8e0221a 100644
--- a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java
+++ b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java
@@ -32,7 +32,7 @@
/**
* AOP Alliance MethodInterceptor
that processes method invocations
* asynchronously, using a given {@link org.springframework.core.task.AsyncTaskExecutor}.
- * Typically used with the {@link org.springframework.context.task.Async} annotation.
+ * Typically used with the {@link org.springframework.scheduling.annotation.Async} annotation.
*
*
In terms of target method signatures, any parameter types are supported.
* However, the return type is constrained to either void
or
diff --git a/spring-aop/src/main/java/org/springframework/aop/target/AbstractPrototypeBasedTargetSource.java b/spring-aop/src/main/java/org/springframework/aop/target/AbstractPrototypeBasedTargetSource.java
index 83c8083e5b74..f2677afbc210 100644
--- a/spring-aop/src/main/java/org/springframework/aop/target/AbstractPrototypeBasedTargetSource.java
+++ b/spring-aop/src/main/java/org/springframework/aop/target/AbstractPrototypeBasedTargetSource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,8 +28,9 @@
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
/**
- * Base class for dynamic {@link TargetSource} implementations that create new prototype
- * bean instances to support a pooling or new-instance-per-invocation strategy.
+ * Base class for dynamic {@link org.springframework.aop.TargetSource} implementations
+ * that create new prototype bean instances to support a pooling or
+ * new-instance-per-invocation strategy.
*
*
Such TargetSources must run in a {@link BeanFactory}, as it needs to
* call the getBean
method to create a new prototype instance.
diff --git a/spring-aop/src/main/resources/org/springframework/aop/config/spring-aop-3.2.xsd b/spring-aop/src/main/resources/org/springframework/aop/config/spring-aop-3.2.xsd
new file mode 100644
index 000000000000..7dae8fc44690
--- /dev/null
+++ b/spring-aop/src/main/resources/org/springframework/aop/config/spring-aop-3.2.xsd
@@ -0,0 +1,410 @@
+
+
+
In the case of multiple argument methods, the 'required' parameter is + *
In the case of multiple argument methods, the 'required' parameter is * applicable for all arguments. * *
In case of a {@link java.util.Collection} or {@link java.util.Map} @@ -52,8 +52,9 @@ * BeanPostProcessor} which in turn means that you cannot * use {@code @Autowired} to inject references into * {@link org.springframework.beans.factory.config.BeanPostProcessor - * BeanPostProcessor} or {@link BeanFactoryPostProcessor} types. Please - * consult the javadoc for the {@link AutowiredAnnotationBeanPostProcessor} + * BeanPostProcessor} or + * {@link org.springframework.beans.factory.config.BeanFactoryPostProcessor BeanFactoryPostProcessor} + * types. Please consult the javadoc for the {@link AutowiredAnnotationBeanPostProcessor} * class (which, by default, checks for the presence of this annotation). * * @author Juergen Hoeller diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/Value.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/Value.java index 2136666cfbd2..eb257fdfcb1c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/Value.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/Value.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,8 +37,9 @@ * BeanPostProcessor} which in turn means that you cannot use * {@code @Value} within * {@link org.springframework.beans.factory.config.BeanPostProcessor - * BeanPostProcessor} or {@link BeanFactoryPostProcessor} types. Please - * consult the javadoc for the {@link AutowiredAnnotationBeanPostProcessor} + * BeanPostProcessor} or + * {@link org.springframework.beans.factory.config.BeanFactoryPostProcessor BeanFactoryPostProcessor} + * types. Please consult the javadoc for the {@link AutowiredAnnotationBeanPostProcessor} * class (which, by default, checks for the presence of this annotation). * * @author Juergen Hoeller diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/FieldRetrievingFactoryBean.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/FieldRetrievingFactoryBean.java index 6e5d0f5780a4..7b3c5ea5df29 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/FieldRetrievingFactoryBean.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/FieldRetrievingFactoryBean.java @@ -171,7 +171,7 @@ public void afterPropertiesSet() throws ClassNotFoundException, NoSuchFieldExcep int lastDotIndex = this.staticField.lastIndexOf('.'); if (lastDotIndex == -1 || lastDotIndex == this.staticField.length()) { throw new IllegalArgumentException( - "staticField must be a fully qualified class plus method name: " + + "staticField must be a fully qualified class plus static field name: " + "e.g. 'example.MyExampleClass.MY_EXAMPLE_FIELD'"); } String className = this.staticField.substring(0, lastDotIndex); 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 ddf060283c4c..2f6bd6865a11 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-2011 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,8 +36,8 @@ * *
{@code ** @@ -76,7 +76,7 @@ *- * - * + * + * * *}
Example XML property with default value: * *
{@code - ** * @author Chris Beams diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionParserDelegate.java b/spring-beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionParserDelegate.java index b63652e79ff0..df22acc2f49b 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionParserDelegate.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionParserDelegate.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -353,7 +353,7 @@ public void initDefaults(Element root) { *+ * *}
<util:constant static-field="java.lang.Integer.MAX_VALUE"/>
.
+ location
' attribute.
+ null
).
- * @param userValue the store value
+ * @param storeValue the store value
* @return the value to return to the user
*/
protected Object fromStoreValue(Object storeValue) {
diff --git a/spring-context/src/main/java/org/springframework/cache/ehcache/EhCacheCacheManager.java b/spring-context/src/main/java/org/springframework/cache/ehcache/EhCacheCacheManager.java
index 2fb21c7e637e..a1588ac7dfc0 100644
--- a/spring-context/src/main/java/org/springframework/cache/ehcache/EhCacheCacheManager.java
+++ b/spring-context/src/main/java/org/springframework/cache/ehcache/EhCacheCacheManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -39,8 +39,8 @@ public class EhCacheCacheManager extends AbstractCacheManager {
/**
- * Returns the backing Ehcache {@link net.sf.ehcache.CacheManager}.
- * @return
+ * Returns the backing EhCache {@link net.sf.ehcache.CacheManager}.
+ * @return the backing EhCache {@link net.sf.ehcache.CacheManager}.
*/
public net.sf.ehcache.CacheManager getCacheManager() {
return cacheManager;
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/BeanFactoryCacheOperationSourceAdvisor.java b/spring-context/src/main/java/org/springframework/cache/interceptor/BeanFactoryCacheOperationSourceAdvisor.java
index 97cb34d624b9..a8d4a51e7b7a 100644
--- a/spring-context/src/main/java/org/springframework/cache/interceptor/BeanFactoryCacheOperationSourceAdvisor.java
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/BeanFactoryCacheOperationSourceAdvisor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -44,7 +44,6 @@ protected CacheOperationSource getCacheOperationSource() {
* Set the cache operation attribute source which is used to find cache
* attributes. This should usually be identical to the source reference
* set on the cache interceptor itself.
- * @see CacheInterceptor#setCacheAttributeSource
*/
public void setCacheOperationSource(CacheOperationSource cacheOperationSource) {
this.cacheOperationSource = cacheOperationSource;
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheProxyFactoryBean.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheProxyFactoryBean.java
index d156a701cdaf..67a22e2b3535 100644
--- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheProxyFactoryBean.java
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheProxyFactoryBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2010-2011 the original author or authors.
+ * Copyright 2010-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,7 +24,7 @@
* Proxy factory bean for simplified declarative caching handling.
* This is a convenient alternative to a standard AOP
* {@link org.springframework.aop.framework.ProxyFactoryBean}
- * with a separate {@link CachingInterceptor} definition.
+ * with a separate {@link CacheInterceptor} definition.
*
* This class is designed to facilitate declarative cache demarcation: namely, wrapping
* a singleton target object with a caching proxy, proxying all the interfaces that the
@@ -36,7 +36,7 @@
*
* @author Costin Leau
* @see org.springframework.aop.framework.ProxyFactoryBean
- * @see CachingInterceptor
+ * @see CacheInterceptor
*/
@SuppressWarnings("serial")
public class CacheProxyFactoryBean extends AbstractSingletonProxyFactoryBean {
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/NameMatchCacheOperationSource.java b/spring-context/src/main/java/org/springframework/cache/interceptor/NameMatchCacheOperationSource.java
index 55d6f2a6c37a..69f22ec49fae 100644
--- a/spring-context/src/main/java/org/springframework/cache/interceptor/NameMatchCacheOperationSource.java
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/NameMatchCacheOperationSource.java
@@ -50,7 +50,6 @@ public class NameMatchCacheOperationSource implements CacheOperationSource, Seri
* (e.g. "myMethod") and CacheOperation instances
* (or Strings to be converted to CacheOperation instances).
* @see CacheOperation
- * @see CacheOperationEditor
*/
public void setNameMap(Map {@code @Configuration} is meta-annotated with @{@link Component}, therefore
+ * {@code @Configuration} is meta-annotated with {@link Component @Component}, therefore
* {@code @Configuration} classes are candidates for component scanning (typically using
* Spring XML's {@code {@code @Configuration} classes may not only be bootstrapped using
* component scanning, but may also themselves configure component scanning using
- * the @{@link ComponentScan} annotation:
+ * the {@link ComponentScan @ComponentScan} annotation:
* {@code @Configuration} classes may be composed using the @{@link Import} annotation,
+ * {@code @Configuration} classes may be composed using the {@link Import @Import} annotation,
* not unlike the way that {@code By default, {@code @Bean} methods will be eagerly instantiated at container
* bootstrap time. To avoid this, {@code @Configuration} may be used in conjunction with
- * the @{@link Lazy} annotation to indicate that all {@code @Bean} methods declared within
+ * the {@link Lazy @Lazy} annotation to indicate that all {@code @Bean} methods declared within
* the class are by default lazily initialized. Note that {@code @Lazy} may be used on
* individual {@code @Bean} methods as well.
*
@@ -291,7 +292,7 @@
* } To customize the weaver used, the {@code @Configuration} class annotated with
- * {@code @EnableLoadTimeWeaving} may also implement the {@link LoadTimeWeaverConfigurer}
+ * {@code @EnableLoadTimeWeaving} may also implement the {@link LoadTimeWeavingConfigurer}
* interface and return a custom {@code LoadTimeWeaver} instance through the
* {@code #getLoadTimeWeaver} method:
* Provides functionality equivalent to the {@code @{@code Bean} definitions declared in imported {@code @Configuration} classes
- * should be accessed by using @{@link Autowired} injection. Either the bean itself can
- * be autowired, or the configuration class instance declaring the bean can be autowired.
- * The latter approach allows for explicit, IDE-friendly navigation between
- * {@code @Configuration} class methods.
+ * {@code @Bean} definitions declared in imported {@code @Configuration} classes
+ * should be accessed by using {@link org.springframework.beans.factory.annotation.Autowired @Autowired}
+ * injection. Either the bean itself can be autowired, or the configuration class instance
+ * declaring the bean can be autowired. The latter approach allows for explicit,
+ * IDE-friendly navigation between {@code @Configuration} class methods.
*
* May be declared at the class level or as a meta-annotation.
*
* If XML or other non-{@code @Configuration} bean definition resources need to be
- * imported, use @{@link ImportResource}
+ * imported, use {@link ImportResource @ImportResource}
*
* @author Chris Beams
* @since 3.0
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/LoadTimeWeavingConfigurer.java b/spring-context/src/main/java/org/springframework/context/annotation/LoadTimeWeavingConfigurer.java
index 63322f629665..8df85b91f095 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/LoadTimeWeavingConfigurer.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/LoadTimeWeavingConfigurer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,12 +19,14 @@
import org.springframework.instrument.classloading.LoadTimeWeaver;
/**
- * Interface to be implemented by @{@link org.springframework.context.annotation.Configuration
- * Configuration} classes annotated with @{@link EnableLoadTimeWeaving} that wish to
+ * Interface to be implemented by
+ * {@link org.springframework.context.annotation.Configuration @Configuration}
+ * classes annotated with {@link EnableLoadTimeWeaving @EnableLoadTimeWeaving} that wish to
* customize the {@link LoadTimeWeaver} instance to be used.
*
- * See @{@link EnableAsync} for usage examples and information on how a default
- * {@code LoadTimeWeaver} is selected when this interface is not used.
+ * See {@link org.springframework.scheduling.annotation.EnableAsync @EnableAsync}
+ * for usage examples and information on how a default {@code LoadTimeWeaver}
+ * is selected when this interface is not used.
*
* @author Chris Beams
* @since 3.1
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 84831884b7be..23b157f1d8ab 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
@@ -506,7 +506,7 @@ protected void prepareRefresh() {
/**
* Replace any stub property sources with actual instances.
* @see org.springframework.core.env.PropertySource.StubPropertySource
- * @see org.springframework.web.context.support.WebApplicationContextUtils#initSerlvetPropertySources
+ * @see org.springframework.web.context.support.WebApplicationContextUtils#initServletPropertySources
*/
protected void initPropertySources() {
// For subclasses: do nothing by default.
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 aa8d9e799249..c831bbb85514 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-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -108,7 +108,7 @@ public void setEnvironment(Environment environment) {
* Processing occurs by replacing ${...} placeholders in bean definitions by resolving each
* against this configurer's set of {@link PropertySources}, which includes:
* As of Spring 3.0, GlassFish V3 is supported as well.
*
* @author Costin Leau
@@ -49,7 +50,8 @@ public GlassFishLoadTimeWeaver() {
* Creates a new instance of the
* For arrays, collections, and maps, element and key/value types are checked if declared.
@@ -348,7 +348,7 @@ public TypeDescriptor getElementTypeDescriptor() {
* Narrows the {@link #getElementTypeDescriptor() elementType} property to the class of the provided collection or array element.
* For example, if this describes a java.util.List<java.lang.Number< and the element argument is a java.lang.Integer, the returned TypeDescriptor will be java.lang.Integer.
* If this describes a java.util.List<?> and the element argument is a java.lang.Integer, the returned TypeDescriptor will be java.lang.Integer as well.
- * Annotation and nested type context will be preserved in the narrowed TypeDescriptor that is returned.
+ * Annotation and nested type context will be preserved in the narrowed TypeDescriptor that is returned.
* @param element the collection or array element
* @return a element type descriptor, narrowed to the type of the provided element
* @throws IllegalStateException if this type is not a java.util.Collection or Array type
@@ -383,7 +383,7 @@ public TypeDescriptor getMapKeyTypeDescriptor() {
* Narrows the {@link #getMapKeyTypeDescriptor() mapKeyType} property to the class of the provided map key.
* For example, if this describes a java.util.Map<java.lang.Number, java.lang.String< and the key argument is a java.lang.Integer, the returned TypeDescriptor will be java.lang.Integer.
* If this describes a java.util.Map<?, ?> and the key argument is a java.lang.Integer, the returned TypeDescriptor will be java.lang.Integer as well.
- * Annotation and nested type context will be preserved in the narrowed TypeDescriptor that is returned.
+ * Annotation and nested type context will be preserved in the narrowed TypeDescriptor that is returned.
* @param mapKey the map key
* @return the map key type descriptor
* @throws IllegalStateException if this type is not a java.util.Map.
@@ -409,10 +409,10 @@ public TypeDescriptor getMapValueTypeDescriptor() {
* Narrows the {@link #getMapValueTypeDescriptor() mapValueType} property to the class of the provided map value.
* For example, if this describes a java.util.Map<java.lang.String, java.lang.Number< and the value argument is a java.lang.Integer, the returned TypeDescriptor will be java.lang.Integer.
* If this describes a java.util.Map<?, ?> and the value argument is a java.lang.Integer, the returned TypeDescriptor will be java.lang.Integer as well.
- * Annotation and nested type context will be preserved in the narrowed TypeDescriptor that is returned.
+ * Annotation and nested type context will be preserved in the narrowed TypeDescriptor that is returned.
* @param mapValue the map value
* @return the map value type descriptor
- * @throws IllegalStateException if this type is not a java.util.Map.
+ * @throws IllegalStateException if this type is not a java.util.Map.
*/
public TypeDescriptor getMapValueTypeDescriptor(Object mapValue) {
return narrow(mapValue, getMapValueTypeDescriptor());
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 4e93c5a2784c..b9798525db43 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
@@ -41,9 +41,9 @@
*
* Concrete subclasses differ primarily on which {@link PropertySource} objects they
* add by default. {@code AbstractEnvironment} adds none. Subclasses should contribute
- * property sources through the protected {@link #customizePropertySources()} hook, while
- * clients should customize using {@link ConfigurableEnvironment#getPropertySources()} and
- * working against the {@link MutablePropertySources} API. See
+ * property sources through the protected {@link #customizePropertySources(MutablePropertySources)}
+ * hook, while clients should customize using {@link ConfigurableEnvironment#getPropertySources()}
+ * and working against the {@link MutablePropertySources} API. See
* {@link ConfigurableEnvironment} Javadoc for usage examples.
*
* @author Chris Beams
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 60b8da43ff0a..cf94472c0c7f 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-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -56,7 +56,7 @@ public interface PropertyResolver {
* 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 T the expected type of the property value
+ * @param targetType the expected type of the property value
* @see #getRequiredProperty(String, Class)
*/
Conversions and promotions are handled as defined in
+ * Section 5.6.2
+ * of the Java Language Specification, with the addiction of BigDecimals management:
*
- * If any of the operands is of a reference type, unboxing conversion (¤5.1.8) is performed. Then: If any of the operands is of a reference type, unboxing conversion (Section 5.1.8) is performed. Then:
- *
* @author Andy Clement
+ * @author Sam Brannen
* @since 3.0
*/
public class OpMultiply extends Operator {
-
public OpMultiply(int pos, SpelNodeImpl... operands) {
super("*", pos, operands);
}
/**
- * Implements multiply directly here for some types of operand, otherwise delegates to any registered overloader for
- * types it does not recognize. Supported types here are:
+ * Implements the {@code multiply} operator directly here for certain types
+ * of supported operands and otherwise delegates to any registered overloader
+ * for types not supported here.
+ *
+ * Supported operand types:
* Note: this is a mutable property. The returned value may therefore
- * represent a processed value that does not match the original value
+ * represent a processed value that does not match the original value
* declared via {@link ContextConfiguration @ContextConfiguration}.
* @return the resource locations; potentially Note: this is a mutable property. The returned value may therefore
- * represent a processed value that does not match the original value
+ * represent a processed value that does not match the original value
* declared via {@link ContextConfiguration @ContextConfiguration}.
* @return the configuration classes; potentially The {@code SmartContextLoader} SPI supersedes the {@link ContextLoader} SPI
* introduced in Spring 2.5: a {@code SmartContextLoader} can choose to process
* either resource locations or configuration classes. Furthermore, a
@@ -34,13 +34,13 @@
* processContextConfiguration()} prior to calling
* {@link #loadContext(MergedContextConfiguration) loadContext()}. This gives a
* {@code SmartContextLoader} the opportunity to provide custom support for
- * modifying resource locations or detecting default resource locations or
+ * modifying resource locations or detecting default resource locations or
* default configuration classes. The results of
* {@link #processContextConfiguration(ContextConfigurationAttributes)
* processContextConfiguration()} should be merged for all classes in the
* hierarchy of the root test class and then supplied to
* {@link #loadContext(MergedContextConfiguration) loadContext()}.
- *
+ *
* Even though {@code SmartContextLoader} extends {@code ContextLoader},
* clients should favor {@code SmartContextLoader}-specific methods over those
* defined in {@code ContextLoader}, particularly because a
@@ -97,7 +97,7 @@ public interface SmartContextLoader extends ContextLoader {
* {@link org.springframework.beans.factory.annotation.Autowired @Autowired},
* {@link javax.annotation.Resource @Resource}, and
* {@link javax.inject.Inject @Inject}. In addition, concrete implementations
- * should set the active bean definition profiles in the context's
+ * should set the active bean definition profiles in the context's
* {@link org.springframework.core.env.Environment Environment}.
* Any As of Spring 3.1, Can be overridden in subclasses — for example, to process
* configuration classes instead of resource locations.
* @since 3.1
- * @see #processLocations()
+ * @see #processLocations(Class, String...)
*/
public void processContextConfiguration(ContextConfigurationAttributes configAttributes) {
String[] processedLocations = processLocations(configAttributes.getDeclaringClass(),
@@ -96,10 +97,10 @@ public void processContextConfiguration(ContextConfigurationAttributes configAtt
* @return a processed array of application context resource locations
* @since 2.5
* @see #isGenerateDefaultLocations()
- * @see #generateDefaultLocations()
- * @see #modifyLocations()
- * @see org.springframework.test.context.ContextLoader#processLocations()
- * @see #processContextConfiguration()
+ * @see #generateDefaultLocations(Class)
+ * @see #modifyLocations(Class, String...)
+ * @see org.springframework.test.context.ContextLoader#processLocations(Class, String...)
+ * @see #processContextConfiguration(ContextConfigurationAttributes)
*/
public final String[] processLocations(Class> clazz, String... locations) {
return (ObjectUtils.isEmpty(locations) && isGenerateDefaultLocations()) ? generateDefaultLocations(clazz)
@@ -115,7 +116,7 @@ public final String[] processLocations(Class> clazz, String... locations) {
* where As of Spring 3.1, the implementation of this method adheres to the
- * contract defined in the {@link SmartContextLoader} SPI. Specifically,
+ * contract defined in the {@link SmartContextLoader} SPI. Specifically,
* this method will preemptively verify that the generated default
* location actually exists. If it does not exist, this method will log a
* warning and return an empty array.
@@ -189,14 +190,15 @@ else if (!ResourcePatternUtils.isUrl(path)) {
/**
* Determine whether or not default resource locations should be
* generated if the As of Spring 3.1, the semantics of this method have been overloaded
* to include detection of either default resource locations or default
- * configuration classes. Consequently, this method can also be used to
+ * configuration classes. Consequently, this method can also be used to
* determine whether or not default configuration classes should be
* detected if the Can be overridden by subclasses to change the default behavior.
* @return always Must be implemented by subclasses.
* @return the resource suffix; should not be Implementation details:
* The default implementation delegates to the {@link BeanDefinitionReader}
- * returned by {@link #createBeanDefinitionReader()} to
+ * returned by {@link #createBeanDefinitionReader(GenericApplicationContext)} to
* {@link BeanDefinitionReader#loadBeanDefinitions(String) load} the
* bean definitions.
* Subclasses must provide an appropriate implementation of
- * {@link #createBeanDefinitionReader()}. Alternatively subclasses may
+ * {@link #createBeanDefinitionReader(GenericApplicationContext)}. Alternatively subclasses may
* provide a no-op implementation of {@code createBeanDefinitionReader()}
* and override this method to provide a custom strategy for loading or
* registering bean definitions.
* @param context the context into which the bean definitions should be loaded
- * @param mergedConfig the merged context configuration
+ * @param mergedConfig the merged context configuration
* @see #loadContext(MergedContextConfiguration)
* @since 3.1
*/
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AnnotationConfigContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/support/AnnotationConfigContextLoader.java
index 9839adc03c94..8d92ff51a42c 100644
--- a/spring-test/src/main/java/org/springframework/test/context/support/AnnotationConfigContextLoader.java
+++ b/spring-test/src/main/java/org/springframework/test/context/support/AnnotationConfigContextLoader.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,7 +35,7 @@
* Concrete implementation of {@link AbstractGenericContextLoader} that loads
* bean definitions from
* {@link org.springframework.context.annotation.Configuration configuration classes}.
- *
+ *
* Note: The implementation of this method adheres to the contract defined in the
* {@link org.springframework.test.context.SmartContextLoader SmartContextLoader}
- * SPI. Specifically, this method uses introspection to detect default
+ * SPI. Specifically, this method uses introspection to detect default
* configuration classes that comply with the constraints required of
* {@code @Configuration} class implementations. If a potential candidate
* configuration class does meet these requirements, this method will log a
@@ -187,7 +187,7 @@ protected String[] generateDefaultLocations(Class> clazz) {
}
/**
- * The Claims:
+ *
+ * This converter can be used to bind to typed beans, or untyped {@link java.util.HashMap HashMap} instances.
+ *
+ * By default, this converter supports {@code application/json}. This can be overridden by setting the
+ * {@link #setSupportedMediaTypes(List) supportedMediaTypes} property.
+ *
+ * @author Arjen Poutsma
+ * @author Keith Donald
+ * @since 3.2
+ * @see org.springframework.web.servlet.view.json.MappingJackson2JsonView
+ */
+public class MappingJackson2HttpMessageConverter extends AbstractHttpMessageConverter Setting a custom-configured {@code ObjectMapper} is one way to take further control of the JSON
+ * serialization process. For example, an extended {@link org.codehaus.jackson.map.SerializerFactory}
+ * can be configured that provides custom serializers for specific types. The other option for refining
+ * the serialization process is to use Jackson's provided annotations on the types to be serialized,
+ * in which case a custom-configured ObjectMapper is unnecessary.
+ */
+ public void setObjectMapper(ObjectMapper objectMapper) {
+ Assert.notNull(objectMapper, "ObjectMapper must not be null");
+ this.objectMapper = objectMapper;
+ configurePrettyPrint();
+ }
+
+ private void configurePrettyPrint() {
+ if (this.prettyPrint != null) {
+ this.objectMapper.configure(SerializationFeature.INDENT_OUTPUT, this.prettyPrint);
+ }
+ }
+
+ /**
+ * Return the underlying {@code ObjectMapper} for this view.
+ */
+ public ObjectMapper getObjectMapper() {
+ return this.objectMapper;
+ }
+
+ /**
+ * Indicate whether the JSON output by this view should be prefixed with "{} &&". Default is false.
+ * Prefixing the JSON string in this manner is used to help prevent JSON Hijacking.
+ * The prefix renders the string syntactically invalid as a script so that it cannot be hijacked.
+ * This prefix does not affect the evaluation of JSON, but if JSON validation is performed on the
+ * string, the prefix would need to be ignored.
+ */
+ public void setPrefixJson(boolean prefixJson) {
+ this.prefixJson = prefixJson;
+ }
+
+ /**
+ * Whether to use the {@link DefaultPrettyPrinter} when writing JSON.
+ * This is a shortcut for setting up an {@code ObjectMapper} as follows:
+ * The default implementation returns {@link ObjectMapper#constructType(java.lang.reflect.Type)},
+ * but this can be overridden in subclasses, to allow for custom generic collection handling.
+ * For instance:
+ * The default value is {@code false}.
+ */
+ public void setPrettyPrint(boolean prettyPrint) {
+ this.prettyPrint = prettyPrint;
+ configurePrettyPrint();
+ }
@Override
public boolean canRead(Class> clazz, MediaType mediaType) {
@@ -123,7 +148,7 @@ protected Object readInternal(Class> clazz, HttpInputMessage inputMessage)
try {
return this.objectMapper.readValue(inputMessage.getBody(), javaType);
}
- catch (JsonProcessingException ex) {
+ catch (IOException ex) {
throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex);
}
}
@@ -135,6 +160,13 @@ protected void writeInternal(Object object, HttpOutputMessage outputMessage)
JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType());
JsonGenerator jsonGenerator =
this.objectMapper.getJsonFactory().createJsonGenerator(outputMessage.getBody(), encoding);
+
+ // A workaround for JsonGenerators not applying serialization features
+ // https://github.com/FasterXML/jackson-databind/issues/12
+ if (this.objectMapper.getSerializationConfig().isEnabled(SerializationConfig.Feature.INDENT_OUTPUT)) {
+ jsonGenerator.useDefaultPrettyPrinter();
+ }
+
try {
if (this.prefixJson) {
jsonGenerator.writeRaw("{} && ");
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/CookieValue.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/CookieValue.java
index aab76733f166..1bc57b7f90e5 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/annotation/CookieValue.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/CookieValue.java
@@ -35,7 +35,8 @@
* @see RequestParam
* @see RequestHeader
* @see org.springframework.web.bind.annotation.RequestMapping
- * @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMethodAdapter
+ * @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
+ * @see org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
* @see org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter
*/
@Target(ElementType.PARAMETER)
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/PathVariable.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/PathVariable.java
index 40b157534e8b..4a1172052332 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/annotation/PathVariable.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/PathVariable.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,7 +29,8 @@
*
* @author Arjen Poutsma
* @see RequestMapping
- * @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMethodAdapter
+ * @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
+ * @see org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
* @since 3.0
*/
@Target(ElementType.PARAMETER)
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestBody.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestBody.java
index 6ea18e308441..b5712abed9b9 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestBody.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestBody.java
@@ -25,17 +25,18 @@
import org.springframework.http.converter.HttpMessageConverter;
/**
- * Annotation indicating a method parameter should be bound to the body of the web request.
- * The body of the request is passed through an {@link HttpMessageConverter} to resolve the
- * method argument depending on the content type of the request. Optionally, automatic
+ * Annotation indicating a method parameter should be bound to the body of the web request.
+ * The body of the request is passed through an {@link HttpMessageConverter} to resolve the
+ * method argument depending on the content type of the request. Optionally, automatic
* validation can be applied by annotating the argument with {@code @Valid}.
- *
+ *
* Supported for annotated handler methods in Servlet environments.
*
* @author Arjen Poutsma
* @see RequestHeader
* @see ResponseBody
- * @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMethodAdapter
+ * @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
+ * @see org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
* @since 3.0
*/
@Target(ElementType.PARAMETER)
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestHeader.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestHeader.java
index ef962da7aa03..2841462e4cd2 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestHeader.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestHeader.java
@@ -31,7 +31,7 @@
* @see RequestMapping
* @see RequestParam
* @see CookieValue
- * @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMethodAdapter
+ * @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
* @see org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter
*/
@Target(ElementType.PARAMETER)
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 00ad724649d1..fe93c5b159a2 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
@@ -21,6 +21,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import java.util.concurrent.Callable;
/**
* Annotation for mapping web requests onto specific handler classes and/or
@@ -56,7 +57,7 @@
* As a consequence, such an argument will never be If the method parameter type is {@link Map} and a request parameter name
+ * is specified, then the request parameter value is converted to a {@link Map}
+ * assuming an appropriate conversion strategy is available.
+ *
+ * If the method parameter is {@link java.util.Map Map<String, String>} or
+ * {@link org.springframework.util.MultiValueMap MultiValueMap<String, String>}
+ * and a parameter name is not specified, then the map parameter is populated
+ * with all request parameter names and values.
+ *
* @author Arjen Poutsma
* @author Juergen Hoeller
* @since 2.5
* @see RequestMapping
* @see RequestHeader
* @see CookieValue
- * @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMethodAdapter
+ * @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
+ * @see org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
* @see org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter
*/
@Target(ElementType.PARAMETER)
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 d07c65d8a866..e4df7821c193 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-2009 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -46,7 +46,8 @@
/**
* The reason to be used for the response. If this element is not set, it will default to the standard status
- * message for the status code.
+ * message for the status code. Note that due to the use of {@code HttpServletResponse.sendError(int, String)},
+ * the response will be considered complete and should not be written to any further.
*
* @see javax.servlet.http.HttpServletResponse#sendError(int, String)
*/
diff --git a/spring-web/src/main/java/org/springframework/web/bind/support/ConfigurableWebBindingInitializer.java b/spring-web/src/main/java/org/springframework/web/bind/support/ConfigurableWebBindingInitializer.java
index 2d5cd6b5acbb..68a251d83290 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/support/ConfigurableWebBindingInitializer.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/support/ConfigurableWebBindingInitializer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -62,7 +62,7 @@ public class ConfigurableWebBindingInitializer implements WebBindingInitializer
* when accessing an out-of-bounds index.
* Default is "true" on a standard DataBinder. Note that this feature is only supported
* for bean property access (DataBinder's default mode), not for field access.
- * @see #initBeanPropertyAccess()
+ * @see org.springframework.validation.DataBinder#initBeanPropertyAccess()
* @see org.springframework.validation.DataBinder#setAutoGrowNestedPaths
*/
public void setAutoGrowNestedPaths(boolean autoGrowNestedPaths) {
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 94f957c686ff..770551895dfd 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -40,6 +40,7 @@
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.feed.AtomFeedHttpMessageConverter;
import org.springframework.http.converter.feed.RssChannelHttpMessageConverter;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
@@ -118,6 +119,10 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
private static final boolean jaxb2Present =
ClassUtils.isPresent("javax.xml.bind.Binder", RestTemplate.class.getClassLoader());
+ private static final boolean jackson2Present =
+ ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", RestTemplate.class.getClassLoader()) &&
+ ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", RestTemplate.class.getClassLoader());
+
private static final boolean jacksonPresent =
ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", RestTemplate.class.getClassLoader()) &&
ClassUtils.isPresent("org.codehaus.jackson.JsonGenerator", RestTemplate.class.getClassLoader());
@@ -143,7 +148,10 @@ public RestTemplate() {
if (jaxb2Present) {
this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
}
- if (jacksonPresent) {
+ if (jackson2Present) {
+ this.messageConverters.add(new MappingJackson2HttpMessageConverter());
+ }
+ else if (jacksonPresent) {
this.messageConverters.add(new MappingJacksonHttpMessageConverter());
}
if (romePresent) {
@@ -384,7 +392,7 @@ public The created application context will be registered into the ServletContext under
- * the attribute name {@link WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE}
+ * the attribute name {@link WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE}
* and subclasses are free to call the {@link #closeWebApplicationContext} method on
* container shutdown to close the application context.
* @see #ContextLoader(WebApplicationContext)
@@ -238,7 +238,7 @@ public ContextLoader() {
* See {@link org.springframework.web.WebApplicationInitializer} for usage examples.
* In any case, the given application context will be registered into the
* ServletContext under the attribute name {@link
- * WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE} and subclasses are
+ * WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE} and subclasses are
* free to call the {@link #closeWebApplicationContext} method on container shutdown
* to close the application context.
* @param context the application context to manage
@@ -325,7 +325,6 @@ else if (ccl != null) {
* In addition, {@link #customizeContext} gets called prior to refreshing the
* context, allowing subclasses to perform custom modifications to the context.
* @param sc current servlet context
- * @param parent the parent ApplicationContext to use, or The created application context will be registered into the ServletContext under
- * the attribute name {@link WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE}
+ * the attribute name {@link WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE}
* and the Spring application context will be closed when the {@link #contextDestroyed}
* lifecycle method is invoked on this listener.
* @see ContextLoader
@@ -66,21 +66,22 @@ public ContextLoaderListener() {
/**
* Create a new {@code ContextLoaderListener} with the given application context. This
* constructor is useful in Servlet 3.0+ environments where instance-based
- * registration of listeners is possible through the {@link ServletContext#addListener}
+ * registration of listeners is possible through the {@link javax.servlet.ServletContext#addListener}
* API.
* The context may or may not yet be {@linkplain
- * ConfigurableApplicationContext#refresh() refreshed}. If it (a) is an implementation
- * of {@link ConfigurableWebApplicationContext} and (b) has not
- * already been refreshed (the recommended approach), then the following will occur:
+ * org.springframework.context.ConfigurableApplicationContext#refresh() refreshed}. If it
+ * (a) is an implementation of {@link ConfigurableWebApplicationContext} and
+ * (b) has not already been refreshed (the recommended approach),
+ * then the following will occur:
* See {@link org.springframework.web.WebApplicationInitializer} for usage examples.
* In any case, the given application context will be registered into the
* ServletContext under the attribute name {@link
- * WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE} and the Spring
+ * WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE} and the Spring
* application context will be closed when the {@link #contextDestroyed} lifecycle
* method is invoked on this listener.
* @param context the application context to manage
diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/AbstractDelegatingCallable.java b/spring-web/src/main/java/org/springframework/web/context/request/async/AbstractDelegatingCallable.java
new file mode 100644
index 000000000000..352616c72223
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/context/request/async/AbstractDelegatingCallable.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.context.request.async;
+
+import java.util.concurrent.Callable;
+
+/**
+ * A base class for a Callable that can be used in a chain of Callable instances.
+ *
+ * Typical use for async request processing scenarios involves:
+ * An async execution chain consists of a sequence of Callable instances and
+ * represents the work required to complete request processing in a separate
+ * thread. To construct the chain, each layer in the call stack of a normal
+ * request (e.g. filter, servlet) may contribute an
+ * {@link AbstractDelegatingCallable} when a request is being processed.
+ * For example the DispatcherServlet might contribute a Callable that
+ * performs view resolution while a HandlerAdapter might contribute a Callable
+ * that returns the ModelAndView, etc. The last Callable is the one that
+ * actually produces an application-specific value, for example the Callable
+ * returned by an {@code @RequestMapping} method.
+ *
+ * @author Rossen Stoyanchev
+ * @since 3.2
+ */
+public final class AsyncExecutionChain {
+
+ public static final String CALLABLE_CHAIN_ATTRIBUTE = AsyncExecutionChain.class.getName() + ".CALLABLE_CHAIN";
+
+ private final List By default a {@link SimpleAsyncTaskExecutor} instance is used.
+ */
+ public void setTaskExecutor(AsyncTaskExecutor taskExecutor) {
+ this.taskExecutor = taskExecutor;
+ }
+
+ /**
+ * Whether async request processing has started through one of:
+ * The resulting processing from this method is identical to
+ * {@link #startCallableChainProcessing()}. The main difference is in
+ * the threading model, i.e. whether a TaskExecutor is used.
+ * @see DeferredResult
+ */
+ public void startDeferredResultProcessing(final DeferredResult deferredResult) {
+ Assert.notNull(deferredResult, "DeferredResult is required");
+ startAsync();
+ deferredResult.init(new DeferredResultHandler() {
+ public void handle(Object result) {
+ if (asyncWebRequest.isAsyncCompleted()) {
+ throw new StaleAsyncWebRequestException("Async request processing already completed");
+ }
+ setCallable(new PassThroughCallable(result));
+ new AsyncExecutionChainRunnable(asyncWebRequest, buildChain()).run();
+ }
+ });
+ if (deferredResult.canHandleTimeout()) {
+ this.asyncWebRequest.setTimeoutHandler(new Runnable() {
+ public void run() {
+ deferredResult.handleTimeout();
+ }
+ });
+ }
+ }
+
+
+ private static class PassThroughCallable implements Callable A {@link StaleAsyncWebRequestException} is logged at debug level and
+ * absorbed while any other unhandled {@link Exception} results in a 500
+ * response code.
+ */
+ public void run() {
+ try {
+ this.callable.call();
+ }
+ catch (StaleAsyncWebRequestException ex) {
+ logger.trace("Could not complete async request", ex);
+ }
+ catch (Exception ex) {
+ logger.trace("Could not complete async request", ex);
+ this.asyncWebRequest.sendError(HttpStatus.INTERNAL_SERVER_ERROR, ex.getMessage());
+ }
+ finally {
+ logger.debug("Completing async request processing");
+ this.asyncWebRequest.complete();
+ }
+ }
+
+}
diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/AsyncWebRequest.java b/spring-web/src/main/java/org/springframework/web/context/request/async/AsyncWebRequest.java
new file mode 100644
index 000000000000..51e6a84a7c8a
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/context/request/async/AsyncWebRequest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.context.request.async;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.context.request.NativeWebRequest;
+
+
+/**
+ * Extend {@link NativeWebRequest} with methods for async request processing.
+ *
+ * @author Rossen Stoyanchev
+ * @since 3.2
+ */
+public interface AsyncWebRequest extends NativeWebRequest {
+
+ /**
+ * Set the timeout for asynchronous request processing in milliseconds.
+ * In Servlet 3 async request processing, the timeout begins when the
+ * main processing thread has exited.
+ */
+ void setTimeout(Long timeout);
+
+ /**
+ * Invoked on a timeout to complete the response instead of the default
+ * behavior that sets the status to 503 (SERVICE_UNAVAILABLE).
+ */
+ void setTimeoutHandler(Runnable runnable);
+
+ /**
+ * Mark the start of async request processing for example ensuring the
+ * request remains open in order to be completed in a separate thread.
+ * @throws IllegalStateException if async processing has started, if it is
+ * not supported, or if it has completed.
+ */
+ void startAsync();
+
+ /**
+ * Whether async processing is in progress and has not yet completed.
+ */
+ boolean isAsyncStarted();
+
+ /**
+ * Complete async request processing making a best effort but without any
+ * effect if async request processing has already completed for any reason
+ * including a timeout.
+ */
+ void complete();
+
+ /**
+ * Whether async processing has completed either normally via calls to
+ * {@link #complete()} or for other reasons such as a timeout likely
+ * detected in a separate thread during async request processing.
+ */
+ boolean isAsyncCompleted();
+
+ /**
+ * Send an error to the client making a best effort to do so but without any
+ * effect if async request processing has already completed, for example due
+ * to a timeout.
+ */
+ void sendError(HttpStatus status, String message);
+
+}
diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/AsyncWebRequestInterceptor.java b/spring-web/src/main/java/org/springframework/web/context/request/async/AsyncWebRequestInterceptor.java
new file mode 100644
index 000000000000..017e0bd7a4fc
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/context/request/async/AsyncWebRequestInterceptor.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.context.request.async;
+
+import java.util.concurrent.Callable;
+
+import org.springframework.web.context.request.WebRequest;
+import org.springframework.web.context.request.WebRequestInterceptor;
+
+/**
+ * Extends {@link WebRequestInterceptor} with lifecycle methods specific to async
+ * request processing.
+ *
+ * This is the sequence of events on the main thread in an async scenario:
+ * This is the sequence of events on the async thread:
+ * Implementations can use this Implementations of this method can ensure ThreadLocal attributes bound
+ * to the main thread are cleared and also prepare for binding them to the
+ * async thread.
+ */
+ void postHandleAsyncStarted(WebRequest request);
+
+}
diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResult.java b/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResult.java
new file mode 100644
index 000000000000..5bcfdaabca89
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResult.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.context.request.async;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.springframework.core.task.AsyncTaskExecutor;
+import org.springframework.util.Assert;
+
+/**
+ * DeferredResult provides an alternative to using a Callable for async request
+ * processing. With a Callable the framework manages a thread on behalf of the
+ * application through an {@link AsyncTaskExecutor}. With a DeferredResult the
+ * application sets the result in a thread of its choice.
+ *
+ * The following sequence describes the intended use scenario:
+ * If the application calls {@link #set(Object)} in thread-2 before the
+ * DeferredResult is initialized by the framework in thread-1, then thread-2
+ * will block and wait for the initialization to complete. Therefore an
+ * application should never create and set the DeferredResult in the same
+ * thread because the initialization will never complete. If an async request was terminated while the next Callable was still
+ * processing, a {@link StaleAsyncWebRequestException} is raised.
+ *
+ * @author Rossen Stoyanchev
+ * @since 3.2
+ */
+public class StaleAsyncRequestCheckingCallable extends AbstractDelegatingCallable {
+
+ private final AsyncWebRequest asyncWebRequest;
+
+ public StaleAsyncRequestCheckingCallable(AsyncWebRequest asyncWebRequest) {
+ this.asyncWebRequest = asyncWebRequest;
+ }
+
+ public Object call() throws Exception {
+ Object result = getNextCallable().call();
+ if (this.asyncWebRequest.isAsyncCompleted()) {
+ throw new StaleAsyncWebRequestException(
+ "Async request no longer available due to a timeout or a (client) error");
+ }
+ return result;
+ }
+
+}
\ No newline at end of file
diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/StaleAsyncWebRequestException.java b/spring-web/src/main/java/org/springframework/web/context/request/async/StaleAsyncWebRequestException.java
new file mode 100644
index 000000000000..fbbb2f46cf9f
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/context/request/async/StaleAsyncWebRequestException.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.context.request.async;
+
+/**
+ * Raised if a stale AsyncWebRequest is detected during async request processing.
+ *
+ * @author Rossen Stoyanchev
+ * @since 3.2
+ *
+ * @see DeferredResult#set(Object)
+ * @see AsyncExecutionChainRunnable#run()
+ */
+@SuppressWarnings("serial")
+public class StaleAsyncWebRequestException extends RuntimeException {
+
+ public StaleAsyncWebRequestException(String message) {
+ super(message);
+ }
+
+}
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
new file mode 100644
index 000000000000..3558ae60442a
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.context.request.async;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.util.Assert;
+import org.springframework.web.context.request.ServletWebRequest;
+
+/**
+ * A Servlet 3.0 implementation of {@link AsyncWebRequest}.
+ *
+ * The servlet and all filters involved in an async request must have async
+ * support enabled using the Servlet API or by adding an
+ * {@code Calls to {@link #register} are idempotent; adding the same
* annotated class more than once has no additional effect.
* @param annotatedClasses one or more annotated classes,
- * e.g. {@link Configuration @Configuration} classes
+ * e.g. {@link org.springframework.context.annotation.Configuration @Configuration} classes
* @see #scan(String...)
* @see #loadBeanDefinitions(DefaultListableBeanFactory)
* @see #setConfigLocation(String)
@@ -179,8 +179,8 @@ public void scan(String... basePackages) {
* annotation.
* @see #register(Class...)
* @see #scan(String...)
- * @see #setConfigLocation()
- * @see #setConfigLocations()
+ * @see #setConfigLocation(String)
+ * @see #setConfigLocations(String[])
* @see AnnotatedBeanDefinitionReader
* @see ClassPathBeanDefinitionScanner
*/
@@ -267,7 +267,7 @@ protected BeanNameGenerator getBeanNameGenerator() {
/**
* Set the {@link ScopeMetadataResolver} to use for detected bean classes.
- * The default is an {@link AnnotationScopeMetadataResolver}.
+ * The default is an {@link org.springframework.context.annotation.AnnotationScopeMetadataResolver}.
*/
public void setScopeMetadataResolver(ScopeMetadataResolver scopeMetadataResolver) {
this.scopeMetadataResolver = scopeMetadataResolver;
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 c36bc87332f5..0f6ef8c341c5 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2008 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
+
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
@@ -31,6 +32,8 @@
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
+import org.springframework.web.context.request.async.AbstractDelegatingCallable;
+import org.springframework.web.context.request.async.AsyncExecutionChain;
import org.springframework.web.util.WebUtils;
/**
@@ -51,6 +54,7 @@
*
* @author Rob Harrop
* @author Juergen Hoeller
+ * @author Rossen Stoyanchev
* @see #beforeRequest
* @see #afterRequest
* @since 1.2.5
@@ -185,14 +189,22 @@ public void setAfterMessageSuffix(String afterMessageSuffix) {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
+
if (isIncludePayload()) {
request = new RequestCachingRequestWrapper(request);
}
beforeRequest(request, getBeforeMessage(request));
+
+ AsyncExecutionChain chain = AsyncExecutionChain.getForCurrentRequest(request);
+ chain.addDelegatingCallable(getAsyncCallable(request));
+
try {
filterChain.doFilter(request, response);
}
finally {
+ if (chain.isAsyncStarted()) {
+ return;
+ }
afterRequest(request, getAfterMessage(request));
}
}
@@ -278,6 +290,20 @@ protected String createMessage(HttpServletRequest request, String prefix, String
*/
protected abstract void afterRequest(HttpServletRequest request, String message);
+ /**
+ * Create a Callable to use to complete processing in an async execution chain.
+ */
+ private AbstractDelegatingCallable getAsyncCallable(final HttpServletRequest request) {
+ return new AbstractDelegatingCallable() {
+ public Object call() throws Exception {
+ getNextCallable().call();
+ afterRequest(request, getAfterMessage(request));
+ return null;
+ }
+ };
+ }
+
+
private static class RequestCachingRequestWrapper extends HttpServletRequestWrapper {
private final ByteArrayOutputStream bos = new ByteArrayOutputStream();
diff --git a/spring-web/src/main/java/org/springframework/web/filter/OncePerRequestFilter.java b/spring-web/src/main/java/org/springframework/web/filter/OncePerRequestFilter.java
index 2f53f6be1b3d..3b8378d7719d 100644
--- a/spring-web/src/main/java/org/springframework/web/filter/OncePerRequestFilter.java
+++ b/spring-web/src/main/java/org/springframework/web/filter/OncePerRequestFilter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2008 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,6 +25,9 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.springframework.web.context.request.async.AbstractDelegatingCallable;
+import org.springframework.web.context.request.async.AsyncExecutionChain;
+
/**
* Filter base class that guarantees to be just executed once per request,
* on any servlet container. It provides a {@link #doFilterInternal}
@@ -35,6 +38,7 @@
* is based on the configured name of the concrete filter instance.
*
* @author Juergen Hoeller
+ * @author Rossen Stoyanchev
* @since 06.12.2003
*/
public abstract class OncePerRequestFilter extends GenericFilterBean {
@@ -70,12 +74,18 @@ public final void doFilter(ServletRequest request, ServletResponse response, Fil
filterChain.doFilter(request, response);
}
else {
+ AsyncExecutionChain chain = AsyncExecutionChain.getForCurrentRequest(request);
+ chain.addDelegatingCallable(getAsyncCallable(request, alreadyFilteredAttributeName));
+
// Do invoke this filter...
request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
try {
doFilterInternal(httpRequest, httpResponse, filterChain);
}
finally {
+ if (chain.isAsyncStarted()) {
+ return;
+ }
// Remove the "already filtered" request attribute for this request.
request.removeAttribute(alreadyFilteredAttributeName);
}
@@ -111,6 +121,20 @@ protected boolean shouldNotFilter(HttpServletRequest request) throws ServletExce
return false;
}
+ /**
+ * Create a Callable to use to complete processing in an async execution chain.
+ */
+ private AbstractDelegatingCallable getAsyncCallable(final ServletRequest request,
+ final String alreadyFilteredAttributeName) {
+
+ return new AbstractDelegatingCallable() {
+ public Object call() throws Exception {
+ getNextCallable().call();
+ request.removeAttribute(alreadyFilteredAttributeName);
+ return null;
+ }
+ };
+ }
/**
* Same contract as for The class may be created with a bean instance or with a bean name (e.g. lazy bean, prototype bean).
* Use {@link #createWithResolvedBean()} to obtain an {@link HandlerMethod} instance with a bean instance
- * initialized through the bean factory.
+ * initialized through the bean factory.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
@@ -49,7 +49,7 @@ public class HandlerMethod {
private final Object bean;
private final Method method;
-
+
private final BeanFactory beanFactory;
private MethodParameter[] parameters;
@@ -87,7 +87,7 @@ public HandlerMethod(Object bean, String methodName, Class>... parameterTypes)
}
/**
- * Constructs a new handler method with the given bean name and method. The bean name will be lazily
+ * Constructs a new handler method with the given bean name and method. The bean name will be lazily
* initialized when {@link #createWithResolvedBean()} is called.
* @param beanName the bean name
* @param beanFactory the bean factory to use for bean initialization
@@ -120,7 +120,7 @@ public Method getMethod() {
}
/**
- * Returns the type of the handler for this handler method.
+ * Returns the type of the handler for this handler method.
* Note that if the bean type is a CGLIB-generated class, the original, user-defined class is returned.
*/
public Class> getBeanType() {
@@ -132,7 +132,7 @@ public Class> getBeanType() {
return ClassUtils.getUserClass(bean.getClass());
}
}
-
+
/**
* If the bean method is a bridge method, this method returns the bridged (user-defined) method.
* Otherwise it returns the same method as {@link #getMethod()}.
@@ -149,7 +149,7 @@ public MethodParameter[] getMethodParameters() {
int parameterCount = this.bridgedMethod.getParameterTypes().length;
MethodParameter[] p = new MethodParameter[parameterCount];
for (int i = 0; i < parameterCount; i++) {
- p[i] = new HandlerMethodParameter(this.bridgedMethod, i);
+ p[i] = new HandlerMethodParameter(i);
}
this.parameters = p;
}
@@ -157,10 +157,17 @@ public MethodParameter[] getMethodParameters() {
}
/**
- * Returns the method return type, as {@code MethodParameter}.
+ * Return the HandlerMethod return type.
*/
public MethodParameter getReturnType() {
- return new HandlerMethodParameter(this.bridgedMethod, -1);
+ return new HandlerMethodParameter(-1);
+ }
+
+ /**
+ * Return the actual return value type.
+ */
+ public MethodParameter getReturnValueType(Object returnValue) {
+ return new ReturnValueMethodParameter(returnValue);
}
/**
@@ -171,8 +178,8 @@ public boolean isVoid() {
}
/**
- * Returns a single annotation on the underlying method traversing its super methods if no
- * annotation can be found on the given method itself.
+ * 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.
* @return the annotation, or {@code null} if none found
*/
@@ -181,7 +188,7 @@ public A getMethodAnnotation(Class annotationType) {
}
/**
- * If the provided instance contains a bean name rather than an object instance, the bean name is resolved
+ * 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.
*/
public HandlerMethod createWithResolvedBean() {
@@ -192,7 +199,7 @@ public HandlerMethod createWithResolvedBean() {
}
return new HandlerMethod(handler, method);
}
-
+
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -216,33 +223,41 @@ public String toString() {
}
/**
- * A {@link MethodParameter} that resolves method annotations even when the actual annotations
- * are on a bridge method rather than on the current method. Annotations on super types are
- * also returned via {@link AnnotationUtils#findAnnotation(Method, Class)}.
+ * A MethodParameter with HandlerMethod-specific behavior.
*/
private class HandlerMethodParameter extends MethodParameter {
-
- public HandlerMethodParameter(Method method, int parameterIndex) {
- super(method, parameterIndex);
+
+ protected HandlerMethodParameter(int index) {
+ super(HandlerMethod.this.bridgedMethod, index);
}
- /**
- * Return {@link HandlerMethod#getBeanType()} rather than the method's class, which could be
- * important for the proper discovery of generic types.
- */
@Override
public Class> getDeclaringClass() {
return HandlerMethod.this.getBeanType();
}
- /**
- * Return the method annotation via {@link HandlerMethod#getMethodAnnotation(Class)}, which will find
- * the annotation by traversing super-types and handling annotations on bridge methods correctly.
- */
@Override
public The {@link #setRequestHandled} flag can be used to indicate the request
* has been handled directly and view resolution is not required.
- *
- * A default {@link Model} is automatically created at instantiation.
- * An alternate model instance may be provided via {@link #setRedirectModel}
- * for use in a redirect scenario. When {@link #setRedirectModelScenario} is set
- * to {@code true} signalling a redirect scenario, the {@link #getModel()}
+ *
+ * A default {@link Model} is automatically created at instantiation.
+ * An alternate model instance may be provided via {@link #setRedirectModel}
+ * for use in a redirect scenario. When {@link #setRedirectModelScenario} is set
+ * to {@code true} signalling a redirect scenario, the {@link #getModel()}
* returns the redirect model instead of the default model.
- *
+ *
* @author Rossen Stoyanchev
* @since 3.1
*/
public class ModelAndViewContainer {
private Object view;
-
+
private boolean requestHandled = false;
-
+
private final ModelMap defaultModel = new BindingAwareModelMap();
private ModelMap redirectModel;
@@ -57,7 +57,7 @@ public class ModelAndViewContainer {
private boolean ignoreDefaultModelOnRedirect = false;
private final SessionStatus sessionStatus = new SimpleSessionStatus();
-
+
/**
* Create a new instance.
*/
@@ -65,7 +65,7 @@ public ModelAndViewContainer() {
}
/**
- * Set a view name to be resolved by the DispatcherServlet via a ViewResolver.
+ * Set a view name to be resolved by the DispatcherServlet via a ViewResolver.
* Will override any pre-existing view name or View.
*/
public void setViewName(String viewName) {
@@ -73,15 +73,15 @@ public void setViewName(String viewName) {
}
/**
- * Return the view name to be resolved by the DispatcherServlet via a
+ * Return the view name to be resolved by the DispatcherServlet via a
* ViewResolver, or {@code null} if a View object is set.
*/
public String getViewName() {
return (this.view instanceof String ? (String) this.view : null);
}
-
+
/**
- * Set a View object to be used by the DispatcherServlet.
+ * Set a View object to be used by the DispatcherServlet.
* Will override any pre-existing view name or View.
*/
public void setView(Object view) {
@@ -97,28 +97,28 @@ public Object getView() {
}
/**
- * Whether the view is a view reference specified via a name to be
+ * Whether the view is a view reference specified via a name to be
* resolved by the DispatcherServlet via a ViewResolver.
*/
public boolean isViewReference() {
return (this.view instanceof String);
}
-
+
/**
- * Signal a scenario where the request is handled directly.
- * A {@link HandlerMethodReturnValueHandler} may use this flag to
- * indicate the response has been fully handled and view resolution
+ * Signal a scenario where the request is handled directly.
+ * A {@link HandlerMethodReturnValueHandler} may use this flag to
+ * indicate the response has been fully handled and view resolution
* is not required (e.g. {@code @ResponseBody}).
* A {@link HandlerMethodArgumentResolver} may also use this flag
- * to indicate the presence of an argument (e.g.
- * {@code ServletResponse} or {@code OutputStream}) that may lead to
+ * to indicate the presence of an argument (e.g.
+ * {@code ServletResponse} or {@code OutputStream}) that may lead to
* a complete response depending on the method return value.
* The default value is {@code true}.
*/
public void setRequestHandled(boolean requestHandled) {
this.requestHandled = requestHandled;
}
-
+
/**
* Whether the request is handled directly.
*/
@@ -129,7 +129,7 @@ public boolean isRequestHandled() {
/**
* Return the model to use: the "default" or the "redirect" model.
* The default model is used if {@code "redirectModelScenario=false"} or
- * if the redirect model is {@code null} (i.e. it wasn't declared as a
+ * if the redirect model is {@code null} (i.e. it wasn't declared as a
* method argument) and {@code ignoreDefaultModelOnRedirect=false}.
*/
public ModelMap getModel() {
@@ -140,17 +140,17 @@ public ModelMap getModel() {
return (this.redirectModel != null) ? this.redirectModel : new ModelMap();
}
}
-
+
/**
* Whether to use the default model or the redirect model.
*/
private boolean useDefaultModel() {
return !this.redirectModelScenario || ((this.redirectModel == null) && !this.ignoreDefaultModelOnRedirect);
}
-
+
/**
- * Provide a separate model instance to use in a redirect scenario.
- * The provided additional model however is not used used unless
+ * Provide a separate model instance to use in a redirect scenario.
+ * The provided additional model however is not used used unless
* {@link #setRedirectModelScenario(boolean)} gets set to {@code true} to signal
* a redirect scenario.
*/
@@ -168,7 +168,7 @@ public void setRedirectModelScenario(boolean redirectModelScenario) {
/**
* When set to {@code true} the default model is never used in a redirect
- * scenario. So if a redirect model is not available, an empty model is
+ * scenario. So if a redirect model is not available, an empty model is
* used instead.
* When set to {@code false} the default model can be used in a redirect
* scenario if a redirect model is not available.
@@ -179,7 +179,7 @@ public void setIgnoreDefaultModelOnRedirect(boolean ignoreDefaultModelOnRedirect
}
/**
- * Return the {@link SessionStatus} instance to use that can be used to
+ * Return the {@link SessionStatus} instance to use that can be used to
* signal that session processing is complete.
*/
public SessionStatus getSessionStatus() {
@@ -188,16 +188,16 @@ public SessionStatus getSessionStatus() {
/**
* Add the supplied attribute to the underlying model.
- * @see ModelMap#addAttribute(String, Object)
+ * A shortcut for {@code getModel().addAttribute(String, Object)}.
*/
public ModelAndViewContainer addAttribute(String name, Object value) {
getModel().addAttribute(name, value);
return this;
}
-
+
/**
* Add the supplied attribute to the underlying model.
- * @see Model#addAttribute(Object)
+ * A shortcut for {@code getModel().addAttribute(Object)}.
*/
public ModelAndViewContainer addAttribute(Object value) {
getModel().addAttribute(value);
@@ -206,7 +206,7 @@ public ModelAndViewContainer addAttribute(Object value) {
/**
* Copy all attributes to the underlying model.
- * @see ModelMap#addAllAttributes(Map)
+ * A shortcut for {@code getModel().addAllAttributes(Map)}.
*/
public ModelAndViewContainer addAllAttributes(Map The parent {@linkplain #getEnvironment() environment} is
* delegated to this (child) context if the parent is a
- * {@link ConfigurableApplicationContext} implementation.
+ * {@link org.springframework.context.ConfigurableApplicationContext} implementation.
* The parent {@linkplain #getServletContext() servlet context} is
* delegated to this (child) context if the parent is a {@link WebApplicationContext}
* implementation.
diff --git a/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/context/PortletContextScope.java b/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/context/PortletContextScope.java
index bdd5f6846c6b..4f3e0178b957 100644
--- a/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/context/PortletContextScope.java
+++ b/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/context/PortletContextScope.java
@@ -54,7 +54,7 @@ public class PortletContextScope implements Scope, DisposableBean {
/**
* Create a new Scope wrapper for the given PortletContext.
- * @param PortletContext the PortletContext to wrap
+ * @param portletContext the PortletContext to wrap
*/
public PortletContextScope(PortletContext portletContext) {
Assert.notNull(portletContext, "PortletContext must not be null");
diff --git a/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/context/StaticPortletApplicationContext.java b/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/context/StaticPortletApplicationContext.java
index 7ffc76336579..7452f6b34cde 100644
--- a/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/context/StaticPortletApplicationContext.java
+++ b/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/context/StaticPortletApplicationContext.java
@@ -88,7 +88,7 @@ protected void initPropertySources() {
* {@inheritDoc}
* The parent {@linkplain #getEnvironment() environment} is
* delegated to this (child) context if the parent is a
- * {@link ConfigurableApplicationContext} implementation.
+ * {@link org.springframework.context.ConfigurableApplicationContext} implementation.
* The parent {@linkplain #getServletContext() servlet context} is
* delegated to this (child) context if the parent is a {@link WebApplicationContext}
* implementation.
diff --git a/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/handler/PortletRequestMethodNotSupportedException.java b/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/handler/PortletRequestMethodNotSupportedException.java
index f347a027fba4..b9c9718a6e23 100644
--- a/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/handler/PortletRequestMethodNotSupportedException.java
+++ b/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/handler/PortletRequestMethodNotSupportedException.java
@@ -55,7 +55,6 @@ public PortletRequestMethodNotSupportedException(String method, String[] support
/**
* Create a new PortletRequestMethodNotSupportedException.
- * @param method the unsupported HTTP request method
* @param supportedMethods the actually supported HTTP methods
*/
public PortletRequestMethodNotSupportedException(String[] supportedMethods) {
diff --git a/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/AnnotationMethodHandlerExceptionResolver.java b/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/AnnotationMethodHandlerExceptionResolver.java
index 9095a3bc043f..13955c415d6b 100644
--- a/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/AnnotationMethodHandlerExceptionResolver.java
+++ b/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/AnnotationMethodHandlerExceptionResolver.java
@@ -75,8 +75,7 @@
public class AnnotationMethodHandlerExceptionResolver extends AbstractHandlerExceptionResolver {
// dummy method placeholder
- private static final Method NO_METHOD_FOUND = ClassUtils
- .getMethodIfAvailable(System.class, "currentTimeMillis", null);
+ private static final Method NO_METHOD_FOUND = ClassUtils.getMethodIfAvailable(System.class, "currentTimeMillis", (Class>[]) null);
private WebArgumentResolver[] customArgumentResolvers;
@@ -134,10 +133,9 @@ protected ModelAndView doResolveException(
private Method findBestExceptionHandlerMethod(Object handler, final Exception thrownException) {
final Class> handlerType = handler.getClass();
final Class extends Throwable> thrownExceptionType = thrownException.getClass();
-
Method handlerMethod = null;
- Map This is the sequence of events on the main thread in an async scenario:
+ * This is the sequence of events on the async thread:
+ * Implementations can use this Implementations of this method can ensure ThreadLocal attributes bound
+ * to the main thread are cleared and also prepare for binding them to the
+ * async thread.
+ * @param request current HTTP request
+ * @param response current HTTP response
+ * @param handler chosen handler to execute, for type and/or instance examination
+ */
+ void postHandleAsyncStarted(HttpServletRequest request, HttpServletResponse response, Object handler);
+
+}
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 d6eddaf7d7e5..7cc2edc5ba1f 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
@@ -50,6 +50,8 @@
import org.springframework.util.StringUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.ServletWebRequest;
+import org.springframework.web.context.request.async.AbstractDelegatingCallable;
+import org.springframework.web.context.request.async.AsyncExecutionChain;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.MultipartResolver;
@@ -130,6 +132,7 @@
* @author Juergen Hoeller
* @author Rob Harrop
* @author Chris Beams
+ * @author Rossen Stoyanchev
* @see org.springframework.web.HttpRequestHandler
* @see org.springframework.web.servlet.mvc.Controller
* @see org.springframework.web.context.ContextLoaderListener
@@ -297,11 +300,10 @@ public class DispatcherServlet extends FrameworkServlet {
/** FlashMapManager used by this servlet */
private FlashMapManager flashMapManager;
-
+
/** List of ViewResolvers used by this servlet */
private List If no implementation is configured then we default to
+ * If no implementation is configured then we default to
* {@code org.springframework.web.servlet.support.DefaultFlashMapManager}.
*/
private void initFlashMapManager(ApplicationContext context) {
@@ -814,6 +816,9 @@ protected Object createDefaultStrategy(ApplicationContext context, Class> claz
*/
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
+
+ AsyncExecutionChain asyncChain = AsyncExecutionChain.getForCurrentRequest(request);
+
if (logger.isDebugEnabled()) {
String requestUri = urlPathHelper.getRequestUri(request);
logger.debug("DispatcherServlet with name '" + getServletName() + "' processing " + request.getMethod() +
@@ -848,10 +853,15 @@ protected void doService(HttpServletRequest request, HttpServletResponse respons
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
+ asyncChain.addDelegatingCallable(getServiceAsyncCallable(request, attributesSnapshot));
+
try {
doDispatch(request, response);
}
finally {
+ if (asyncChain.isAsyncStarted()) {
+ return;
+ }
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
@@ -859,6 +869,27 @@ protected void doService(HttpServletRequest request, HttpServletResponse respons
}
}
+ /**
+ * Create a Callable to complete doService() processing asynchronously.
+ */
+ private AbstractDelegatingCallable getServiceAsyncCallable(
+ final HttpServletRequest request, final Map The handler will be obtained by applying the servlet's HandlerMappings in order.
@@ -873,11 +904,11 @@ protected void doService(HttpServletRequest request, HttpServletResponse respons
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
- int interceptorIndex = -1;
+ AsyncExecutionChain asyncChain = AsyncExecutionChain.getForCurrentRequest(request);
try {
- ModelAndView mv;
- boolean errorView = false;
+ ModelAndView mv = null;
+ Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
@@ -892,7 +923,7 @@ protected void doDispatch(HttpServletRequest request, HttpServletResponse respon
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
- // Process last-modified header, if supported by the handler.
+ // Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
@@ -906,76 +937,43 @@ protected void doDispatch(HttpServletRequest request, HttpServletResponse respon
}
}
- // Apply preHandle methods of registered interceptors.
- HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();
- if (interceptors != null) {
- for (int i = 0; i < interceptors.length; i++) {
- HandlerInterceptor interceptor = interceptors[i];
- if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) {
- triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
- return;
- }
- interceptorIndex = i;
- }
+ if (!mappedHandler.applyPreHandle(processedRequest, response)) {
+ return;
}
+ mappedHandler.addDelegatingCallables(processedRequest, response);
+
+ asyncChain.addDelegatingCallable(
+ getDispatchAsyncCallable(mappedHandler, request, response, processedRequest));
+
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
- // Do we need view name translation?
- if (mv != null && !mv.hasView()) {
- mv.setViewName(getDefaultViewName(request));
+ if (asyncChain.isAsyncStarted()) {
+ mappedHandler.applyPostHandleAsyncStarted(processedRequest, response);
+ logger.debug("Exiting request thread and leaving the response open");
+ return;
}
- // Apply postHandle methods of registered interceptors.
- if (interceptors != null) {
- for (int i = interceptors.length - 1; i >= 0; i--) {
- HandlerInterceptor interceptor = interceptors[i];
- interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv);
- }
- }
- }
- catch (ModelAndViewDefiningException ex) {
- logger.debug("ModelAndViewDefiningException encountered", ex);
- mv = ex.getModelAndView();
- }
- catch (Exception ex) {
- Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
- mv = processHandlerException(processedRequest, response, handler, ex);
- errorView = (mv != null);
- }
+ applyDefaultViewName(request, mv);
- // Did the handler return a view to render?
- if (mv != null && !mv.wasCleared()) {
- render(mv, processedRequest, response);
- if (errorView) {
- WebUtils.clearErrorRequestAttributes(request);
- }
+ mappedHandler.applyPostHandle(processedRequest, response, mv);
}
- else {
- if (logger.isDebugEnabled()) {
- logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
- "': assuming HandlerAdapter completed request handling");
- }
+ catch (Exception ex) {
+ dispatchException = ex;
}
-
- // Trigger after-completion for successful outcome.
- triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
+ processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
-
catch (Exception ex) {
- // Trigger after-completion for thrown exception.
- triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
- throw ex;
+ triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Error err) {
- ServletException ex = new NestedServletException("Handler processing failed", err);
- // Trigger after-completion for thrown exception.
- triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
- throw ex;
+ triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
}
-
finally {
+ if (asyncChain.isAsyncStarted()) {
+ return;
+ }
// Clean up any resources used by a multipart request.
if (processedRequest != request) {
cleanupMultipart(processedRequest);
@@ -983,6 +981,94 @@ protected void doDispatch(HttpServletRequest request, HttpServletResponse respon
}
}
+ /**
+ * Do we need view name translation?
+ */
+ private void applyDefaultViewName(HttpServletRequest request, ModelAndView mv) throws Exception {
+ if (mv != null && !mv.hasView()) {
+ mv.setViewName(getDefaultViewName(request));
+ }
+ }
+
+ /**
+ * Handle the result of handler selection and handler invocation, which is
+ * either a ModelAndView or an Exception to be resolved to a ModelAndView.
+ */
+ private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
+ HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
+
+ boolean errorView = false;
+
+ if (exception != null) {
+ if (exception instanceof ModelAndViewDefiningException) {
+ logger.debug("ModelAndViewDefiningException encountered", exception);
+ mv = ((ModelAndViewDefiningException) exception).getModelAndView();
+ }
+ else {
+ Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
+ mv = processHandlerException(request, response, handler, exception);
+ errorView = (mv != null);
+ }
+ }
+
+ // Did the handler return a view to render?
+ if (mv != null && !mv.wasCleared()) {
+ render(mv, request, response);
+ if (errorView) {
+ WebUtils.clearErrorRequestAttributes(request);
+ }
+ }
+ else {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
+ "': assuming HandlerAdapter completed request handling");
+ }
+ }
+
+ if (mappedHandler != null) {
+ mappedHandler.triggerAfterCompletion(request, response, null);
+ }
+ }
+
+ /**
+ * Create a Callable to complete doDispatch processing asynchronously.
+ */
+ private AbstractDelegatingCallable getDispatchAsyncCallable(
+ final HandlerExecutionChain mappedHandler,
+ final HttpServletRequest request, final HttpServletResponse response,
+ final HttpServletRequest processedRequest) throws Exception {
+
+ return new AbstractDelegatingCallable() {
+ public Object call() throws Exception {
+ try {
+ ModelAndView mv = null;
+ Exception dispatchException = null;
+ try {
+ mv = (ModelAndView) getNextCallable().call();
+ applyDefaultViewName(processedRequest, mv);
+ mappedHandler.applyPostHandle(request, response, mv);
+ }
+ catch (Exception ex) {
+ dispatchException = ex;
+ }
+ processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
+ }
+ catch (Exception ex) {
+ triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
+ }
+ catch (Error err) {
+ triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
+ }
+ finally {
+ if (processedRequest != request) {
+ cleanupMultipart(processedRequest);
+ }
+ }
+ return null;
+ }
+ };
+ }
+
/**
* Build a LocaleContext for the given request, exposing the request's primary locale as current locale.
* The default implementation uses the dispatcher's LocaleResolver to obtain the current locale,
@@ -996,7 +1082,6 @@ protected LocaleContext buildLocaleContext(final HttpServletRequest request) {
public Locale getLocale() {
return localeResolver.resolveLocale(request);
}
- @Override
public String toString() {
return getLocale().toString();
}
@@ -1216,36 +1301,23 @@ protected View resolveViewName(String viewName, Map The default implementation is empty. Note that this method is designed to allow subclasses to modify the application
- * context, while {@link #initializeWebApplicationContext} is designed to allow
+ * context, while {@link #initWebApplicationContext} is designed to allow
* end-users to modify the context through the use of
* {@link ApplicationContextInitializer}s.
* @param wac the configured WebApplicationContext (not refreshed yet)
* @see #createWebApplicationContext
- * @see #initializeWebApplicationContext
+ * @see #initWebApplicationContext
* @see ConfigurableWebApplicationContext#refresh()
*/
protected void postProcessWebApplicationContext(ConfigurableWebApplicationContext wac) {
@@ -850,7 +853,6 @@ protected void doTrace(HttpServletRequest request, HttpServletResponse response)
super.doTrace(request, response);
}
-
/**
* Process this request, publishing an event regardless of the outcome.
* The actual event handling is performed by the abstract
@@ -862,49 +864,96 @@ protected final void processRequest(HttpServletRequest request, HttpServletRespo
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
- // Expose current LocaleResolver and request as LocaleContext.
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
- LocaleContextHolder.setLocaleContext(buildLocaleContext(request), this.threadContextInheritable);
+ LocaleContext localeContext = buildLocaleContext(request);
- // Expose current RequestAttributes to current thread.
- RequestAttributes previousRequestAttributes = RequestContextHolder.getRequestAttributes();
+ RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = null;
- if (previousRequestAttributes == null || previousRequestAttributes.getClass().equals(ServletRequestAttributes.class)) {
+ if (previousAttributes == null || previousAttributes.getClass().equals(ServletRequestAttributes.class)) {
requestAttributes = new ServletRequestAttributes(request);
- RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
}
- if (logger.isTraceEnabled()) {
- logger.trace("Bound request context to thread: " + request);
- }
+ initContextHolders(request, localeContext, requestAttributes);
+
+ AsyncExecutionChain chain = AsyncExecutionChain.getForCurrentRequest(request);
+ chain.addDelegatingCallable(getAsyncCallable(startTime, request, response,
+ previousLocaleContext, previousAttributes, localeContext, requestAttributes));
try {
doService(request, response);
}
- catch (ServletException ex) {
- failureCause = ex;
- throw ex;
+ catch (Throwable t) {
+ failureCause = t;
}
- catch (IOException ex) {
- failureCause = ex;
- throw ex;
+ finally {
+ resetContextHolders(request, previousLocaleContext, previousAttributes);
+ if (chain.isAsyncStarted()) {
+ return;
+ }
+ finalizeProcessing(startTime, request, response, requestAttributes, failureCause);
+ }
+ }
+
+ /**
+ * Build a LocaleContext for the given request, exposing the request's
+ * primary locale as current locale.
+ * @param request current HTTP request
+ * @return the corresponding LocaleContext
+ */
+ protected LocaleContext buildLocaleContext(HttpServletRequest request) {
+ return new SimpleLocaleContext(request.getLocale());
+ }
+
+ private void initContextHolders(HttpServletRequest request,
+ LocaleContext localeContext, RequestAttributes attributes) {
+
+ LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);
+ if (attributes != null) {
+ RequestContextHolder.setRequestAttributes(attributes, this.threadContextInheritable);
+ }
+ if (logger.isTraceEnabled()) {
+ logger.trace("Bound request context to thread: " + request);
}
- catch (Throwable ex) {
- failureCause = ex;
- throw new NestedServletException("Request processing failed", ex);
+ }
+
+ private void resetContextHolders(HttpServletRequest request,
+ LocaleContext prevLocaleContext, RequestAttributes previousAttributes) {
+
+ LocaleContextHolder.setLocaleContext(prevLocaleContext, this.threadContextInheritable);
+ RequestContextHolder.setRequestAttributes(previousAttributes, this.threadContextInheritable);
+ if (logger.isTraceEnabled()) {
+ logger.trace("Cleared thread-bound request context: " + request);
}
+ }
+
+ /**
+ * Log and re-throw unhandled exceptions, publish a ServletRequestHandledEvent, etc.
+ */
+ private void finalizeProcessing(long startTime, HttpServletRequest request, HttpServletResponse response,
+ ServletRequestAttributes requestAttributes, Throwable t) throws ServletException, IOException {
+ Throwable failureCause = null;
+ try {
+ if (t != null) {
+ if (t instanceof ServletException) {
+ failureCause = t;
+ throw (ServletException) t;
+ }
+ else if (t instanceof IOException) {
+ failureCause = t;
+ throw (IOException) t;
+ }
+ else {
+ NestedServletException ex = new NestedServletException("Request processing failed", t);
+ failureCause = ex;
+ throw ex;
+ }
+ }
+ }
finally {
- // Clear request attributes and reset thread-bound context.
- LocaleContextHolder.setLocaleContext(previousLocaleContext, this.threadContextInheritable);
if (requestAttributes != null) {
- RequestContextHolder.setRequestAttributes(previousRequestAttributes, this.threadContextInheritable);
requestAttributes.requestCompleted();
}
- if (logger.isTraceEnabled()) {
- logger.trace("Cleared thread-bound request context: " + request);
- }
-
if (logger.isDebugEnabled()) {
if (failureCause != null) {
this.logger.debug("Could not complete request", failureCause);
@@ -927,13 +976,30 @@ protected final void processRequest(HttpServletRequest request, HttpServletRespo
}
/**
- * Build a LocaleContext for the given request, exposing the request's
- * primary locale as current locale.
- * @param request current HTTP request
- * @return the corresponding LocaleContext
+ * Create a Callable to use to complete processing in an async execution chain.
*/
- protected LocaleContext buildLocaleContext(HttpServletRequest request) {
- return new SimpleLocaleContext(request.getLocale());
+ private AbstractDelegatingCallable getAsyncCallable(final long startTime,
+ final HttpServletRequest request, final HttpServletResponse response,
+ final LocaleContext previousLocaleContext, final RequestAttributes previousAttributes,
+ final LocaleContext localeContext, final ServletRequestAttributes requestAttributes) {
+
+ return new AbstractDelegatingCallable() {
+ public Object call() throws Exception {
+ initContextHolders(request, localeContext, requestAttributes);
+ Throwable unhandledFailure = null;
+ try {
+ getNextCallable().call();
+ }
+ catch (Throwable t) {
+ unhandledFailure = t;
+ }
+ finally {
+ resetContextHolders(request, previousLocaleContext, previousAttributes);
+ finalizeProcessing(startTime, request, response, requestAttributes, unhandledFailure);
+ }
+ return null;
+ }
+ };
}
/**
@@ -965,7 +1031,6 @@ protected String getUsernameForRequest(HttpServletRequest request) {
protected abstract void doService(HttpServletRequest request, HttpServletResponse response)
throws Exception;
-
/**
* Close the WebApplicationContext of this servlet.
* @see org.springframework.context.ConfigurableApplicationContext#close()
@@ -978,7 +1043,6 @@ public void destroy() {
}
}
-
/**
* ApplicationListener endpoint that receives events from this servlet's WebApplicationContext
* only, delegating to This class registers the following {@link HandlerMapping}s: Note: Additional HandlerMappings may be registered
- * as a result of using the {@code Note: Additional HandlerMappings may be registered
+ * as a result of using the {@code This class registers the following {@link HandlerAdapter}s:
* This class registers the following {@link HandlerExceptionResolver}s:
* Both the {@link RequestMappingHandlerAdapter} and the
- * {@link ExceptionHandlerExceptionResolver} are configured with default
+ *
+ * Both the {@link RequestMappingHandlerAdapter} and the
+ * {@link ExceptionHandlerExceptionResolver} are configured with default
* instances of the following kind, unless custom instances are provided:
* This class registers the following {@link HandlerMapping}s: Registers these {@link HandlerAdapter}s:
* Registers a {@link HandlerExceptionResolverComposite} with this chain of
* exception resolvers:
* Both the {@link RequestMappingHandlerAdapter} and the
- * {@link ExceptionHandlerExceptionResolver} are configured with default
+ * Both the {@link RequestMappingHandlerAdapter} and the
+ * {@link ExceptionHandlerExceptionResolver} are configured with default
* instances of the following kind, unless custom instances are provided:
* Custom argument resolvers are invoked before built-in resolvers
- * except for those that rely on the presence of annotations (e.g.
- * {@code @RequestParameter}, {@code @PathVariable}, etc.).
- * The latter can be customized by configuring the
- * {@link RequestMappingHandlerAdapter} directly.
- * @param argumentResolvers the list of custom converters;
+ * Custom argument resolvers are invoked before built-in resolvers
+ * except for those that rely on the presence of annotations (e.g.
+ * {@code @RequestParameter}, {@code @PathVariable}, etc.).
+ * The latter can be customized by configuring the
+ * {@link RequestMappingHandlerAdapter} directly.
+ * @param argumentResolvers the list of custom converters;
* initially an empty list.
*/
protected void addArgumentResolvers(List Custom return value handlers are invoked before built-in ones except
- * for those that rely on the presence of annotations (e.g.
- * {@code @ResponseBody}, {@code @ModelAttribute}, etc.).
- * The latter can be customized by configuring the
+ * Custom return value handlers are invoked before built-in ones except
+ * for those that rely on the presence of annotations (e.g.
+ * {@code @ResponseBody}, {@code @ModelAttribute}, etc.).
+ * The latter can be customized by configuring the
* {@link RequestMappingHandlerAdapter} directly.
- * @param returnValueHandlers the list of custom handlers;
+ * @param returnValueHandlers the list of custom handlers;
* initially an empty list.
*/
protected void addReturnValueHandlers(List Adds the following exception resolvers:
* For each registered handler method, a unique mapping is maintained with
+ * subclasses defining the details of the mapping type {@code 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
+ *
+ * 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.
- *
- * @param An implementation of {@code RequestCondition} itself, a
- * {@code RequestConditionHolder} decorates the held request condition allowing
- * it to be combined and compared with other custom request conditions while
+ *
+ * An implementation of {@code RequestCondition} itself, a
+ * {@code RequestConditionHolder} decorates the held request condition allowing
+ * it to be combined and compared with other custom request conditions while
* ensuring type and null safety.
- *
+ *
* @author Rossen Stoyanchev
* @since 3.1
*/
@@ -38,9 +38,9 @@ public final class RequestConditionHolder extends AbstractRequestCondition This handler does not have a defined behavior for {@code null} return
+ * values and will raise an {@link IllegalArgumentException}.
+ *
+ * @author Rossen Stoyanchev
+ * @since 3.2
+ */
+public class AsyncMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
+
+ public boolean supportsReturnType(MethodParameter returnType) {
+ Class> paramType = returnType.getParameterType();
+ return Callable.class.isAssignableFrom(paramType) || DeferredResult.class.isAssignableFrom(paramType);
+ }
+
+ @SuppressWarnings("unchecked")
+ public void handleReturnValue(Object returnValue,
+ MethodParameter returnType, ModelAndViewContainer mavContainer,
+ NativeWebRequest webRequest) throws Exception {
+
+ Assert.notNull(returnValue, "A Callable or a DeferredValue is required");
+
+ mavContainer.setRequestHandled(true);
+
+ Class> paramType = returnType.getParameterType();
+ ServletRequest servletRequest = webRequest.getNativeRequest(ServletRequest.class);
+ AsyncExecutionChain chain = AsyncExecutionChain.getForCurrentRequest(servletRequest);
+
+ if (Callable.class.isAssignableFrom(paramType)) {
+ chain.setCallable((Callable These converters are used to convert from and to HTTP requests and responses.
@@ -174,7 +174,7 @@ public HandlerMethodReturnValueHandlerComposite getReturnValueHandlers() {
public void setMessageConverters(List Support for custom argument and return value types can be added via
* {@link #setCustomArgumentResolvers} and {@link #setCustomReturnValueHandlers}.
* Or alternatively to re-configure all argument and return value types use
@@ -103,7 +111,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
private List Note: This method is available for backwards
- * compatibility only. However, it is recommended to re-write a
+ * Provide custom {@link ModelAndViewResolver}s.
+ * Note: This method is available for backwards
+ * compatibility only. However, it is recommended to re-write a
* {@code ModelAndViewResolver} as {@link HandlerMethodReturnValueHandler}.
- * An adapter between the two interfaces is not possible since the
+ * An adapter between the two interfaces is not possible since the
* {@link HandlerMethodReturnValueHandler#supportsReturnType} method
* cannot be implemented. Hence {@code ModelAndViewResolver}s are limited
- * to always being invoked at the end after all other return value
+ * to always being invoked at the end after all other return value
* handlers have been given a chance.
- * A {@code HandlerMethodReturnValueHandler} provides better access to
+ * A {@code HandlerMethodReturnValueHandler} provides better access to
* the return type and controller method information and can be ordered
* freely relative to other return value handlers.
*/
@@ -273,8 +285,8 @@ public List In contrast to the "cacheSeconds" property which will apply to all general
- * handlers (but not to In contrast to the "cacheSeconds" property which will apply to all general
+ * handlers (but not to Setting this flag to {@code true} guarantees the "default" model is
- * never used in a redirect scenario even if a RedirectAttributes argument
- * is not declared. Setting it to {@code false} means the "default" model
- * may be used in a redirect if the controller method doesn't declare a
+ * Setting this flag to {@code true} guarantees the "default" model is
+ * never used in a redirect scenario even if a RedirectAttributes argument
+ * is not declared. Setting it to {@code 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 {@code false} but new applications should
+ * The default setting is {@code false} but new applications should
* consider setting it to {@code true}.
* @see RedirectAttributes
*/
@@ -375,9 +387,32 @@ public void setIgnoreDefaultModelOnRedirect(boolean ignoreDefaultModelOnRedirect
this.ignoreDefaultModelOnRedirect = ignoreDefaultModelOnRedirect;
}
+ /**
+ * Set the AsyncTaskExecutor to use when a controller method returns a
+ * {@code Callable}.
+ * The default instance type is a {@link SimpleAsyncTaskExecutor}.
+ * It's recommended to change that default in production as the simple
+ * executor does not re-use threads.
+ */
+ public void setAsyncTaskExecutor(AsyncTaskExecutor taskExecutor) {
+ this.taskExecutor = taskExecutor;
+ }
+
+ /**
+ * Set the timeout for asynchronous request processing in milliseconds.
+ * When the timeout begins depends on the underlying async technology.
+ * With the Servlet 3 async support the timeout begins after the main
+ * processing thread has exited and has been returned to the container pool.
+ * If a value is not provided, the default timeout of the underlying
+ * async technology is used (10 seconds on Tomcat with Servlet 3 async).
+ */
+ public void setAsyncRequestTimeout(long asyncRequestTimeout) {
+ this.asyncRequestTimeout = asyncRequestTimeout;
+ }
+
/**
* {@inheritDoc}
- * A {@link ConfigurableBeanFactory} is expected for resolving
+ * A {@link ConfigurableBeanFactory} is expected for resolving
* expressions in method argument default values.
*/
public void setBeanFactory(BeanFactory beanFactory) {
@@ -387,7 +422,7 @@ public void setBeanFactory(BeanFactory beanFactory) {
}
/**
- * Return the owning factory of this bean instance, or {@code null}.
+ * Return the owning factory of this bean instance, or {@code null}.
*/
protected ConfigurableBeanFactory getBeanFactory() {
return this.beanFactory;
@@ -451,7 +486,7 @@ private List The default value is {@code true}.
+ * The default value is {@code true}.
*/
public void setUseSuffixPatternMatch(boolean useSuffixPatternMatch) {
this.useSuffixPatternMatch = useSuffixPatternMatch;
}
-
+
/**
* Whether to match to URLs irrespective of the presence of a trailing slash.
* If enabled a method mapped to "/users" also matches to "/users/".
@@ -78,21 +78,22 @@ public boolean useTrailingSlashMatch() {
}
/**
- * {@inheritDoc}
+ * {@inheritDoc}
* Expects a handler to have a type-level @{@link Controller} annotation.
*/
@Override
protected boolean isHandler(Class> beanType) {
- return AnnotationUtils.findAnnotation(beanType, Controller.class) != null;
+ return ((AnnotationUtils.findAnnotation(beanType, Controller.class) != null) ||
+ (AnnotationUtils.findAnnotation(beanType, RequestMapping.class) != null));
}
/**
* Uses method and type-level @{@link RequestMapping} annotations to create
* the RequestMappingInfo.
- *
+ *
* @return the created RequestMappingInfo, or {@code null} if the method
* does not have a {@code @RequestMapping} annotation.
- *
+ *
* @see #getCustomMethodCondition(Method)
* @see #getCustomTypeCondition(Class)
*/
@@ -114,22 +115,22 @@ protected RequestMappingInfo getMappingForMethod(Method method, Class> handler
/**
* Provide a custom method-level request condition.
- * The custom {@link RequestCondition} can be of any type so long as the
+ * The custom {@link RequestCondition} can be of any type so long as the
* same condition type is returned from all calls to this method in order
- * to ensure custom request conditions can be combined and compared.
+ * to ensure custom request conditions can be combined and compared.
* @param method the handler method for which to create the condition
* @return the condition, or {@code null}
*/
protected RequestCondition> getCustomMethodCondition(Method method) {
return null;
}
-
+
/**
* Provide a custom type-level request condition.
- * The custom {@link RequestCondition} can be of any type so long as the
+ * The custom {@link RequestCondition} can be of any type so long as the
* same condition type is returned from all calls to this method in order
- * to ensure custom request conditions can be combined and compared.
- * @param method the handler method for which to create the condition
+ * to ensure custom request conditions can be combined and compared.
+ * @param handlerType the handler type for which to create the condition
* @return the condition, or {@code null}
*/
protected RequestCondition> getCustomTypeCondition(Class> handlerType) {
@@ -141,13 +142,13 @@ protected RequestCondition> getCustomTypeCondition(Class> handlerType) {
*/
private RequestMappingInfo createRequestMappingInfo(RequestMapping annotation, RequestCondition> customCondition) {
return new RequestMappingInfo(
- new PatternsRequestCondition(annotation.value(),
+ new PatternsRequestCondition(annotation.value(),
getUrlPathHelper(), getPathMatcher(), this.useSuffixPatternMatch, this.useTrailingSlashMatch),
new RequestMethodsRequestCondition(annotation.method()),
new ParamsRequestCondition(annotation.params()),
new HeadersRequestCondition(annotation.headers()),
new ConsumesRequestCondition(annotation.consumes(), annotation.headers()),
- new ProducesRequestCondition(annotation.produces(), annotation.headers()),
+ new ProducesRequestCondition(annotation.produces(), annotation.headers()),
customCondition);
}
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 3eb3261fb7dd..32d4920750f5 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
@@ -17,14 +17,17 @@
package org.springframework.web.servlet.mvc.method.annotation;
import java.io.IOException;
+import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
+import java.util.concurrent.Callable;
import org.springframework.http.HttpStatus;
+import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ResponseStatus;
-import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
-import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+import org.springframework.web.context.request.async.AbstractDelegatingCallable;
+import org.springframework.web.context.request.async.AsyncExecutionChain;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite;
import org.springframework.web.method.support.InvocableHandlerMethod;
@@ -32,16 +35,19 @@
import org.springframework.web.servlet.View;
/**
- * Extends {@link InvocableHandlerMethod} with the ability to handle the value returned from the method through
- * a registered {@link HandlerMethodArgumentResolver} that supports the given return value type.
- * Return value handling may include writing to the response or updating the {@link ModelAndViewContainer} structure.
+ * Extends {@link InvocableHandlerMethod} with the ability to handle return
+ * values through a registered {@link HandlerMethodReturnValueHandler} and
+ * also supports setting the response status based on a method-level
+ * {@code @ResponseStatus} annotation.
*
- * If the underlying method has a {@link ResponseStatus} instruction, the status on the response is set
- * accordingly after the method is invoked but before the return value is handled.
+ * A {@code null} return value (including void) may be interpreted as the
+ * end of request processing in combination with a {@code @ResponseStatus}
+ * annotation, a not-modified check condition
+ * (see {@link ServletWebRequest#checkNotModified(long)}), or
+ * a method argument that provides access to the response stream.
*
* @author Rossen Stoyanchev
* @since 3.1
- * @see #invokeAndHandle(NativeWebRequest, ModelAndViewContainer, Object...)
*/
public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
@@ -51,10 +57,6 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
- public void setHandlerMethodReturnValueHandlers(HandlerMethodReturnValueHandlerComposite returnValueHandlers) {
- this.returnValueHandlers = returnValueHandlers;
- }
-
/**
* Creates a {@link ServletInvocableHandlerMethod} instance with the given bean and method.
* @param handler the object handler
@@ -71,43 +73,47 @@ public ServletInvocableHandlerMethod(Object handler, Method method) {
}
/**
- * Invokes the method and handles the return value through a registered {@link HandlerMethodReturnValueHandler}.
- * Return value handling may be skipped entirely when the method returns {@code null} (also possibly due
- * to a {@code void} return type) and one of the following additional conditions is true:
- * After the return value is handled, callers of this method can use the {@link ModelAndViewContainer}
- * to gain access to model attributes, view selection choices, and to check if view resolution is even needed.
+ * Register {@link HandlerMethodReturnValueHandler} instances to use to
+ * handle return values.
+ */
+ public void setHandlerMethodReturnValueHandlers(HandlerMethodReturnValueHandlerComposite returnValueHandlers) {
+ this.returnValueHandlers = returnValueHandlers;
+ }
+
+ /**
+ * Invokes the method and handles the return value through a registered
+ * {@link HandlerMethodReturnValueHandler}.
*
- * @param request the current request
- * @param mavContainer the {@link ModelAndViewContainer} for the current request
- * @param providedArgs argument values to try to use without the need for view resolution
+ * @param webRequest the current request
+ * @param mavContainer the ModelAndViewContainer for this request
+ * @param providedArgs "given" arguments matched by type, not resolved
*/
- public final void invokeAndHandle(
- NativeWebRequest request, ModelAndViewContainer mavContainer,
- Object... providedArgs) throws Exception {
+ public final void invokeAndHandle(ServletWebRequest webRequest,
+ ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
+
+ AsyncExecutionChain chain = AsyncExecutionChain.getForCurrentRequest(webRequest.getRequest());
+ chain.addDelegatingCallable(geAsyncCallable(webRequest, mavContainer, providedArgs));
- Object returnValue = invokeForRequest(request, mavContainer, providedArgs);
+ Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
- setResponseStatus((ServletWebRequest) request);
+ setResponseStatus(webRequest);
if (returnValue == null) {
- if (isRequestNotModified(request) || hasResponseStatus() || mavContainer.isRequestHandled()) {
+ if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) {
mavContainer.setRequestHandled(true);
return;
}
}
+ else if (StringUtils.hasText(this.responseReason)) {
+ mavContainer.setRequestHandled(true);
+ return;
+ }
mavContainer.setRequestHandled(false);
try {
- returnValueHandlers.handleReturnValue(returnValue, getReturnType(), mavContainer, request);
+ this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
+
} catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
@@ -116,6 +122,21 @@ public final void invokeAndHandle(
}
}
+ /**
+ * Create a Callable to populate the ModelAndViewContainer asynchronously.
+ */
+ private AbstractDelegatingCallable geAsyncCallable(final ServletWebRequest webRequest,
+ final ModelAndViewContainer mavContainer, final Object... providedArgs) {
+
+ return new AbstractDelegatingCallable() {
+ public Object call() throws Exception {
+ mavContainer.setRequestHandled(false);
+ new CallableHandlerMethod(getNextCallable()).invokeAndHandle(webRequest, mavContainer, providedArgs);
+ return null;
+ }
+ };
+ }
+
private String getReturnValueHandlingErrorMessage(String message, Object returnValue) {
StringBuilder sb = new StringBuilder(message);
if (returnValue != null) {
@@ -129,17 +150,19 @@ private String getReturnValueHandlingErrorMessage(String message, Object returnV
* Set the response status according to the {@link ResponseStatus} annotation.
*/
private void setResponseStatus(ServletWebRequest webRequest) throws IOException {
- if (this.responseStatus != null) {
- if (StringUtils.hasText(this.responseReason)) {
- webRequest.getResponse().sendError(this.responseStatus.value(), this.responseReason);
- }
- else {
- webRequest.getResponse().setStatus(this.responseStatus.value());
- }
+ if (this.responseStatus == null) {
+ return;
+ }
- // to be picked up by the RedirectView
- webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, this.responseStatus);
+ if (StringUtils.hasText(this.responseReason)) {
+ webRequest.getResponse().sendError(this.responseStatus.value(), this.responseReason);
}
+ else {
+ webRequest.getResponse().setStatus(this.responseStatus.value());
+ }
+
+ // to be picked up by the RedirectView
+ webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, this.responseStatus);
}
/**
@@ -147,8 +170,8 @@ private void setResponseStatus(ServletWebRequest webRequest) throws IOException
* @see ServletWebRequest#checkNotModified(long)
* @see ServletWebRequest#checkNotModified(String)
*/
- private boolean isRequestNotModified(NativeWebRequest request) {
- return ((ServletWebRequest) request).isNotModified();
+ private boolean isRequestNotModified(ServletWebRequest webRequest) {
+ return webRequest.isNotModified();
}
/**
@@ -157,4 +180,24 @@ private boolean isRequestNotModified(NativeWebRequest request) {
private boolean hasResponseStatus() {
return responseStatus != null;
}
+
+
+ /**
+ * Wraps the Callable returned from a HandlerMethod so may be invoked just
+ * like the HandlerMethod with the same return value handling guarantees.
+ * Method-level annotations must be on the HandlerMethod, not the Callable.
+ */
+ private class CallableHandlerMethod extends ServletInvocableHandlerMethod {
+
+ public CallableHandlerMethod(Callable> callable) {
+ super(callable, ClassUtils.getMethod(callable.getClass(), "call"));
+ this.setHandlerMethodReturnValueHandlers(ServletInvocableHandlerMethod.this.returnValueHandlers);
+ }
+
+ @Override
+ public A getMethodAnnotation(Class annotationType) {
+ return ServletInvocableHandlerMethod.this.getMethodAnnotation(annotationType);
+ }
+ }
+
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletModelAttributeMethodProcessor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletModelAttributeMethodProcessor.java
index e2865dc166a9..56c45e033b7b 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletModelAttributeMethodProcessor.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletModelAttributeMethodProcessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -37,8 +37,8 @@
/**
* A Servlet-specific {@link org.springframework.web.method.annotation.ModelAttributeMethodProcessor} that applies data
- * binding through a WebDataBinder of type {@link ServletRequestDataBinder}.
- *
+ * binding through a WebDataBinder of type {@link ServletRequestDataBinder}.
+ *
* Also adds a fall-back strategy to instantiate the model attribute from a
* URI template variable or from a request parameter if the name matches the
* model attribute name and there is an appropriate type conversion strategy.
@@ -50,7 +50,7 @@ public class ServletModelAttributeMethodProcessor extends ModelAttributeMethodPr
/**
* @param annotationNotRequired if "true", non-simple method arguments and
- * return values are considered model attributes with or without a
+ * return values are considered model attributes with or without a
* {@code @ModelAttribute} annotation.
*/
public ServletModelAttributeMethodProcessor(boolean annotationNotRequired) {
@@ -62,12 +62,12 @@ public ServletModelAttributeMethodProcessor(boolean annotationNotRequired) {
* request parameter if the name matches to the model attribute name and
* if there is an appropriate type conversion strategy. If none of these
* are true delegate back to the base class.
- * @see #createAttributeFromUriValue
+ * @see #createAttributeFromRequestValue(String, String, MethodParameter, WebDataBinderFactory, NativeWebRequest)
*/
@Override
- protected final Object createAttribute(String attributeName,
- MethodParameter parameter,
- WebDataBinderFactory binderFactory,
+ protected final Object createAttribute(String attributeName,
+ MethodParameter parameter,
+ WebDataBinderFactory binderFactory,
NativeWebRequest request) throws Exception {
String value = getRequestValueForAttribute(attributeName, request);
@@ -83,7 +83,7 @@ protected final Object createAttribute(String attributeName,
/**
* Obtain a value from the request that may be used to instantiate the
- * model attribute through type conversion from String to the target type.
+ * model attribute through type conversion from String to the target type.
* The default implementation looks for the attribute name to match
* a URI variable first and then a request parameter.
* @param attributeName the model attribute name
@@ -105,7 +105,7 @@ else if (StringUtils.hasText(request.getParameter(attributeName))) {
@SuppressWarnings("unchecked")
protected final Map The default implementation converts only if there a registered
+ * The default implementation converts only if there a registered
* {@link Converter} that can perform the conversion.
* @param sourceValue the source value to create the model attribute from
* @param attributeName the name of the attribute, never {@code null}
@@ -125,9 +125,9 @@ protected final Map This handler is intended to be used with a "/*" mapping when the {@link DispatcherServlet}
+ * This handler is intended to be used with a "/*" mapping when the
+ * {@link org.springframework.web.servlet.DispatcherServlet DispatcherServlet}
* is mapped to "/", thus overriding the Servlet container's default handling of static resources.
* The mapping to this handler should generally be ordered as the last in the chain so that it will
* only execute when no other more specific mappings (i.e., to controllers) can be matched.
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 a66aaf0cdea4..a4cc6669a5a0 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
@@ -25,6 +25,7 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
@@ -38,7 +39,7 @@
import org.springframework.web.servlet.support.WebContentGenerator;
/**
- * {@link HttpRequestHandler} that serves static resources optimized for superior browser performance
+ * {@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).
*
@@ -50,7 +51,7 @@
* (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/",
+ * 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.
*
* To ensure that users with a primed browser cache get the latest changes to application-specific
@@ -66,7 +67,7 @@
* @author Juergen Hoeller
* @since 3.0.4
*/
-public class ResourceHttpRequestHandler extends WebContentGenerator implements HttpRequestHandler {
+public class ResourceHttpRequestHandler extends WebContentGenerator implements HttpRequestHandler, InitializingBean {
private static final boolean jafPresent =
ClassUtils.isPresent("javax.activation.FileTypeMap", ResourceHttpRequestHandler.class.getClassLoader());
@@ -87,6 +88,9 @@ public void setLocations(List 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.
- *
+ *
* Can be instantiated manually, or automatically exposed to views as model attribute via AbstractView's
* "requestContextAttribute" property.
- *
+ *
* 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
* @since 03.03.2003
* @see org.springframework.web.servlet.DispatcherServlet
@@ -83,8 +83,8 @@ public class RequestContext {
public static final String WEB_APPLICATION_CONTEXT_ATTRIBUTE = RequestContext.class.getName() + ".CONTEXT";
/**
- * The name of the bean to use to look up in an implementation of
- * {@link RequestDataValueProcessor} has been configured.
+ * The name of the bean to use to look up in an implementation of
+ * {@link RequestDataValueProcessor} has been configured.
*/
private static final String REQUEST_DATA_VALUE_PROCESSOR_BEAN_NAME = "requestDataValueProcessor";
@@ -148,7 +148,7 @@ public RequestContext(HttpServletRequest request, ServletContext servletContext)
* @param model the model attributes for the current view (can be Delegates to the UrlPathHelper for decoding the context path.
* @see javax.servlet.http.HttpServletRequest#getContextPath
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/ContentNegotiatingViewResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/ContentNegotiatingViewResolver.java
index e304511d2692..588489d4716f 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/ContentNegotiatingViewResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/ContentNegotiatingViewResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -101,6 +101,7 @@
*
* @author Arjen Poutsma
* @author Juergen Hoeller
+ * @author Rossen Stoyanchev
* @since 3.0
* @see ViewResolver
* @see InternalResourceViewResolver
@@ -117,6 +118,9 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
private static final UrlPathHelper urlPathHelper = new UrlPathHelper();
+ static {
+ urlPathHelper.setUrlDecode(false);
+ }
private int order = Ordered.HIGHEST_PRECEDENCE;
@@ -270,7 +274,7 @@ protected void initServletContext(ServletContext servletContext) {
String name = viewResolvers.get(i).getClass().getName() + i;
getApplicationContext().getAutowireCapableBeanFactory().initializeBean(viewResolvers.get(i), name);
}
-
+
}
if (this.viewResolvers.isEmpty()) {
logger.warn("Did not find any ViewResolvers to delegate to; please configure them using the " +
@@ -351,13 +355,13 @@ protected List By default, the entire contents of the model map (with the exception of framework-specific classes)
+ * will be encoded as JSON. If the model contains only one key, you can have it extracted encoded as JSON
+ * alone via {@link #setExtractValueFromSingleKeyModel}.
+ *
+ * @author Jeremy Grelle
+ * @author Arjen Poutsma
+ * @author Rossen Stoyanchev
+ * @since 3.2
+ * @see org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
+ */
+public class MappingJackson2JsonView extends AbstractView {
+
+ /**
+ * Default content type. Overridable as bean property.
+ */
+ public static final String DEFAULT_CONTENT_TYPE = "application/json";
+
+
+ private ObjectMapper objectMapper = new ObjectMapper();
+
+ private JsonEncoding encoding = JsonEncoding.UTF8;
+
+ private boolean prefixJson = false;
+
+ private Boolean prettyPrint;
+
+ private Set Setting a custom-configured {@code ObjectMapper} is one way to take further control
+ * of the JSON serialization process. For example, an extended {@code SerializerFactory}
+ * can be configured that provides custom serializers for specific types. The other option
+ * for refining the serialization process is to use Jackson's provided annotations on the
+ * types to be serialized, in which case a custom-configured ObjectMapper is unnecessary.
+ */
+ public void setObjectMapper(ObjectMapper objectMapper) {
+ Assert.notNull(objectMapper, "'objectMapper' must not be null");
+ this.objectMapper = objectMapper;
+ configurePrettyPrint();
+ }
+
+ private void configurePrettyPrint() {
+ if (this.prettyPrint != null) {
+ this.objectMapper.configure(SerializationFeature.INDENT_OUTPUT, this.prettyPrint);
+ }
+ }
+
+ /**
+ * Set the {@code JsonEncoding} for this converter.
+ * By default, {@linkplain JsonEncoding#UTF8 UTF-8} is used.
+ */
+ public void setEncoding(JsonEncoding encoding) {
+ Assert.notNull(encoding, "'encoding' must not be null");
+ this.encoding = encoding;
+ }
+
+ /**
+ * Indicates whether the JSON output by this view should be prefixed with "{} && ".
+ * Default is false.
+ * Prefixing the JSON string in this manner is used to help prevent JSON Hijacking.
+ * The prefix renders the string syntactically invalid as a script so that it cannot be hijacked.
+ * This prefix does not affect the evaluation of JSON, but if JSON validation is performed
+ * on the string, the prefix would need to be ignored.
+ */
+ public void setPrefixJson(boolean prefixJson) {
+ this.prefixJson = prefixJson;
+ }
+
+ /**
+ * Whether to use the {@link DefaultPrettyPrinter} when writing JSON.
+ * This is a shortcut for setting up an {@code ObjectMapper} as follows:
+ * The default value is {@code false}.
+ */
+ public void setPrettyPrint(boolean prettyPrint) {
+ this.prettyPrint = prettyPrint;
+ configurePrettyPrint();
+ }
+
+ /**
+ * Set the attribute in the model that should be rendered by this view.
+ * When set, all other model attributes will be ignored.
+ */
+ public void setModelKey(String modelKey) {
+ this.modelKeys = Collections.singleton(modelKey);
+ }
+
+ /**
+ * Set the attributes in the model that should be rendered by this view.
+ * When set, all other model attributes will be ignored.
+ */
+ public void setModelKeys(Set The effect of setting this flag is similar to using {@code MappingJacksonHttpMessageConverter}
+ * with an {@code @ResponseBody} request-handling method.
+ * Default is {@code false}.
+ */
+ public void setExtractValueFromSingleKeyModel(boolean extractValueFromSingleKeyModel) {
+ this.extractValueFromSingleKeyModel = extractValueFromSingleKeyModel;
+ }
+
+ /**
+ * Disables caching of the generated JSON.
+ * Default is {@code true}, which will prevent the client from caching the generated JSON.
+ */
+ public void setDisableCaching(boolean disableCaching) {
+ this.disableCaching = disableCaching;
+ }
+
+
+ @Override
+ protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) {
+ response.setContentType(getContentType());
+ response.setCharacterEncoding(this.encoding.getJavaName());
+ if (this.disableCaching) {
+ response.addHeader("Pragma", "no-cache");
+ response.addHeader("Cache-Control", "no-cache, no-store, max-age=0");
+ response.addDateHeader("Expires", 1L);
+ }
+ }
+
+ @Override
+ protected void renderMergedOutputModel(Map The default implementation removes {@link BindingResult} instances and entries
+ * not included in the {@link #setRenderedAttributes renderedAttributes} property.
+ * @param model the model, as passed on to {@link #renderMergedOutputModel}
+ * @return the object to be rendered
+ */
+ protected Object filterModel(Map The default value is {@code false}.
+ */
+ public void setPrettyPrint(boolean prettyPrint) {
+ this.prettyPrint = prettyPrint;
+ configurePrettyPrint();
+ }
+
/**
* Set the attribute in the model that should be rendered by this view.
* When set, all other model attributes will be ignored.
@@ -120,7 +147,7 @@ public void setPrefixJson(boolean prefixJson) {
public void setModelKey(String modelKey) {
this.modelKeys = Collections.singleton(modelKey);
}
-
+
/**
* Set the attributes in the model that should be rendered by this view.
* When set, all other model attributes will be ignored.
@@ -193,6 +220,13 @@ protected void renderMergedOutputModel(Map
@@ -76,14 +76,14 @@
* post processors that facilitate handling {@code @Configuration} classes.
*
*
Via component scanning
- *
* @Configuration
* @ComponentScan("com.acme.app.services")
@@ -91,8 +91,8 @@
* // various @Bean definitions ...
* }
*
- * See @{@link ComponentScan} Javadoc for details.
- *
+ * See {@link ComponentScan @ComponentScan} Javadoc for details.
+ *
*
* Working with externalized values
* Using the {@code Environment} API
@@ -115,7 +115,8 @@
*
* Properties resolved through the {@code Environment} reside in one or more "property
* source" objects, and {@code @Configuration} classes may contribute property sources to
- * the {@code Environment} object using the @{@link PropertySources} annotation:
+ * the {@code Environment} object using
+ * the {@link org.springframework.core.env.PropertySources @PropertySources} annotation:
*
* @Configuration
* @PropertySource("classpath:/com/acme/app.properties")
@@ -129,11 +130,11 @@
* }
*
* See {@link org.springframework.core.env.Environment Environment}
- * and @{@link PropertySource} Javadoc for further details.
- *
+ * and {@link PropertySource @PropertySource} Javadoc for further details.
+ *
* Using the {@code @Value} annotation
* Externalized values may be 'wired into' {@code @Configuration} classes using
- * the @{@link Value} annotation:
+ * the {@link Value @Value} annotation:
*
* @Configuration
* @PropertySource("classpath:/com/acme/app.properties")
@@ -151,13 +152,13 @@
* PropertySourcesPlaceholderConfigurer}, usually enabled via XML with
* {@code
*
* Composing {@code @Configuration} classes
* With the {@code @Import} annotation
- * With the {@code @Profile} annotation
- * {@code @Configuration} classes may be marked with the @{@link Profile} annotation to
+ * {@code @Configuration} classes may be marked with the {@link Profile @Profile} annotation to
* indicate they should be processed only if a given profile or profiles are
* active:
*
@@ -211,14 +212,14 @@
* }
* }
*
- * See @{@link Profile} and {@link org.springframework.core.env.Environment Environment}
+ * See {@link Profile @Profile} and {@link org.springframework.core.env.Environment Environment}
* Javadoc for further details.
*
* With Spring XML using the {@code @ImportResource} annotation
* As mentioned above, {@code @Configuration} classes may be declared as regular Spring
* {@code
* @Configuration
@@ -267,7 +268,7 @@
*
*
* See TestContext framework reference documentation for details.
- *
+ *
* Configuring lazy initialization
* Enabling built-in Spring features using {@code @Enable} annotations
* Spring features such as asynchronous method execution, scheduled task execution,
* annotation driven transaction management, and even Spring MVC can be enabled and
@@ -309,7 +310,7 @@
*
* @Configuration
* @EnableLoadTimeWeaving
- * public class AppConfig implements LoadTimeWeaverConfigurer {
+ * public class AppConfig implements LoadTimeWeavingConfigurer {
* @Override
* public LoadTimeWeaver getLoadTimeWeaver() {
* MyLoadTimeWeaver ltw = new MyLoadTimeWeaver();
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/FilterType.java b/spring-context/src/main/java/org/springframework/context/annotation/FilterType.java
index 9ed1b8994348..26bd302d8e8f 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/FilterType.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/FilterType.java
@@ -28,8 +28,8 @@
* @author Chris Beams
* @since 2.5
* @see ComponentScan
- * @see ComponentScan.IncludeFilter
- * @see ComponentScan.ExcludeFilter
+ * @see ComponentScan#includeFilters()
+ * @see ComponentScan#excludeFilters()
* @see org.springframework.core.type.filter.TypeFilter
*/
public enum FilterType {
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/Import.java b/spring-context/src/main/java/org/springframework/context/annotation/Import.java
index 4a2fe947ca87..6aacf68a4403 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/Import.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/Import.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,23 +23,23 @@
import java.lang.annotation.Target;
/**
- * Indicates one or more @{@link Configuration} classes to import.
+ * Indicates one or more {@link Configuration @Configuration} classes to import.
*
*
- *
* If the context has already been refreshed or does not implement
* {@code ConfigurableWebApplicationContext}, none of the above will occur under the
@@ -89,7 +90,7 @@ public ContextLoaderListener() {
* GlassFishLoadTimeWeaver
class.
* @param classLoader the specific {@link ClassLoader} to use; must not be null
* @throws IllegalArgumentException if the supplied classLoader
is null
;
- * or if the supplied classLoader
is not an {@link InstrumentableClassLoader}
+ * or if the supplied classLoader
is not an
+ * {@link org.glassfish.api.deployment.InstrumentableClassLoader InstrumentableClassLoader}
*/
public GlassFishLoadTimeWeaver(ClassLoader classLoader) {
Assert.notNull(classLoader, "ClassLoader must not be null");
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 716ec0c6882a..0396c0dfb811 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
@@ -526,7 +526,7 @@ private Object invokeOperation(Method method, Object[] args) throws JMException,
* Convert the given result object (from attribute access or operation invocation)
* to the specified target class for returning from the proxy method.
* @param result the result object as returned by the MBeanServer
- * @param targetClass the result type of the proxy method that's been invoked
+ * @param parameter the method parameter of the proxy method that's been invoked
* @return the converted result object, or the passed-in object if no conversion
* is necessary
*/
diff --git a/spring-context/src/main/java/org/springframework/jmx/export/assembler/AbstractMBeanInfoAssembler.java b/spring-context/src/main/java/org/springframework/jmx/export/assembler/AbstractMBeanInfoAssembler.java
index f81f0c2815fd..c0a95abb4ff7 100644
--- a/spring-context/src/main/java/org/springframework/jmx/export/assembler/AbstractMBeanInfoAssembler.java
+++ b/spring-context/src/main/java/org/springframework/jmx/export/assembler/AbstractMBeanInfoAssembler.java
@@ -88,7 +88,7 @@ protected void checkManagedBean(Object managedBean) throws IllegalArgumentExcept
* and the plain bean class else.
* @param managedBean the bean instance (might be an AOP proxy)
* @return the bean class to expose
- * @see org.springframework.aop.framework.AopProxyUtils#getTargetClass
+ * @see org.springframework.aop.support.AopUtils#getTargetClass(Object)
*/
protected Class getTargetClass(Object managedBean) {
return AopUtils.getTargetClass(managedBean);
@@ -100,7 +100,7 @@ protected Class getTargetClass(Object managedBean) {
* (for example, checked for annotations).
* @param managedBean the bean instance (might be an AOP proxy)
* @return the bean class to expose
- * @see JmxUtils#getClassToExpose(Object)
+ * @see JmxUtils#getClassToExpose(Object)
*/
protected Class> getClassToExpose(Object managedBean) {
return JmxUtils.getClassToExpose(managedBean);
diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationAdvisor.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationAdvisor.java
index b1578438aa5c..245ff645b9ca 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationAdvisor.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationAdvisor.java
@@ -45,7 +45,7 @@
*
* @author Juergen Hoeller
* @since 3.0
- * @see PersistenceExceptionTranslationAdvisor
+ * @see org.springframework.dao.annotation.PersistenceExceptionTranslationAdvisor
* @see org.springframework.stereotype.Repository
* @see org.springframework.dao.DataAccessException
* @see org.springframework.dao.support.PersistenceExceptionTranslator
@@ -126,8 +126,8 @@ protected Advice buildAdvice(Executor executor) {
}
/**
- * Calculate a pointcut for the given target class, if any.
- * @param targetClass the class to introspect
+ * Calculate a pointcut for the given async annotation types, if any.
+ * @param asyncAnnotationTypes the async annotation types to introspect
* @return the applicable Pointcut object, or null
if none
*/
protected Pointcut buildPointcut(Set
+ *
- * It can be used as a unary operator for numbers (double/long/int). The standard promotions are performed
+ * It can be used as a unary operator for numbers (BigDecimal/double/long/int). The standard promotions are performed
* when the operand types vary (double-int=double).
* For other options it defers to the registered overloader.
*
@@ -50,7 +54,10 @@ public TypedValue getValueInternal(ExpressionState state) throws EvaluationExcep
Object operand = leftOp.getValueInternal(state).getValue();
if (operand instanceof Number) {
Number n = (Number) operand;
- if (operand instanceof Double) {
+ if ( operand instanceof BigDecimal ) {
+ BigDecimal bdn = NumberUtils.convertNumberToTargetClass(n, BigDecimal.class);
+ return new TypedValue(BigDecimal.ZERO.subtract(bdn));
+ } else if (operand instanceof Double) {
return new TypedValue(0 - n.doubleValue());
} else if (operand instanceof Long) {
return new TypedValue(0 - n.longValue());
@@ -65,7 +72,11 @@ public TypedValue getValueInternal(ExpressionState state) throws EvaluationExcep
if (left instanceof Number && right instanceof Number) {
Number op1 = (Number) left;
Number op2 = (Number) right;
- if (op1 instanceof Double || op2 instanceof Double) {
+ if ( op1 instanceof BigDecimal || op2 instanceof BigDecimal ) {
+ BigDecimal bd1 = NumberUtils.convertNumberToTargetClass(op1, BigDecimal.class);
+ BigDecimal bd2 = NumberUtils.convertNumberToTargetClass(op2, BigDecimal.class);
+ return new TypedValue(bd1.subtract(bd2));
+ } else if (op1 instanceof Double || op2 instanceof Double) {
return new TypedValue(op1.doubleValue() - op2.doubleValue());
} else if (op1 instanceof Long || op2 instanceof Long) {
return new TypedValue(op1.longValue() - op2.longValue());
diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpModulus.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpModulus.java
index 04ef2ffaef32..cb8340a6d1c5 100644
--- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpModulus.java
+++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpModulus.java
@@ -16,10 +16,13 @@
package org.springframework.expression.spel.ast;
+import java.math.BigDecimal;
+
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Operation;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
+import org.springframework.util.NumberUtils;
/**
* Implements the modulus operator.
@@ -40,7 +43,11 @@ public TypedValue getValueInternal(ExpressionState state) throws EvaluationExcep
if (operandOne instanceof Number && operandTwo instanceof Number) {
Number op1 = (Number) operandOne;
Number op2 = (Number) operandTwo;
- if (op1 instanceof Double || op2 instanceof Double) {
+ if ( op1 instanceof BigDecimal || op2 instanceof BigDecimal ) {
+ BigDecimal bd1 = NumberUtils.convertNumberToTargetClass(op1, BigDecimal.class);
+ BigDecimal bd2 = NumberUtils.convertNumberToTargetClass(op2, BigDecimal.class);
+ return new TypedValue(bd1.remainder(bd2));
+ } else if (op1 instanceof Double || op2 instanceof Double) {
return new TypedValue(op1.doubleValue() % op2.doubleValue());
} else if (op1 instanceof Long || op2 instanceof Long) {
return new TypedValue(op1.longValue() % op2.longValue());
diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpMultiply.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpMultiply.java
index e4565e211adf..08b9773f623e 100644
--- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpMultiply.java
+++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpMultiply.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2009 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,39 +16,48 @@
package org.springframework.expression.spel.ast;
+import java.math.BigDecimal;
+
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Operation;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
+import org.springframework.util.NumberUtils;
/**
- * Implements the multiply operator. Conversions and promotions:
- * http://java.sun.com/docs/books/jls/third_edition/html/conversions.html Section 5.6.2:
+ * Implements the {@code multiply} operator.
+ *
+ *
+ *
+ * If either operand is of type BigDecimal, the other is converted to BigDecimal.
* If either operand is of type double, the other is converted to double.
* Otherwise, if either operand is of type float, the other is converted to float.
* Otherwise, if either operand is of type long, the other is converted to long.
* Otherwise, both operands are converted to type int.
*
- *
- *
*/
@@ -59,14 +68,21 @@ public TypedValue getValueInternal(ExpressionState state) throws EvaluationExcep
if (operandOne instanceof Number && operandTwo instanceof Number) {
Number leftNumber = (Number) operandOne;
Number rightNumber = (Number) operandTwo;
- if (leftNumber instanceof Double || rightNumber instanceof Double) {
+ if ( leftNumber instanceof BigDecimal || rightNumber instanceof BigDecimal ) {
+ BigDecimal bdLeft = NumberUtils.convertNumberToTargetClass(leftNumber, BigDecimal.class);
+ BigDecimal bdRight = NumberUtils.convertNumberToTargetClass(rightNumber, BigDecimal.class);
+ return new TypedValue(bdLeft.multiply(bdRight));
+ } else if (leftNumber instanceof Double || rightNumber instanceof Double) {
return new TypedValue(leftNumber.doubleValue() * rightNumber.doubleValue());
- } else if (leftNumber instanceof Long || rightNumber instanceof Long) {
+ }
+ else if (leftNumber instanceof Long || rightNumber instanceof Long) {
return new TypedValue(leftNumber.longValue() * rightNumber.longValue());
- } else {
+ }
+ else {
return new TypedValue(leftNumber.intValue() * rightNumber.intValue());
}
- } else if (operandOne instanceof String && operandTwo instanceof Integer) {
+ }
+ else if (operandOne instanceof String && operandTwo instanceof Integer) {
int repeats = (Integer) operandTwo;
StringBuilder result = new StringBuilder();
for (int i = 0; i < repeats; i++) {
diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpNE.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpNE.java
index aebcb852777b..08a6edf4389c 100644
--- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpNE.java
+++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpNE.java
@@ -16,9 +16,14 @@
package org.springframework.expression.spel.ast;
+import java.math.BigDecimal;
+
import org.springframework.expression.EvaluationException;
import org.springframework.expression.spel.ExpressionState;
+import org.springframework.expression.spel.SpelEvaluationException;
+import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.support.BooleanTypedValue;
+import org.springframework.util.NumberUtils;
/**
* Implements the not-equal operator.
@@ -39,7 +44,14 @@ public BooleanTypedValue getValueInternal(ExpressionState state) throws Evaluati
if (left instanceof Number && right instanceof Number) {
Number op1 = (Number) left;
Number op2 = (Number) right;
- if (op1 instanceof Double || op2 instanceof Double) {
+ if ( op1 instanceof BigDecimal || op2 instanceof BigDecimal ) {
+ BigDecimal bd1 = NumberUtils.convertNumberToTargetClass(op1, BigDecimal.class);
+ BigDecimal bd2 = NumberUtils.convertNumberToTargetClass(op2, BigDecimal.class);
+ if ( bd1 == null || bd2 == null ) {
+ throw new SpelEvaluationException(SpelMessage.NOT_COMPARABLE, left.getClass(), right.getClass());
+ }
+ return BooleanTypedValue.forValue(bd1.compareTo(bd2) != 0);
+ } else if (op1 instanceof Double || op2 instanceof Double) {
return BooleanTypedValue.forValue(op1.doubleValue() != op2.doubleValue());
} else if (op1 instanceof Long || op2 instanceof Long) {
return BooleanTypedValue.forValue(op1.longValue() != op2.longValue());
diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpPlus.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpPlus.java
index eb1f55c82ae9..2fb56028fbf2 100644
--- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpPlus.java
+++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpPlus.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2009 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,29 +16,38 @@
package org.springframework.expression.spel.ast;
+import java.math.BigDecimal;
+
+import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Operation;
+import org.springframework.expression.TypeConverter;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
+import org.springframework.util.Assert;
+import org.springframework.util.NumberUtils;
/**
* The plus operator will:
*
+ *
- * It can be used as a unary operator for numbers (double/long/int). The standard promotions are performed
+ * It can be used as a unary operator for numbers (BigDecimal/double/long/int). The standard promotions are performed
* when the operand types vary (double+int=double). For other options it defers to the registered overloader.
- *
+ *
* @author Andy Clement
+ * @author Ivo Smid
* @since 3.0
*/
public class OpPlus extends Operator {
public OpPlus(int pos, SpelNodeImpl... operands) {
super("+", pos, operands);
+ Assert.notEmpty(operands);
}
@Override
@@ -48,23 +57,29 @@ public TypedValue getValueInternal(ExpressionState state) throws EvaluationExcep
if (rightOp == null) { // If only one operand, then this is unary plus
Object operandOne = leftOp.getValueInternal(state).getValue();
if (operandOne instanceof Number) {
- if (operandOne instanceof Double) {
- return new TypedValue(((Double) operandOne).doubleValue());
- } else if (operandOne instanceof Long) {
- return new TypedValue(((Long) operandOne).longValue());
+ if (operandOne instanceof Double || operandOne instanceof Long || operandOne instanceof BigDecimal) {
+ return new TypedValue(operandOne);
} else {
- return new TypedValue(((Integer) operandOne).intValue());
+ return new TypedValue(((Number) operandOne).intValue());
}
}
return state.operate(Operation.ADD, operandOne, null);
}
else {
- Object operandOne = leftOp.getValueInternal(state).getValue();
- Object operandTwo = rightOp.getValueInternal(state).getValue();
+ final TypedValue operandOneValue = leftOp.getValueInternal(state);
+ final Object operandOne = operandOneValue.getValue();
+
+ final TypedValue operandTwoValue = rightOp.getValueInternal(state);
+ final Object operandTwo = operandTwoValue.getValue();
+
if (operandOne instanceof Number && operandTwo instanceof Number) {
Number op1 = (Number) operandOne;
Number op2 = (Number) operandTwo;
- if (op1 instanceof Double || op2 instanceof Double) {
+ if ( op1 instanceof BigDecimal || op2 instanceof BigDecimal ) {
+ BigDecimal bd1 = NumberUtils.convertNumberToTargetClass(op1, BigDecimal.class);
+ BigDecimal bd2 = NumberUtils.convertNumberToTargetClass(op2, BigDecimal.class);
+ return new TypedValue(bd1.add(bd2));
+ } else if (op1 instanceof Double || op2 instanceof Double) {
return new TypedValue(op1.doubleValue() + op2.doubleValue());
} else if (op1 instanceof Long || op2 instanceof Long) {
return new TypedValue(op1.longValue() + op2.longValue());
@@ -74,13 +89,14 @@ public TypedValue getValueInternal(ExpressionState state) throws EvaluationExcep
} else if (operandOne instanceof String && operandTwo instanceof String) {
return new TypedValue(new StringBuilder((String) operandOne).append((String) operandTwo).toString());
} else if (operandOne instanceof String) {
- StringBuilder result = new StringBuilder((String)operandOne);
- result.append((operandTwo==null?"null":operandTwo.toString()));
- return new TypedValue(result.toString());
+ StringBuilder result = new StringBuilder((String) operandOne);
+ result.append((operandTwo == null ? "null" : convertTypedValueToString(operandTwoValue, state)));
+ return new TypedValue(result.toString());
} else if (operandTwo instanceof String) {
- StringBuilder result = new StringBuilder((operandOne==null?"null":operandOne.toString()));
- result.append((String)operandTwo);
- return new TypedValue(result.toString());
+ StringBuilder result = new StringBuilder((operandOne == null ? "null" : convertTypedValueToString(
+ operandOneValue, state)));
+ result.append((String) operandTwo);
+ return new TypedValue(result.toString());
}
return state.operate(Operation.ADD, operandOne, operandTwo);
}
@@ -94,10 +110,32 @@ public String toStringAST() {
return super.toStringAST();
}
+ @Override
public SpelNodeImpl getRightOperand() {
- if (children.length<2) {return null;}
+ if (children.length < 2) {
+ return null;
+ }
return children[1];
}
+ /**
+ * Convert operand value to string using registered converter or using
+ * {@code toString} method.
+ *
+ * @param value typed value to be converted
+ * @param state expression state
+ * @return {@code TypedValue} instance converted to {@code String}
+ */
+ private static String convertTypedValueToString(TypedValue value, ExpressionState state) {
+ final TypeConverter typeConverter = state.getEvaluationContext().getTypeConverter();
+ final TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(String.class);
+
+ if (typeConverter.canConvert(value.getTypeDescriptor(), typeDescriptor)) {
+ final Object obj = typeConverter.convertValue(value.getValue(), value.getTypeDescriptor(), typeDescriptor);
+ return String.valueOf(obj);
+ } else {
+ return String.valueOf(value.getValue());
+ }
+ }
}
diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorPower.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorPower.java
index f15e7d043104..e327272b6f98 100644
--- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorPower.java
+++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorPower.java
@@ -16,10 +16,13 @@
package org.springframework.expression.spel.ast;
+import java.math.BigDecimal;
+
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Operation;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
+import org.springframework.util.NumberUtils;
/**
* The power operator.
@@ -43,7 +46,12 @@ public TypedValue getValueInternal(ExpressionState state) throws EvaluationExcep
if (operandOne instanceof Number && operandTwo instanceof Number) {
Number op1 = (Number) operandOne;
Number op2 = (Number) operandTwo;
- if (op1 instanceof Double || op2 instanceof Double) {
+ if ( op1 instanceof BigDecimal ) {
+ // TODO BigDecimal.pow has a limit in the range.
+ // is it correct to use the power function this way?
+ BigDecimal bd1 = NumberUtils.convertNumberToTargetClass(op1, BigDecimal.class);
+ return new TypedValue(bd1.pow(op2.intValue()));
+ } else if (op1 instanceof Double || op2 instanceof Double) {
return new TypedValue(Math.pow(op1.doubleValue(),op2.doubleValue()));
} else if (op1 instanceof Long || op2 instanceof Long) {
double d= Math.pow(op1.longValue(), op2.longValue());
diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/OperatorTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/OperatorTests.java
index a174023fd4de..2f2c26cb0da1 100644
--- a/spring-expression/src/test/java/org/springframework/expression/spel/OperatorTests.java
+++ b/spring-expression/src/test/java/org/springframework/expression/spel/OperatorTests.java
@@ -16,6 +16,8 @@
package org.springframework.expression.spel;
+import java.math.BigDecimal;
+
import junit.framework.Assert;
import org.junit.Test;
@@ -41,6 +43,9 @@ public void testRealLiteral() {
@Test
public void testLessThan() {
+
+ evaluate("5 < 5", false, Boolean.class);
+
evaluate("3 < 5", true, Boolean.class);
evaluate("5 < 3", false, Boolean.class);
evaluate("3L < 5L", true, Boolean.class);
@@ -49,6 +54,15 @@ public void testLessThan() {
evaluate("5.0d < 3.0d", false, Boolean.class);
evaluate("'abc' < 'def'",true,Boolean.class);
evaluate("'def' < 'abc'",false,Boolean.class);
+ evaluate("new java.math.BigDecimal('3') < new java.math.BigDecimal('5')", true, Boolean.class);
+ evaluate("new java.math.BigDecimal('5') < new java.math.BigDecimal('3')", false, Boolean.class);
+ evaluate("3 < new java.math.BigDecimal('5')", true, Boolean.class);
+ evaluate("new java.math.BigDecimal('3') < 5", true, Boolean.class);
+ evaluate("3L < new java.math.BigDecimal('5')", true, Boolean.class);
+ evaluate("3.0d < new java.math.BigDecimal('5')", true, Boolean.class);
+ evaluate("3L < new java.math.BigDecimal('3.1')", true, Boolean.class);
+ evaluate("3.0d < new java.math.BigDecimal('3.1')", true, Boolean.class);
+ evaluate("3.0d < new java.math.BigDecimal('3.0')", false, Boolean.class);
evaluate("3 lt 5", true, Boolean.class);
evaluate("5 lt 3", false, Boolean.class);
@@ -58,6 +72,15 @@ public void testLessThan() {
evaluate("5.0d Lt 3.0d", false, Boolean.class);
evaluate("'abc' LT 'def'",true,Boolean.class);
evaluate("'def' lt 'abc'",false,Boolean.class);
+ evaluate("new java.math.BigDecimal('3') lt new java.math.BigDecimal('5')", true, Boolean.class);
+ evaluate("new java.math.BigDecimal('5') lt new java.math.BigDecimal('3')", false, Boolean.class);
+ evaluate("3 lt new java.math.BigDecimal('5')", true, Boolean.class);
+ evaluate("new java.math.BigDecimal('3') lt 5", true, Boolean.class);
+ evaluate("3L lt new java.math.BigDecimal('5')", true, Boolean.class);
+ evaluate("3.0d lt new java.math.BigDecimal('5')", true, Boolean.class);
+ evaluate("3L lt new java.math.BigDecimal('3.1')", true, Boolean.class);
+ evaluate("3.0d lt new java.math.BigDecimal('3.1')", true, Boolean.class);
+ evaluate("3.0d lt new java.math.BigDecimal('3.0')", false, Boolean.class);
}
@Test
@@ -74,7 +97,19 @@ public void testLessThanOrEqual() {
evaluate("'abc' <= 'def'",true,Boolean.class);
evaluate("'def' <= 'abc'",false,Boolean.class);
evaluate("'abc' <= 'abc'",true,Boolean.class);
+ evaluate("new java.math.BigDecimal('5') <= new java.math.BigDecimal('5')", true, Boolean.class);
+ evaluate("new java.math.BigDecimal('3') <= new java.math.BigDecimal('5')", true, Boolean.class);
+ evaluate("new java.math.BigDecimal('5') <= new java.math.BigDecimal('3')", false, Boolean.class);
+ evaluate("3 <= new java.math.BigDecimal('5')", true, Boolean.class);
+ evaluate("new java.math.BigDecimal('3') <= 5", true, Boolean.class);
+ evaluate("3L <= new java.math.BigDecimal('5')", true, Boolean.class);
+ evaluate("3.0d <= new java.math.BigDecimal('5')", true, Boolean.class);
+ evaluate("3L <= new java.math.BigDecimal('3.1')", true, Boolean.class);
+ evaluate("3.0d <= new java.math.BigDecimal('3.1')", true, Boolean.class);
+ evaluate("3.0d <= new java.math.BigDecimal('3.0')", true, Boolean.class);
+ evaluate("3 le 5", true, Boolean.class);
+ evaluate("5 le 3", false, Boolean.class);
evaluate("3 le 5", true, Boolean.class);
evaluate("5 le 3", false, Boolean.class);
evaluate("6 Le 6", true, Boolean.class);
@@ -87,42 +122,102 @@ public void testLessThanOrEqual() {
evaluate("'abc' Le 'def'",true,Boolean.class);
evaluate("'def' LE 'abc'",false,Boolean.class);
evaluate("'abc' le 'abc'",true,Boolean.class);
+ evaluate("new java.math.BigDecimal('5') le new java.math.BigDecimal('5')", true, Boolean.class);
+ evaluate("new java.math.BigDecimal('3') le new java.math.BigDecimal('5')", true, Boolean.class);
+ evaluate("new java.math.BigDecimal('5') le new java.math.BigDecimal('3')", false, Boolean.class);
+ evaluate("3 le new java.math.BigDecimal('5')", true, Boolean.class);
+ evaluate("new java.math.BigDecimal('3') le 5", true, Boolean.class);
+ evaluate("3L le new java.math.BigDecimal('5')", true, Boolean.class);
+ evaluate("3.0d le new java.math.BigDecimal('5')", true, Boolean.class);
+ evaluate("3L le new java.math.BigDecimal('3.1')", true, Boolean.class);
+ evaluate("3.0d le new java.math.BigDecimal('3.1')", true, Boolean.class);
+ evaluate("3.0d le new java.math.BigDecimal('3.0')", true, Boolean.class);
}
@Test
public void testEqual() {
+ evaluate("3 == 5", false, Boolean.class);
+ evaluate("5 == 3", false, Boolean.class);
evaluate("3 == 5", false, Boolean.class);
evaluate("5 == 3", false, Boolean.class);
evaluate("6 == 6", true, Boolean.class);
evaluate("3.0f == 5.0f", false, Boolean.class);
evaluate("3.0f == 3.0f", true, Boolean.class);
evaluate("'abc' == null", false, Boolean.class);
+ evaluate("new java.math.BigDecimal('5') == new java.math.BigDecimal('5')", true, Boolean.class);
+ evaluate("new java.math.BigDecimal('3') == new java.math.BigDecimal('5')", false, Boolean.class);
+ evaluate("new java.math.BigDecimal('5') == new java.math.BigDecimal('3')", false, Boolean.class);
+ evaluate("3 == new java.math.BigDecimal('5')", false, Boolean.class);
+ evaluate("new java.math.BigDecimal('3') == 5", false, Boolean.class);
+ evaluate("3L == new java.math.BigDecimal('5')", false, Boolean.class);
+ evaluate("3.0d == new java.math.BigDecimal('5')", false, Boolean.class);
+ evaluate("3L == new java.math.BigDecimal('3.1')", false, Boolean.class);
+ evaluate("3.0d == new java.math.BigDecimal('3.1')", false, Boolean.class);
+ evaluate("3.0d == new java.math.BigDecimal('3.0')", true, Boolean.class);
+ evaluate("3 eq 5", false, Boolean.class);
+ evaluate("5 eQ 3", false, Boolean.class);
evaluate("3 eq 5", false, Boolean.class);
evaluate("5 eQ 3", false, Boolean.class);
evaluate("6 Eq 6", true, Boolean.class);
evaluate("3.0f eq 5.0f", false, Boolean.class);
evaluate("3.0f EQ 3.0f", true, Boolean.class);
evaluate("'abc' EQ null", false, Boolean.class);
+ evaluate("new java.math.BigDecimal('5') eq new java.math.BigDecimal('5')", true, Boolean.class);
+ evaluate("new java.math.BigDecimal('3') eq new java.math.BigDecimal('5')", false, Boolean.class);
+ evaluate("new java.math.BigDecimal('5') eq new java.math.BigDecimal('3')", false, Boolean.class);
+ evaluate("3 eq new java.math.BigDecimal('5')", false, Boolean.class);
+ evaluate("new java.math.BigDecimal('3') eq 5", false, Boolean.class);
+ evaluate("3L eq new java.math.BigDecimal('5')", false, Boolean.class);
+ evaluate("3.0d eq new java.math.BigDecimal('5')", false, Boolean.class);
+ evaluate("3L eq new java.math.BigDecimal('3.1')", false, Boolean.class);
+ evaluate("3.0d eq new java.math.BigDecimal('3.1')", false, Boolean.class);
+ evaluate("3.0d eq new java.math.BigDecimal('3.0')", true, Boolean.class);
}
@Test
public void testNotEqual() {
+ evaluate("3 != 5", true, Boolean.class);
+ evaluate("5 != 3", true, Boolean.class);
evaluate("3 != 5", true, Boolean.class);
evaluate("5 != 3", true, Boolean.class);
evaluate("6 != 6", false, Boolean.class);
evaluate("3.0f != 5.0f", true, Boolean.class);
evaluate("3.0f != 3.0f", false, Boolean.class);
+ evaluate("new java.math.BigDecimal('5') != new java.math.BigDecimal('5')", false, Boolean.class);
+ evaluate("new java.math.BigDecimal('3') != new java.math.BigDecimal('5')", true, Boolean.class);
+ evaluate("new java.math.BigDecimal('5') != new java.math.BigDecimal('3')", true, Boolean.class);
+ evaluate("3 != new java.math.BigDecimal('5')", true, Boolean.class);
+ evaluate("new java.math.BigDecimal('3') != 5", true, Boolean.class);
+ evaluate("3L != new java.math.BigDecimal('5')", true, Boolean.class);
+ evaluate("3.0d != new java.math.BigDecimal('5')", true, Boolean.class);
+ evaluate("3L != new java.math.BigDecimal('3.1')", true, Boolean.class);
+ evaluate("3.0d != new java.math.BigDecimal('3.1')", true, Boolean.class);
+ evaluate("3.0d != new java.math.BigDecimal('3.0')", false, Boolean.class);
+ evaluate("3 ne 5", true, Boolean.class);
+ evaluate("5 nE 3", true, Boolean.class);
evaluate("3 ne 5", true, Boolean.class);
evaluate("5 nE 3", true, Boolean.class);
evaluate("6 Ne 6", false, Boolean.class);
evaluate("3.0f NE 5.0f", true, Boolean.class);
evaluate("3.0f ne 3.0f", false, Boolean.class);
+ evaluate("new java.math.BigDecimal('5') ne new java.math.BigDecimal('5')", false, Boolean.class);
+ evaluate("new java.math.BigDecimal('3') ne new java.math.BigDecimal('5')", true, Boolean.class);
+ evaluate("new java.math.BigDecimal('5') ne new java.math.BigDecimal('3')", true, Boolean.class);
+ evaluate("3 ne new java.math.BigDecimal('5')", true, Boolean.class);
+ evaluate("new java.math.BigDecimal('3') ne 5", true, Boolean.class);
+ evaluate("3L ne new java.math.BigDecimal('5')", true, Boolean.class);
+ evaluate("3.0d ne new java.math.BigDecimal('5')", true, Boolean.class);
+ evaluate("3L ne new java.math.BigDecimal('3.1')", true, Boolean.class);
+ evaluate("3.0d ne new java.math.BigDecimal('3.1')", true, Boolean.class);
+ evaluate("3.0d ne new java.math.BigDecimal('3.0')", false, Boolean.class);
}
@Test
public void testGreaterThanOrEqual() {
+ evaluate("3 >= 5", false, Boolean.class);
+ evaluate("5 >= 3", true, Boolean.class);
evaluate("3 >= 5", false, Boolean.class);
evaluate("5 >= 3", true, Boolean.class);
evaluate("6 >= 6", true, Boolean.class);
@@ -135,15 +230,39 @@ public void testGreaterThanOrEqual() {
evaluate("'abc' >= 'def'",false,Boolean.class);
evaluate("'def' >= 'abc'",true,Boolean.class);
evaluate("'abc' >= 'abc'",true,Boolean.class);
+ evaluate("new java.math.BigDecimal('5') >= new java.math.BigDecimal('5')", true, Boolean.class);
+ evaluate("new java.math.BigDecimal('3') >= new java.math.BigDecimal('5')", false, Boolean.class);
+ evaluate("new java.math.BigDecimal('5') >= new java.math.BigDecimal('3')", true, Boolean.class);
+ evaluate("3 >= new java.math.BigDecimal('5')", false, Boolean.class);
+ evaluate("new java.math.BigDecimal('3') >= 5", false, Boolean.class);
+ evaluate("3L >= new java.math.BigDecimal('5')", false, Boolean.class);
+ evaluate("3.0d >= new java.math.BigDecimal('5')", false, Boolean.class);
+ evaluate("3L >= new java.math.BigDecimal('3.1')", false, Boolean.class);
+ evaluate("3.0d >= new java.math.BigDecimal('3.1')", false, Boolean.class);
+ evaluate("3.0d >= new java.math.BigDecimal('3.0')", true, Boolean.class);
+ evaluate("3 GE 5", false, Boolean.class);
+ evaluate("5 gE 3", true, Boolean.class);
evaluate("3 GE 5", false, Boolean.class);
evaluate("5 gE 3", true, Boolean.class);
evaluate("6 Ge 6", true, Boolean.class);
evaluate("3L ge 5L", false, Boolean.class);
+ evaluate("new java.math.BigDecimal('5') ge new java.math.BigDecimal('5')", true, Boolean.class);
+ evaluate("new java.math.BigDecimal('3') ge new java.math.BigDecimal('5')", false, Boolean.class);
+ evaluate("new java.math.BigDecimal('5') ge new java.math.BigDecimal('3')", true, Boolean.class);
+ evaluate("3 ge new java.math.BigDecimal('5')", false, Boolean.class);
+ evaluate("new java.math.BigDecimal('3') ge 5", false, Boolean.class);
+ evaluate("3L ge new java.math.BigDecimal('5')", false, Boolean.class);
+ evaluate("3.0d ge new java.math.BigDecimal('5')", false, Boolean.class);
+ evaluate("3L ge new java.math.BigDecimal('3.1')", false, Boolean.class);
+ evaluate("3.0d ge new java.math.BigDecimal('3.1')", false, Boolean.class);
+ evaluate("3.0d ge new java.math.BigDecimal('3.0')", true, Boolean.class);
}
@Test
public void testGreaterThan() {
+ evaluate("3 > 5", false, Boolean.class);
+ evaluate("5 > 3", true, Boolean.class);
evaluate("3 > 5", false, Boolean.class);
evaluate("5 > 3", true, Boolean.class);
evaluate("3L > 5L", false, Boolean.class);
@@ -152,11 +271,31 @@ public void testGreaterThan() {
evaluate("5.0d > 3.0d", true, Boolean.class);
evaluate("'abc' > 'def'",false,Boolean.class);
evaluate("'def' > 'abc'",true,Boolean.class);
+ evaluate("new java.math.BigDecimal('3') > new java.math.BigDecimal('5')", false, Boolean.class);
+ evaluate("new java.math.BigDecimal('5') > new java.math.BigDecimal('3')", true, Boolean.class);
+ evaluate("3 > new java.math.BigDecimal('5')", false, Boolean.class);
+ evaluate("new java.math.BigDecimal('3') > 5", false, Boolean.class);
+ evaluate("3L > new java.math.BigDecimal('5')", false, Boolean.class);
+ evaluate("3.0d > new java.math.BigDecimal('5')", false, Boolean.class);
+ evaluate("3L > new java.math.BigDecimal('3.1')", false, Boolean.class);
+ evaluate("3.0d > new java.math.BigDecimal('3.1')", false, Boolean.class);
+ evaluate("3.0d > new java.math.BigDecimal('3.0')", false, Boolean.class);
+ evaluate("3.0d gt 5.0d", false, Boolean.class);
+ evaluate("5.0d gT 3.0d", true, Boolean.class);
evaluate("3.0d gt 5.0d", false, Boolean.class);
evaluate("5.0d gT 3.0d", true, Boolean.class);
evaluate("'abc' Gt 'def'",false,Boolean.class);
evaluate("'def' GT 'abc'",true,Boolean.class);
+ evaluate("new java.math.BigDecimal('3') gt new java.math.BigDecimal('5')", false, Boolean.class);
+ evaluate("new java.math.BigDecimal('5') gt new java.math.BigDecimal('3')", true, Boolean.class);
+ evaluate("3 gt new java.math.BigDecimal('5')", false, Boolean.class);
+ evaluate("new java.math.BigDecimal('3') gt 5", false, Boolean.class);
+ evaluate("3L gt new java.math.BigDecimal('5')", false, Boolean.class);
+ evaluate("3.0d gt new java.math.BigDecimal('5')", false, Boolean.class);
+ evaluate("3L gt new java.math.BigDecimal('3.1')", false, Boolean.class);
+ evaluate("3.0d gt new java.math.BigDecimal('3.1')", false, Boolean.class);
+ evaluate("3.0d gt new java.math.BigDecimal('3.0')", false, Boolean.class);
}
@Test
@@ -169,6 +308,30 @@ public void testMultiplyDoubleDoubleGivesDouble() {
evaluate("3.0d * 5.0d", 15.0d, Double.class);
}
+ @Test
+ public void testMixedOperandsBigDecimal() {
+ evaluate("3 * new java.math.BigDecimal('5')", new BigDecimal("15"), BigDecimal.class);
+ evaluate("3L * new java.math.BigDecimal('5')", new BigDecimal("15"), BigDecimal.class);
+ evaluate("3.0d * new java.math.BigDecimal('5')", new BigDecimal("15.0"), BigDecimal.class);
+
+ evaluate("3 + new java.math.BigDecimal('5')", new BigDecimal("8"), BigDecimal.class);
+ evaluate("3L + new java.math.BigDecimal('5')", new BigDecimal("8"), BigDecimal.class);
+ evaluate("3.0d + new java.math.BigDecimal('5')", new BigDecimal("8.0"), BigDecimal.class);
+
+ evaluate("3 - new java.math.BigDecimal('5')", new BigDecimal("-2"), BigDecimal.class);
+ evaluate("3L - new java.math.BigDecimal('5')", new BigDecimal("-2"), BigDecimal.class);
+ evaluate("3.0d - new java.math.BigDecimal('5')", new BigDecimal("-2.0"), BigDecimal.class);
+
+ evaluate("3 / new java.math.BigDecimal('5')", new BigDecimal("0.600"), BigDecimal.class);
+ evaluate("3L / new java.math.BigDecimal('5')", new BigDecimal("0.600"), BigDecimal.class);
+ evaluate("3.0d / new java.math.BigDecimal('5')", new BigDecimal("0.600"), BigDecimal.class);
+
+ evaluate("5 % new java.math.BigDecimal('3')", new BigDecimal("2"), BigDecimal.class);
+ evaluate("3 % new java.math.BigDecimal('5')", new BigDecimal("3"), BigDecimal.class);
+ evaluate("3L % new java.math.BigDecimal('5')", new BigDecimal("3"), BigDecimal.class);
+ evaluate("3.0d % new java.math.BigDecimal('5')", new BigDecimal("3.0"), BigDecimal.class);
+ }
+
@Test
public void testMathOperatorAdd02() {
evaluate("'hello' + ' ' + 'world'", "hello world", String.class);
@@ -183,6 +346,7 @@ public void testMathOperatorsInChains() {
@Test
public void testIntegerArithmetic() {
+ // XXX SONO ARRIVATO QUI
evaluate("2 + 4", "6", Integer.class);
evaluate("5 - 4", "1", Integer.class);
evaluate("3 * 5", 15, Integer.class);
@@ -201,6 +365,7 @@ public void testPlus() throws Exception {
evaluate("7 + 2", "9", Integer.class);
evaluate("3.0f + 5.0f", 8.0d, Double.class);
evaluate("3.0d + 5.0d", 8.0d, Double.class);
+ evaluate("3 + new java.math.BigDecimal('5')", new BigDecimal("8"), BigDecimal.class);
evaluate("'ab' + 2", "ab2", String.class);
evaluate("2 + 'a'", "2a", String.class);
@@ -217,6 +382,7 @@ public void testPlus() throws Exception {
evaluate("+5d",5d,Double.class);
evaluate("+5L",5L,Long.class);
evaluate("+5",5,Integer.class);
+ evaluate("+new java.math.BigDecimal('5')", new BigDecimal("5"),BigDecimal.class);
evaluateAndCheckError("+'abc'",SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES);
// string concatenation
@@ -240,6 +406,7 @@ public void testMinus() throws Exception {
evaluate("-5d",-5d,Double.class);
evaluate("-5L",-5L,Long.class);
evaluate("-5",-5,Integer.class);
+ evaluate("-new java.math.BigDecimal('5')", new BigDecimal("-5"),BigDecimal.class);
evaluateAndCheckError("-'abc'",SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES);
}
@@ -249,6 +416,8 @@ public void testModulus() {
evaluate("3L%2L",1L,Long.class);
evaluate("3.0f%2.0f",1d,Double.class);
evaluate("5.0d % 3.1d", 1.9d, Double.class);
+ evaluate("new java.math.BigDecimal('5') % new java.math.BigDecimal('3')", new BigDecimal("2"), BigDecimal.class);
+ evaluate("new java.math.BigDecimal('5') % 3", new BigDecimal("2"), BigDecimal.class);
evaluateAndCheckError("'abc'%'def'",SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES);
}
@@ -258,6 +427,7 @@ public void testDivide() {
evaluate("4L/2L",2L,Long.class);
evaluate("3.0f div 5.0f", 0.6d, Double.class);
evaluate("4L DIV 2L",2L,Long.class);
+ evaluate("new java.math.BigDecimal('3') / 5", new BigDecimal("0.600"), BigDecimal.class);
evaluateAndCheckError("'abc'/'def'",SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES);
}
@@ -284,6 +454,18 @@ public void testDoubles() {
evaluate("6.0d % 3.5d", 2.5d, Double.class);
}
+
+ @Test
+ public void testBigDecimals() {
+ evaluate("3 + new java.math.BigDecimal('5')", new BigDecimal("8"), BigDecimal.class);
+ evaluate("3 - new java.math.BigDecimal('5')", new BigDecimal("-2"), BigDecimal.class);
+ evaluate("3 * new java.math.BigDecimal('5')", new BigDecimal("15"), BigDecimal.class);
+ evaluate("3 / new java.math.BigDecimal('5')", new BigDecimal("0.600"), BigDecimal.class);
+ evaluate("5 % new java.math.BigDecimal('3')", new BigDecimal("2"), BigDecimal.class);
+ evaluate("new java.math.BigDecimal('5') % 3", new BigDecimal("2"), BigDecimal.class);
+ evaluate("new java.math.BigDecimal('5') ^ 3", new BigDecimal("125"), BigDecimal.class);
+ }
+
@Test
public void testOperatorNames() throws Exception {
Operator node = getOperatorNode((SpelExpression)parser.parseExpression("1==3"));
@@ -335,6 +517,7 @@ public void testPower() {
evaluate("3.0d^2.0d",9.0d,Double.class);
evaluate("3L^2L",9L,Long.class);
evaluate("(2^32)^2",9223372036854775807L,Long.class);
+ evaluate("new java.math.BigDecimal('5') ^ 3", new BigDecimal("125"), BigDecimal.class);
}
@Test
diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/ast/OpPlusTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/ast/OpPlusTests.java
new file mode 100644
index 000000000000..b156b565c0da
--- /dev/null
+++ b/spring-expression/src/test/java/org/springframework/expression/spel/ast/OpPlusTests.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.expression.spel.ast;
+
+import java.sql.Time;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+import org.junit.Test;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.core.convert.support.GenericConversionService;
+import org.springframework.expression.TypedValue;
+import org.springframework.expression.spel.ExpressionState;
+import org.springframework.expression.spel.SpelEvaluationException;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
+import org.springframework.expression.spel.support.StandardTypeConverter;
+
+import static org.junit.Assert.*;
+
+/**
+ * Unit tests for SpEL's plus operator.
+ *
+ * @author Ivo Smid
+ * @author Chris Beams
+ * @since 3.2
+ * @see OpPlus
+ */
+public class OpPlusTests {
+
+ @Test(expected = IllegalArgumentException.class)
+ public void test_emptyOperands() {
+ new OpPlus(-1);
+ }
+
+ @Test(expected = SpelEvaluationException.class)
+ public void test_unaryPlusWithStringLiteral() {
+ ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext());
+
+ StringLiteral str = new StringLiteral("word", -1, "word");
+
+ OpPlus o = new OpPlus(-1, str);
+ o.getValueInternal(expressionState);
+ }
+
+ @Test
+ public void test_unaryPlusWithNumberOperand() {
+ ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext());
+
+ {
+ RealLiteral realLiteral = new RealLiteral("123.00", -1, 123.0);
+ OpPlus o = new OpPlus(-1, realLiteral);
+ TypedValue value = o.getValueInternal(expressionState);
+
+ assertEquals(Double.class, value.getTypeDescriptor().getObjectType());
+ assertEquals(Double.class, value.getTypeDescriptor().getType());
+ assertEquals(realLiteral.getLiteralValue().getValue(), value.getValue());
+ }
+
+ {
+ IntLiteral intLiteral = new IntLiteral("123", -1, 123);
+ OpPlus o = new OpPlus(-1, intLiteral);
+ TypedValue value = o.getValueInternal(expressionState);
+
+ assertEquals(Integer.class, value.getTypeDescriptor().getObjectType());
+ assertEquals(Integer.class, value.getTypeDescriptor().getType());
+ assertEquals(intLiteral.getLiteralValue().getValue(), value.getValue());
+ }
+
+ {
+ LongLiteral longLiteral = new LongLiteral("123", -1, 123L);
+ OpPlus o = new OpPlus(-1, longLiteral);
+ TypedValue value = o.getValueInternal(expressionState);
+
+ assertEquals(Long.class, value.getTypeDescriptor().getObjectType());
+ assertEquals(Long.class, value.getTypeDescriptor().getType());
+ assertEquals(longLiteral.getLiteralValue().getValue(), value.getValue());
+ }
+ }
+
+ @Test
+ public void test_binaryPlusWithNumberOperands() {
+ ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext());
+
+ {
+ RealLiteral n1 = new RealLiteral("123.00", -1, 123.0);
+ RealLiteral n2 = new RealLiteral("456.00", -1, 456.0);
+ OpPlus o = new OpPlus(-1, n1, n2);
+ TypedValue value = o.getValueInternal(expressionState);
+
+ assertEquals(Double.class, value.getTypeDescriptor().getObjectType());
+ assertEquals(Double.class, value.getTypeDescriptor().getType());
+ assertEquals(Double.valueOf(123.0 + 456.0), value.getValue());
+ }
+
+ {
+ LongLiteral n1 = new LongLiteral("123", -1, 123L);
+ LongLiteral n2 = new LongLiteral("456", -1, 456L);
+ OpPlus o = new OpPlus(-1, n1, n2);
+ TypedValue value = o.getValueInternal(expressionState);
+
+ assertEquals(Long.class, value.getTypeDescriptor().getObjectType());
+ assertEquals(Long.class, value.getTypeDescriptor().getType());
+ assertEquals(Long.valueOf(123L + 456L), value.getValue());
+ }
+
+ {
+ IntLiteral n1 = new IntLiteral("123", -1, 123);
+ IntLiteral n2 = new IntLiteral("456", -1, 456);
+ OpPlus o = new OpPlus(-1, n1, n2);
+ TypedValue value = o.getValueInternal(expressionState);
+
+ assertEquals(Integer.class, value.getTypeDescriptor().getObjectType());
+ assertEquals(Integer.class, value.getTypeDescriptor().getType());
+ assertEquals(Integer.valueOf(123 + 456), value.getValue());
+ }
+ }
+
+ @Test
+ public void test_binaryPlusWithStringOperands() {
+ ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext());
+
+ StringLiteral n1 = new StringLiteral("\"foo\"", -1, "\"foo\"");
+ StringLiteral n2 = new StringLiteral("\"bar\"", -1, "\"bar\"");
+ OpPlus o = new OpPlus(-1, n1, n2);
+ TypedValue value = o.getValueInternal(expressionState);
+
+ assertEquals(String.class, value.getTypeDescriptor().getObjectType());
+ assertEquals(String.class, value.getTypeDescriptor().getType());
+ assertEquals("foobar", value.getValue());
+ }
+
+ @Test
+ public void test_binaryPlusWithLeftStringOperand() {
+ ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext());
+
+ StringLiteral n1 = new StringLiteral("\"number is \"", -1, "\"number is \"");
+ LongLiteral n2 = new LongLiteral("123", -1, 123);
+ OpPlus o = new OpPlus(-1, n1, n2);
+ TypedValue value = o.getValueInternal(expressionState);
+
+ assertEquals(String.class, value.getTypeDescriptor().getObjectType());
+ assertEquals(String.class, value.getTypeDescriptor().getType());
+ assertEquals("number is 123", value.getValue());
+ }
+
+ @Test
+ public void test_binaryPlusWithRightStringOperand() {
+ ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext());
+
+ LongLiteral n1 = new LongLiteral("123", -1, 123);
+ StringLiteral n2 = new StringLiteral("\" is a number\"", -1, "\" is a number\"");
+ OpPlus o = new OpPlus(-1, n1, n2);
+ TypedValue value = o.getValueInternal(expressionState);
+
+ assertEquals(String.class, value.getTypeDescriptor().getObjectType());
+ assertEquals(String.class, value.getTypeDescriptor().getType());
+ assertEquals("123 is a number", value.getValue());
+ }
+
+ @Test
+ public void test_binaryPlusWithTime_ToString() {
+
+ ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext());
+
+ Time time = new Time(new Date().getTime());
+
+ VariableReference var = new VariableReference("timeVar", -1);
+ var.setValue(expressionState, time);
+
+ StringLiteral n2 = new StringLiteral("\" is now\"", -1, "\" is now\"");
+ OpPlus o = new OpPlus(-1, var, n2);
+ TypedValue value = o.getValueInternal(expressionState);
+
+ assertEquals(String.class, value.getTypeDescriptor().getObjectType());
+ assertEquals(String.class, value.getTypeDescriptor().getType());
+ assertEquals(time + " is now", value.getValue());
+ }
+
+ @Test
+ public void test_binaryPlusWithTimeConverted() {
+
+ final SimpleDateFormat format = new SimpleDateFormat("hh :--: mm :--: ss", Locale.ENGLISH);
+
+ GenericConversionService conversionService = new GenericConversionService();
+ conversionService.addConverter(new ConverterContextConfigurationAttributes
encapsulates the context
+ * ContextConfigurationAttributes
encapsulates the context
* configuration attributes declared on a test class via
* {@link ContextConfiguration @ContextConfiguration}.
- *
+ *
* @author Sam Brannen
* @since 3.1
* @see ContextConfiguration
@@ -52,7 +52,7 @@ public class ContextConfigurationAttributes {
* Resolve resource locations from the {@link ContextConfiguration#locations() locations}
* and {@link ContextConfiguration#value() value} attributes of the supplied
* {@link ContextConfiguration} annotation.
- *
+ *
* @throws IllegalStateException if both the locations and value attributes have been declared
*/
private static String[] resolveLocations(Class> declaringClass, ContextConfiguration contextConfiguration) {
@@ -81,7 +81,7 @@ else if (!ObjectUtils.isEmpty(valueLocations)) {
* supplied {@link ContextConfiguration @ContextConfiguration} annotation and
* the {@link Class test class} that declared it.
* @param declaringClass the test class that declared {@code @ContextConfiguration}
- * @param contextConfiguration the annotation from which to retrieve the attributes
+ * @param contextConfiguration the annotation from which to retrieve the attributes
*/
public ContextConfigurationAttributes(Class> declaringClass, ContextConfiguration contextConfiguration) {
this(declaringClass, resolveLocations(declaringClass, contextConfiguration), contextConfiguration.classes(),
@@ -93,14 +93,14 @@ public ContextConfigurationAttributes(Class> declaringClass, ContextConfigurat
* {@link Class test class} that declared the
* {@link ContextConfiguration @ContextConfiguration} annotation and its
* corresponding attributes.
- *
+ *
* @param declaringClass the test class that declared {@code @ContextConfiguration}
* @param locations the resource locations declared via {@code @ContextConfiguration}
* @param classes the configuration classes declared via {@code @ContextConfiguration}
* @param inheritLocations the inheritLocations
flag declared via {@code @ContextConfiguration}
* @param contextLoaderClass the {@code ContextLoader} class declared via {@code @ContextConfiguration}
* @throws IllegalArgumentException if the {@code declaringClass} or {@code contextLoaderClass} is
- * null
, or if the {@code locations} and {@code classes} are both non-empty
+ * null
, or if the {@code locations} and {@code classes} are both non-empty
*/
public ContextConfigurationAttributes(Class> declaringClass, String[] locations, Class>[] classes,
boolean inheritLocations, Class extends ContextLoader> contextLoaderClass) {
@@ -138,12 +138,12 @@ public Class> getDeclaringClass() {
* Get the resource locations that were declared via
* {@link ContextConfiguration @ContextConfiguration}.
* null
or empty
* @see ContextConfiguration#value
* @see ContextConfiguration#locations
- * @see #setLocations()
+ * @see #setLocations(String[])
*/
public String[] getLocations() {
return locations;
@@ -162,11 +162,11 @@ public void setLocations(String[] locations) {
* Get the configuration classes that were declared via
* {@link ContextConfiguration @ContextConfiguration}.
* null
or empty
* @see ContextConfiguration#classes
- * @see #setClasses()
+ * @see #setClasses(Class[])
*/
public Class>[] getClasses() {
return classes;
@@ -182,7 +182,7 @@ public void setClasses(Class>[] classes) {
}
/**
- * Determine if this {@code ContextConfigurationAttributes} instance has
+ * Determine if this {@code ContextConfigurationAttributes} instance has
* path-based resource locations.
* @return true
if the {@link #getLocations() locations} array is not empty
* @see #hasResources()
@@ -193,7 +193,7 @@ public boolean hasLocations() {
}
/**
- * Determine if this {@code ContextConfigurationAttributes} instance has
+ * Determine if this {@code ContextConfigurationAttributes} instance has
* class-based resources.
* @return true
if the {@link #getClasses() classes} array is not empty
* @see #hasResources()
@@ -204,7 +204,7 @@ public boolean hasClasses() {
}
/**
- * Determine if this {@code ContextConfigurationAttributes} instance has
+ * Determine if this {@code ContextConfigurationAttributes} instance has
* either path-based resource locations or class-based resources.
* @return true
if either the {@link #getLocations() locations}
* or the {@link #getClasses() classes} array is not empty
diff --git a/spring-test/src/main/java/org/springframework/test/context/SmartContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/SmartContextLoader.java
index fbf6cd31da27..63d52418a9dd 100644
--- a/spring-test/src/main/java/org/springframework/test/context/SmartContextLoader.java
+++ b/spring-test/src/main/java/org/springframework/test/context/SmartContextLoader.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,7 @@
/**
* Strategy interface for loading an {@link ApplicationContext application context}
* for an integration test managed by the Spring TestContext Framework.
- *
+ *
* ApplicationContext
loaded by a
* {@code SmartContextLoader} must register a JVM
@@ -110,7 +110,8 @@ public interface SmartContextLoader extends ContextLoader {
* @return a new application context
* @throws Exception if context loading failed
* @see #processContextConfiguration(ContextConfigurationAttributes)
- * @see org.springframework.context.annotation.AnnotationConfigUtils#registerAnnotationConfigProcessors()
+ * @see org.springframework.context.annotation.AnnotationConfigUtils
+ * #registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry)
* @see org.springframework.test.context.MergedContextConfiguration#getActiveProfiles()
* @see org.springframework.context.ConfigurableApplicationContext#getEnvironment()
*/
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 76ba939bce46..925e9f468aae 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,9 +28,9 @@
* RunAfterTestMethodCallbacks
is a custom JUnit 4.5+
* {@link Statement} which allows the Spring TestContext Framework to
* be plugged into the JUnit execution chain by calling
- * {@link TestContextManager#afterTestMethod(Object, Method) afterTestMethod()}
+ * {@link TestContextManager#afterTestMethod(Object, Method, Throwable) afterTestMethod()}
* on the supplied {@link TestContextManager}.
- *
+ *
* @see #evaluate()
* @see RunBeforeTestMethodCallbacks
* @author Sam Brannen
@@ -50,7 +50,7 @@ public class RunAfterTestMethodCallbacks extends Statement {
/**
* Constructs a new RunAfterTestMethodCallbacks
statement.
- *
+ *
* @param next the next Statement
in the execution chain
* @param testInstance the current test instance (never null
)
* @param testMethod the test method which has just been executed on the
@@ -70,7 +70,7 @@ public RunAfterTestMethodCallbacks(Statement next, Object testInstance, Method t
* Invokes the next {@link Statement} in the execution chain (typically an
* instance of {@link org.junit.internal.runners.statements.RunAfters
* RunAfters}), catching any exceptions thrown, and then calls
- * {@link TestContextManager#afterTestMethod(Object, Method)} with the first
+ * {@link TestContextManager#afterTestMethod(Object, Method, Throwable)} with the first
* caught exception (if any). If the call to afterTestMethod()
* throws an exception, it will also be tracked. Multiple exceptions will be
* combined into a {@link MultipleFailureException}.
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 44e18cf1ecb0..83e50888e774 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,11 +35,12 @@
* implementations of the {@link ContextLoader} SPI. Provides a
* Template Method based approach for {@link #processLocations processing}
* resource locations.
- *
+ *
* AbstractContextLoader
also provides a basis
* for all concrete implementations of the {@link SmartContextLoader} SPI. For
- * backwards compatibility with the {@code ContextLoader} SPI,
- * {@link #processContextConfiguration()} delegates to {@link #processLocations()}.
+ * backwards compatibility with the {@code ContextLoader} SPI,
+ * {@link #processContextConfiguration(ContextConfigurationAttributes)} delegates
+ * to {@link #processLocations(Class, String...)}.
*
* @author Sam Brannen
* @author Juergen Hoeller
@@ -59,7 +60,7 @@ public abstract class AbstractContextLoader implements SmartContextLoader {
/**
* For backwards compatibility with the {@link ContextLoader} SPI, the
- * default implementation simply delegates to {@link #processLocations()},
+ * default implementation simply delegates to {@link #processLocations(Class, String...)},
* passing it the {@link ContextConfigurationAttributes#getDeclaringClass()
* declaring class} and {@link ContextConfigurationAttributes#getLocations()
* resource locations} retrieved from the supplied
@@ -70,7 +71,7 @@ public abstract class AbstractContextLoader implements SmartContextLoader {
* <suffix>
is the value of the
* {@link #getResourceSuffix() resource suffix} string.
* locations
provided to
- * {@link #processLocations()} are null
or empty.
+ * {@link #processLocations(Class, String...)} are null
or empty.
* classes
present in the
* {@link ContextConfigurationAttributes configuration attributes} supplied
- * to {@link #processContextConfiguration()} are null
or empty.
+ * to {@link #processContextConfiguration(ContextConfigurationAttributes)}
+ * are null
or empty.
* true
by default
* @since 2.5
@@ -211,7 +213,7 @@ protected boolean isGenerateDefaultLocations() {
* null
or empty
* @since 2.5
- * @see #generateDefaultLocations()
+ * @see #generateDefaultLocations(Class)
*/
protected abstract String getResourceSuffix();
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 243e10da4d8e..6da30280093f 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@
/**
* Abstract, generic extension of {@link AbstractContextLoader} that loads a
* {@link GenericApplicationContext}.
- *
+ *
*
*
SmartContextLoader
will decide whether to load the context from
* locations or
* {@link org.springframework.context.annotation.Configuration configuration classes}.MergedContextConfiguration
in the
* {@link org.springframework.core.env.Environment Environment} of the context.DefaultListableBeanFactory
.MergedContextConfiguration
.
*
DefaultListableBeanFactory
.MergedContextConfiguration
.AnnotationConfigContextLoader
supports
* {@link org.springframework.context.annotation.Configuration configuration classes}
* rather than the String-based resource locations defined by the legacy
@@ -46,13 +46,13 @@
* AbstractContextLoader
or AbstractGenericContextLoader
.
* Consequently, AnnotationConfigContextLoader
should chiefly be
* considered a {@link org.springframework.test.context.SmartContextLoader SmartContextLoader}
- * rather than a {@link org.springframework.test.context.ContextLoader ContextLoader}.
- *
+ * rather than a {@link org.springframework.test.context.ContextLoader ContextLoader}.
+ *
* @author Sam Brannen
* @since 3.1
- * @see #processContextConfiguration()
- * @see #detectDefaultConfigurationClasses()
- * @see #loadBeanDefinitions()
+ * @see #processContextConfiguration(ContextConfigurationAttributes)
+ * @see #detectDefaultConfigurationClasses(Class)
+ * @see #loadBeanDefinitions(GenericApplicationContext, MergedContextConfiguration)
*/
public class AnnotationConfigContextLoader extends AbstractGenericContextLoader {
@@ -72,9 +72,9 @@ public class AnnotationConfigContextLoader extends AbstractGenericContextLoader
* supplied configuration attributes. Otherwise, properties in the supplied
* configuration attributes will not be modified.
* @param configAttributes the context configuration attributes to process
- * @see org.springframework.test.context.SmartContextLoader#processContextConfiguration()
+ * @see org.springframework.test.context.SmartContextLoader#processContextConfiguration(ContextConfigurationAttributes)
* @see #isGenerateDefaultLocations()
- * @see #detectDefaultConfigurationClasses()
+ * @see #detectDefaultConfigurationClasses(Class)
*/
public void processContextConfiguration(ContextConfigurationAttributes configAttributes) {
if (ObjectUtils.isEmpty(configAttributes.getClasses()) && isGenerateDefaultLocations()) {
@@ -117,7 +117,7 @@ private boolean isDefaultConfigurationClassCandidate(Class> clazz) {
* {@link Configuration @Configuration}.
* AnnotationConfigContextLoader
should be used as a
+ * AnnotationConfigContextLoader
should be used as a
* {@link org.springframework.test.context.SmartContextLoader SmartContextLoader},
* not as a legacy {@link org.springframework.test.context.ContextLoader ContextLoader}.
* Consequently, this method is not supported.
@@ -226,7 +226,7 @@ protected void loadBeanDefinitions(GenericApplicationContext context, MergedCont
}
/**
- * AnnotationConfigContextLoader
should be used as a
+ * AnnotationConfigContextLoader
should be used as a
* {@link org.springframework.test.context.SmartContextLoader SmartContextLoader},
* not as a legacy {@link org.springframework.test.context.ContextLoader ContextLoader}.
* Consequently, this method is not supported.
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/DelegatingSmartContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/support/DelegatingSmartContextLoader.java
index 0cdbb0aeecb8..fcdf2a30db07 100644
--- a/spring-test/src/main/java/org/springframework/test/context/support/DelegatingSmartContextLoader.java
+++ b/spring-test/src/main/java/org/springframework/test/context/support/DelegatingSmartContextLoader.java
@@ -34,7 +34,7 @@
* {@code DelegatingSmartContextLoader} is an implementation of the {@link SmartContextLoader}
* SPI that delegates to a set of candidate SmartContextLoaders (i.e.,
* {@link GenericXmlContextLoader} and {@link AnnotationConfigContextLoader}) to
- * determine which context loader is appropriate for a given test classÕs configuration.
+ * determine which context loader is appropriate for a given test class's configuration.
* Each candidate is given a chance to {@link #processContextConfiguration process} the
* {@link ContextConfigurationAttributes} for each class in the test class hierarchy that
* is annotated with {@link ContextConfiguration @ContextConfiguration}, and the candidate
diff --git a/spring-test/src/test/java/org/springframework/mock/web/MockServletContextTests.java b/spring-test/src/test/java/org/springframework/mock/web/MockServletContextTests.java
index 8693ed6bab3f..537d44be8ec5 100644
--- a/spring-test/src/test/java/org/springframework/mock/web/MockServletContextTests.java
+++ b/spring-test/src/test/java/org/springframework/mock/web/MockServletContextTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,22 +16,24 @@
package org.springframework.mock.web;
+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 java.util.Set;
-import org.junit.Ignore;
import org.junit.Test;
-import static org.junit.Assert.*;
-
/**
* @author Juergen Hoeller
* @author Chris Beams
+ * @author Sam Brannen
* @since 19.02.2006
*/
public class MockServletContextTests {
- @Ignore
- // fails to work under ant after move from .testsuite -> .test
@Test
public void testListFiles() {
MockServletContext sc = new MockServletContext("org/springframework/mock");
@@ -40,8 +42,6 @@ public void testListFiles() {
assertTrue(paths.contains("/web/MockServletContextTests.class"));
}
- @Ignore
- // fails to work under ant after move from .testsuite -> .test
@Test
public void testListSubdirectories() {
MockServletContext sc = new MockServletContext("org/springframework/mock");
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/ProfileAnnotationConfigTestSuite.java b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/ProfileAnnotationConfigTestSuite.java
index bfc462db600d..b3b7068c39a4 100644
--- a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/ProfileAnnotationConfigTestSuite.java
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/ProfileAnnotationConfigTestSuite.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,7 +22,7 @@
/**
* JUnit test suite for bean definition profile support in the
- * Spring TestContext Framework with XML-based configuration.
+ * Spring TestContext Framework with annotation-based configuration.
*
* @author Sam Brannen
* @since 3.1
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DefaultProfileAnnotationConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DefaultProfileAnnotationConfigTests.java
index dd0717bf105f..f6f2907f875a 100644
--- a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DefaultProfileAnnotationConfigTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DefaultProfileAnnotationConfigTests.java
@@ -16,24 +16,24 @@
package org.springframework.test.context.junit4.profile.importresource;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
import org.junit.Test;
import org.junit.runner.RunWith;
-
import org.springframework.beans.Employee;
import org.springframework.beans.Pet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
-import org.springframework.test.context.support.AnnotationConfigContextLoader;
-
-import static org.junit.Assert.*;
/**
* @author Juergen Hoeller
* @since 3.1
*/
@RunWith(SpringJUnit4ClassRunner.class)
-@ContextConfiguration(classes = DefaultProfileConfig.class, loader = AnnotationConfigContextLoader.class)
+@ContextConfiguration(classes = DefaultProfileConfig.class)
public class DefaultProfileAnnotationConfigTests {
@Autowired
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/AnnotatedConfigClassesWithoutAtConfigurationTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/AnnotatedConfigClassesWithoutAtConfigurationTests.java
new file mode 100644
index 000000000000..9548cb64963b
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/AnnotatedConfigClassesWithoutAtConfigurationTests.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.spr9051;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+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.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * This set of tests refutes the claims made in
+ * SPR-9051.
+ *
+ *
+ * When a {@code @ContextConfiguration} test class references a config class
+ * missing an {@code @Configuration} annotation, {@code @Bean} dependencies are
+ * wired successfully but the bean lifecycle is not applied (no init methods are
+ * invoked, for example). Adding the missing {@code @Configuration} annotation
+ * solves the problem, however the problem and solution isn't obvious since
+ * wiring/injection appeared to work.
+ *
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(classes = AnnotatedConfigClassesWithoutAtConfigurationTests.AnnotatedFactoryBeans.class)
+public class AnnotatedConfigClassesWithoutAtConfigurationTests {
+
+ /**
+ * This is intentionally not annotated with {@code @Configuration}.
+ * Consequently, this class contains what we call annotated factory bean
+ * methods instead of standard bean definition methods.
+ */
+ static class AnnotatedFactoryBeans {
+
+ static final AtomicInteger enigmaCallCount = new AtomicInteger();
+
+
+ @Bean
+ public String enigma() {
+ return "enigma #" + enigmaCallCount.incrementAndGet();
+ }
+
+ @Bean
+ public LifecycleBean lifecycleBean() {
+ // The following call to enigma() literally invokes the local
+ // enigma() method, not a CGLIB proxied version, since these methods
+ // are essentially factory bean methods.
+ LifecycleBean bean = new LifecycleBean(enigma());
+ assertFalse(bean.isInitialized());
+ return bean;
+ }
+ }
+
+
+ @Autowired
+ private String enigma;
+
+ @Autowired
+ private LifecycleBean lifecycleBean;
+
+
+ @Test
+ public void simpleStringBean() {
+ assertNotNull(enigma);
+ assertEquals("enigma #1", enigma);
+ }
+
+ @Test
+ public void beanWithLifecycleCallback() {
+ assertNotNull(lifecycleBean);
+ assertEquals("enigma #2", lifecycleBean.getName());
+ assertTrue(lifecycleBean.isInitialized());
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/LifecycleBean.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/LifecycleBean.java
new file mode 100644
index 000000000000..0d08f3d93ac4
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/LifecycleBean.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.spr9051;
+
+import javax.annotation.PostConstruct;
+
+/**
+ * Simple POJO that contains lifecycle callbacks.
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ */
+public class LifecycleBean {
+
+ private final String name;
+
+ private boolean initialized = false;
+
+
+ public LifecycleBean(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ @PostConstruct
+ public void init() {
+ initialized = true;
+ }
+
+ public boolean isInitialized() {
+ return this.initialized;
+ }
+
+}
diff --git a/spring-transaction/src/main/java/org/springframework/dao/QueryTimeoutException.java b/spring-tx/src/main/java/org/springframework/dao/QueryTimeoutException.java
similarity index 100%
rename from spring-transaction/src/main/java/org/springframework/dao/QueryTimeoutException.java
rename to spring-tx/src/main/java/org/springframework/dao/QueryTimeoutException.java
diff --git a/spring-tx/src/main/java/org/springframework/jca/work/jboss/JBossWorkManagerUtils.java b/spring-tx/src/main/java/org/springframework/jca/work/jboss/JBossWorkManagerUtils.java
index f25330148d2a..7c2757b600ce 100644
--- a/spring-tx/src/main/java/org/springframework/jca/work/jboss/JBossWorkManagerUtils.java
+++ b/spring-tx/src/main/java/org/springframework/jca/work/jboss/JBossWorkManagerUtils.java
@@ -53,7 +53,7 @@ public static WorkManager getWorkManager() {
/**
* Obtain the default JBoss JCA WorkManager through a JMX lookup
* for the JBossWorkManagerMBean.
- * @param workManagerObjectName the JMX object name to use
+ * @param mbeanName the JMX object name to use
* @see org.jboss.resource.work.JBossWorkManagerMBean
*/
public static WorkManager getWorkManager(String mbeanName) {
diff --git a/spring-tx/src/main/java/org/springframework/transaction/annotation/EnableTransactionManagement.java b/spring-tx/src/main/java/org/springframework/transaction/annotation/EnableTransactionManagement.java
index da6ea6494295..aa51af07e740 100644
--- a/spring-tx/src/main/java/org/springframework/transaction/annotation/EnableTransactionManagement.java
+++ b/spring-tx/src/main/java/org/springframework/transaction/annotation/EnableTransactionManagement.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -157,7 +157,7 @@
/**
* Indicate how transactional advice should be applied. The default is
- * {@link AdviceMode.PROXY}.
+ * {@link AdviceMode#PROXY}.
* @see AdviceMode
*/
AdviceMode mode() default AdviceMode.PROXY;
diff --git a/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionProxyFactoryBean.java b/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionProxyFactoryBean.java
index fe7ab37bbea6..eaf963cc397d 100644
--- a/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionProxyFactoryBean.java
+++ b/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionProxyFactoryBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -156,7 +156,6 @@ public void setTransactionAttributes(Properties transactionAttributes) {
* @see TransactionAttributeSourceEditor
* @see MethodMapTransactionAttributeSource
* @see NameMatchTransactionAttributeSource
- * @see AttributesTransactionAttributeSource
* @see org.springframework.transaction.annotation.AnnotationTransactionAttributeSource
*/
public void setTransactionAttributeSource(TransactionAttributeSource transactionAttributeSource) {
diff --git a/spring-tx/src/main/java/org/springframework/transaction/support/DelegatingTransactionDefinition.java b/spring-tx/src/main/java/org/springframework/transaction/support/DelegatingTransactionDefinition.java
index 8e15758e2244..d3e111f5bdbb 100644
--- a/spring-tx/src/main/java/org/springframework/transaction/support/DelegatingTransactionDefinition.java
+++ b/spring-tx/src/main/java/org/springframework/transaction/support/DelegatingTransactionDefinition.java
@@ -37,7 +37,7 @@ public abstract class DelegatingTransactionDefinition implements TransactionDefi
/**
* Create a DelegatingTransactionAttribute for the given target attribute.
- * @param targetAttribute the target TransactionAttribute to delegate to
+ * @param targetDefinition the target TransactionAttribute to delegate to
*/
public DelegatingTransactionDefinition(TransactionDefinition targetDefinition) {
Assert.notNull(targetDefinition, "Target definition must not be null");
diff --git a/spring-tx/src/main/resources/org/springframework/transaction/config/spring-tx-3.2.xsd b/spring-tx/src/main/resources/org/springframework/transaction/config/spring-tx-3.2.xsd
new file mode 100644
index 000000000000..0f6513855815
--- /dev/null
+++ b/spring-tx/src/main/resources/org/springframework/transaction/config/spring-tx-3.2.xsd
@@ -0,0 +1,247 @@
+
+
+
+ * ObjectMapper mapper = new ObjectMapper();
+ * mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
+ * converter.setObjectMapper(mapper);
+ *
+ */
+ public void setPrettyPrint(boolean prettyPrint) {
+ this.prettyPrint = prettyPrint;
+ configurePrettyPrint();
+ }
+
+
+ @Override
+ public boolean canRead(Class> clazz, MediaType mediaType) {
+ JavaType javaType = getJavaType(clazz);
+ return (this.objectMapper.canDeserialize(javaType) && canRead(mediaType));
+ }
+
+ @Override
+ public boolean canWrite(Class> clazz, MediaType mediaType) {
+ return (this.objectMapper.canSerialize(clazz) && canWrite(mediaType));
+ }
+
+ @Override
+ protected boolean supports(Class> clazz) {
+ // should not be called, since we override canRead/Write instead
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected Object readInternal(Class> clazz, HttpInputMessage inputMessage)
+ throws IOException, HttpMessageNotReadableException {
+
+ JavaType javaType = getJavaType(clazz);
+ try {
+ return this.objectMapper.readValue(inputMessage.getBody(), javaType);
+ }
+ catch (IOException ex) {
+ throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex);
+ }
+ }
+
+ @Override
+ protected void writeInternal(Object object, HttpOutputMessage outputMessage)
+ throws IOException, HttpMessageNotWritableException {
+
+ JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType());
+ JsonGenerator jsonGenerator =
+ this.objectMapper.getJsonFactory().createJsonGenerator(outputMessage.getBody(), encoding);
+
+ // A workaround for JsonGenerators not applying serialization features
+ // https://github.com/FasterXML/jackson-databind/issues/12
+ if (this.objectMapper.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
+ jsonGenerator.useDefaultPrettyPrinter();
+ }
+
+ try {
+ if (this.prefixJson) {
+ jsonGenerator.writeRaw("{} && ");
+ }
+ this.objectMapper.writeValue(jsonGenerator, object);
+ }
+ catch (JsonProcessingException ex) {
+ throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
+ }
+ }
+
+
+ /**
+ * Return the Jackson {@link JavaType} for the specified class.
+ *
+ * protected JavaType getJavaType(Class<?> clazz) {
+ * if (List.class.isAssignableFrom(clazz)) {
+ * return objectMapper.getTypeFactory().constructCollectionType(ArrayList.class, MyBean.class);
+ * } else {
+ * return super.getJavaType(clazz);
+ * }
+ * }
+ *
+ * @param clazz the class to return the java type for
+ * @return the java type
+ */
+ protected JavaType getJavaType(Class> clazz) {
+ return objectMapper.constructType(clazz);
+ }
+
+ /**
+ * Determine the JSON encoding to use for the given content type.
+ * @param contentType the media type as requested by the caller
+ * @return the JSON encoding to use (never null
)
+ */
+ protected JsonEncoding getJsonEncoding(MediaType contentType) {
+ if (contentType != null && contentType.getCharSet() != null) {
+ Charset charset = contentType.getCharSet();
+ for (JsonEncoding encoding : JsonEncoding.values()) {
+ if (charset.name().equals(encoding.getJavaName())) {
+ return encoding;
+ }
+ }
+ }
+ return JsonEncoding.UTF8;
+ }
+
+}
diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/MappingJacksonHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/json/MappingJacksonHttpMessageConverter.java
index 2eb6e62171b1..51bc4d503763 100644
--- a/spring-web/src/main/java/org/springframework/http/converter/json/MappingJacksonHttpMessageConverter.java
+++ b/spring-web/src/main/java/org/springframework/http/converter/json/MappingJacksonHttpMessageConverter.java
@@ -24,9 +24,9 @@
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.SerializationConfig;
import org.codehaus.jackson.map.type.TypeFactory;
import org.codehaus.jackson.type.JavaType;
-
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
@@ -35,6 +35,8 @@
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.util.Assert;
+import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
+
/**
* Implementation of {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverter}
* that can read and write JSON using Jackson's {@link ObjectMapper}.
@@ -57,6 +59,8 @@ public class MappingJacksonHttpMessageConverter extends AbstractHttpMessageConve
private boolean prefixJson = false;
+ private Boolean prettyPrint;
+
/**
* Construct a new {@code BindingJacksonHttpMessageConverter}.
@@ -77,6 +81,13 @@ public MappingJacksonHttpMessageConverter() {
public void setObjectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "ObjectMapper must not be null");
this.objectMapper = objectMapper;
+ configurePrettyPrint();
+ }
+
+ private void configurePrettyPrint() {
+ if (this.prettyPrint != null) {
+ this.objectMapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, this.prettyPrint);
+ }
}
/**
@@ -97,6 +108,20 @@ public void setPrefixJson(boolean prefixJson) {
this.prefixJson = prefixJson;
}
+ /**
+ * Whether to use the {@link DefaultPrettyPrinter} when writing JSON.
+ * This is a shortcut for setting up an {@code ObjectMapper} as follows:
+ *
+ * ObjectMapper mapper = new ObjectMapper();
+ * mapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true);
+ * converter.setObjectMapper(mapper);
+ *
+ * null
.
* Note that session access may not be thread-safe, in particular in a
* Servlet environment: Consider switching the
- * {@link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMethodAdapter#setSynchronizeOnSession "synchronizeOnSession"}
+ * {@link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#setSynchronizeOnSession "synchronizeOnSession"}
* flag to "true" if multiple requests are allowed to access a session concurrently.
* void
if the method handles the response itself (by
* writing the response content directly, declaring an argument of type
* {@link javax.servlet.ServletResponse} / {@link javax.servlet.http.HttpServletResponse}
@@ -235,8 +242,7 @@
* @see SessionAttributes
* @see InitBinder
* @see org.springframework.web.context.request.WebRequest
- * @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMethodMapping
- * @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMethodAdapter
+ * @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
* @see org.springframework.web.portlet.mvc.annotation.DefaultAnnotationHandlerMapping
* @see org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter
*/
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestParam.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestParam.java
index 591f4ad5f8b3..2c3d7ecec280 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestParam.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestParam.java
@@ -21,19 +21,30 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import java.util.Map;
/**
* Annotation which indicates that a method parameter should be bound to a web
* request parameter. Supported for annotated handler methods in Servlet and
* Portlet environments.
- *
+ *
+ * null
if none
* @return the root WebApplicationContext
* @see ConfigurableWebApplicationContext
*/
@@ -343,7 +342,7 @@ protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
/**
* @deprecated as of Spring 3.1 in favor of
* {@link #createWebApplicationContext(ServletContext)} and
- * {@link #configureAndRefreshWebApplicationContext(WebApplicationContext, ServletContext)}
+ * {@link #configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext, ServletContext)}
*/
@Deprecated
protected WebApplicationContext createWebApplicationContext(ServletContext sc, ApplicationContext parent) {
diff --git a/spring-web/src/main/java/org/springframework/web/context/ContextLoaderListener.java b/spring-web/src/main/java/org/springframework/web/context/ContextLoaderListener.java
index 28de0fa40ec4..d378ffdfac07 100644
--- a/spring-web/src/main/java/org/springframework/web/context/ContextLoaderListener.java
+++ b/spring-web/src/main/java/org/springframework/web/context/ContextLoaderListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -52,7 +52,7 @@ public class ContextLoaderListener extends ContextLoader implements ServletConte
* as a {@code
*
+ *
+ *
+ * @author Rossen Stoyanchev
+ * @since 3.2
+ *
+ * @see AsyncExecutionChain
+ */
+public abstract class AbstractDelegatingCallable implements Callable
+ *
+ */
+ public boolean isAsyncStarted() {
+ return ((this.asyncWebRequest != null) && this.asyncWebRequest.isAsyncStarted());
+ }
+
+ /**
+ * Add a Callable with logic required to complete request processing in a
+ * separate thread. See {@link AbstractDelegatingCallable} for details.
+ */
+ public void addDelegatingCallable(AbstractDelegatingCallable callable) {
+ Assert.notNull(callable, "Callable required");
+ this.delegatingCallables.add(callable);
+ }
+
+ /**
+ * Add the last Callable, for example the one returned by the controller.
+ * This property must be set prior to invoking
+ * {@link #startCallableChainProcessing()}.
+ */
+ public AsyncExecutionChain setCallable(Callable
+ *
+ *
+ *
+ *
+ *
+ * @author Rossen Stoyanchev
+ * @since 3.2
+ */
+public interface AsyncWebRequestInterceptor extends WebRequestInterceptor {
+
+ /**
+ * Invoked after {@link #preHandle(WebRequest)} and before
+ * the handler is executed. The returned {@link Callable} is used only if
+ * handler execution leads to teh start of async processing. It is invoked
+ * the async thread before the request is handled fro.
+ * Callable
to initialize
+ * ThreadLocal attributes on the async thread.
+ * @return a {@link Callable} instance or null
+ */
+ AbstractDelegatingCallable getAsyncCallable(WebRequest request);
+
+ /**
+ * Invoked after the execution of a handler if the handler started
+ * async processing instead of handling the request. Effectively this method
+ * is invoked on the way out of the main processing thread instead of
+ * {@link #postHandle(WebRequest, org.springframework.ui.ModelMap)}. The
+ * postHandle
method is invoked after the request is handled
+ * in the async thread.
+ *
+ *
+ *
+ * doFilter
, but guaranteed to be
diff --git a/spring-web/src/main/java/org/springframework/web/filter/RequestContextFilter.java b/spring-web/src/main/java/org/springframework/web/filter/RequestContextFilter.java
index 725907e03e91..3ce5e4e18a5e 100644
--- a/spring-web/src/main/java/org/springframework/web/filter/RequestContextFilter.java
+++ b/spring-web/src/main/java/org/springframework/web/filter/RequestContextFilter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2009 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
package org.springframework.web.filter;
import java.io.IOException;
+
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@@ -25,6 +26,8 @@
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
+import org.springframework.web.context.request.async.AbstractDelegatingCallable;
+import org.springframework.web.context.request.async.AsyncExecutionChain;
/**
* Servlet 2.3 Filter that exposes the request to the current thread,
@@ -40,6 +43,7 @@
*
* @author Juergen Hoeller
* @author Rod Johnson
+ * @author Rossen Stoyanchev
* @since 2.0
* @see org.springframework.context.i18n.LocaleContextHolder
* @see org.springframework.web.context.request.RequestContextHolder
@@ -74,17 +78,19 @@ protected void doFilterInternal(
throws ServletException, IOException {
ServletRequestAttributes attributes = new ServletRequestAttributes(request);
- LocaleContextHolder.setLocale(request.getLocale(), this.threadContextInheritable);
- RequestContextHolder.setRequestAttributes(attributes, this.threadContextInheritable);
- if (logger.isDebugEnabled()) {
- logger.debug("Bound request context to thread: " + request);
- }
+ initContextHolders(request, attributes);
+
+ AsyncExecutionChain chain = AsyncExecutionChain.getForCurrentRequest(request);
+ chain.addDelegatingCallable(getChainedCallable(request, attributes));
+
try {
filterChain.doFilter(request, response);
}
finally {
- LocaleContextHolder.resetLocaleContext();
- RequestContextHolder.resetRequestAttributes();
+ resetContextHolders();
+ if (chain.isAsyncStarted()) {
+ return;
+ }
attributes.requestCompleted();
if (logger.isDebugEnabled()) {
logger.debug("Cleared thread-bound request context: " + request);
@@ -92,4 +98,41 @@ protected void doFilterInternal(
}
}
+ private void initContextHolders(HttpServletRequest request, ServletRequestAttributes requestAttributes) {
+ LocaleContextHolder.setLocale(request.getLocale(), this.threadContextInheritable);
+ RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Bound request context to thread: " + request);
+ }
+ }
+
+ private void resetContextHolders() {
+ LocaleContextHolder.resetLocaleContext();
+ RequestContextHolder.resetRequestAttributes();
+ }
+
+ /**
+ * Create a Callable to use to complete processing in an async execution chain.
+ */
+ private AbstractDelegatingCallable getChainedCallable(final HttpServletRequest request,
+ final ServletRequestAttributes requestAttributes) {
+
+ return new AbstractDelegatingCallable() {
+ public Object call() throws Exception {
+ initContextHolders(request, requestAttributes);
+ try {
+ getNextCallable().call();
+ }
+ finally {
+ resetContextHolders();
+ requestAttributes.requestCompleted();
+ if (logger.isDebugEnabled()) {
+ logger.debug("Cleared thread-bound request context: " + request);
+ }
+ }
+ return null;
+ }
+ };
+ }
+
}
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 d214561959c9..e88948cce3c3 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-2010 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
+
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
@@ -30,6 +31,8 @@
import org.springframework.util.DigestUtils;
import org.springframework.util.FileCopyUtils;
+import org.springframework.web.context.request.async.AbstractDelegatingCallable;
+import org.springframework.web.context.request.async.AsyncExecutionChain;
import org.springframework.web.util.WebUtils;
/**
@@ -41,6 +44,7 @@
* is still rendered. As such, this filter only saves bandwidth, not server performance.
*
* @author Arjen Poutsma
+ * @author Rossen Stoyanchev
* @since 3.0
*/
public class ShallowEtagHeaderFilter extends OncePerRequestFilter {
@@ -55,8 +59,37 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
throws ServletException, IOException {
ShallowEtagResponseWrapper responseWrapper = new ShallowEtagResponseWrapper(response);
+
+ AsyncExecutionChain chain = AsyncExecutionChain.getForCurrentRequest(request);
+ chain.addDelegatingCallable(getAsyncCallable(request, response, responseWrapper));
+
filterChain.doFilter(request, responseWrapper);
+ if (chain.isAsyncStarted()) {
+ return;
+ }
+
+ updateResponse(request, response, responseWrapper);
+ }
+
+ /**
+ * Create a Callable to use to complete processing in an async execution chain.
+ */
+ private AbstractDelegatingCallable getAsyncCallable(final HttpServletRequest request,
+ final HttpServletResponse response, final ShallowEtagResponseWrapper responseWrapper) {
+
+ return new AbstractDelegatingCallable() {
+ public Object call() throws Exception {
+ getNextCallable().call();
+ updateResponse(request, response, responseWrapper);
+ return null;
+ }
+ };
+ }
+
+ private void updateResponse(HttpServletRequest request, HttpServletResponse response,
+ ShallowEtagResponseWrapper responseWrapper) throws IOException {
+
byte[] body = responseWrapper.toByteArray();
int statusCode = responseWrapper.getStatusCode();
@@ -149,6 +182,7 @@ public void setStatus(int sc) {
this.statusCode = sc;
}
+ @SuppressWarnings("deprecation")
@Override
public void setStatus(int sc, String sm) {
super.setStatus(sc, sm);
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 cdf3f5f84532..8417945de117 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-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,13 +29,13 @@
import org.springframework.util.ClassUtils;
/**
- * Encapsulates information about a bean method consisting of a {@linkplain #getMethod() method} and a
- * {@linkplain #getBean() bean}. Provides convenient access to method parameters, the method return value,
+ * Encapsulates information about a bean method consisting of a {@linkplain #getMethod() method} and a
+ * {@linkplain #getBean() bean}. Provides convenient access to method parameters, the method return value,
* method annotations.
*
* Map
with existing objects of
+ * Copy attributes in the supplied Map
with existing objects of
* the same name taking precedence (i.e. not getting replaced).
- * @see ModelMap#mergeAttributes(Map)
+ * A shortcut for {@code getModel().mergeAttributes(Map
+ *
+ *
+ *
+ *
+ *
+ * @author Rossen Stoyanchev
+ * @since 3.2
+ */
+public interface AsyncHandlerInterceptor extends HandlerInterceptor {
+
+ /**
+ * Invoked after {@link #preHandle(WebRequest)} and before
+ * the handler is executed. The returned {@link Callable} is used only if
+ * handler execution leads to teh start of async processing. It is invoked
+ * the async thread before the request is handled fro.
+ * Callable
to initialize
+ * ThreadLocal attributes on the async thread.
+ * @param request current HTTP request
+ * @param response current HTTP response
+ * @param handler chosen handler to execute, for type and/or instance examination
+ * @return a {@link Callable} instance or null
+ */
+ AbstractDelegatingCallable getAsyncCallable(HttpServletRequest request, HttpServletResponse response, Object handler);
+
+ /**
+ * Invoked after the execution of a handler if the handler started
+ * async processing instead of handling the request. Effectively this method
+ * is invoked on the way out of the main processing thread instead of
+ * {@link #postHandle(WebRequest, org.springframework.ui.ModelMap)}. The
+ * postHandle
method is invoked after the request is handled
+ * in the async thread.
+ * null
if none
- * @see HandlerInterceptor#afterCompletion
- */
- private void triggerAfterCompletion(HandlerExecutionChain mappedHandler,
- int interceptorIndex,
- HttpServletRequest request,
- HttpServletResponse response,
- Exception ex) throws Exception {
+ private void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response,
+ HandlerExecutionChain mappedHandler, Exception ex) throws Exception {
- // Apply afterCompletion methods of registered interceptors.
if (mappedHandler != null) {
- HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();
- if (interceptors != null) {
- for (int i = interceptorIndex; i >= 0; i--) {
- HandlerInterceptor interceptor = interceptors[i];
- try {
- interceptor.afterCompletion(request, response, mappedHandler.getHandler(), ex);
- }
- catch (Throwable ex2) {
- logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
- }
- }
- }
+ mappedHandler.triggerAfterCompletion(request, response, ex);
+ }
+ throw ex;
+ }
+
+ private void triggerAfterCompletionWithError(HttpServletRequest request, HttpServletResponse response,
+ HandlerExecutionChain mappedHandler, Error error) throws Exception, ServletException {
+
+ ServletException ex = new NestedServletException("Handler processing failed", error);
+ if (mappedHandler != null) {
+ mappedHandler.triggerAfterCompletion(request, response, ex);
}
+ throw ex;
}
/**
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 f44b130484fc..b8d7855ed256 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collections;
+
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@@ -45,6 +46,8 @@
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
+import org.springframework.web.context.request.async.AbstractDelegatingCallable;
+import org.springframework.web.context.request.async.AsyncExecutionChain;
import org.springframework.web.context.support.ServletRequestHandledEvent;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.context.support.XmlWebApplicationContext;
@@ -112,6 +115,7 @@
* @author Juergen Hoeller
* @author Sam Brannen
* @author Chris Beams
+ * @author Rossen Stoyanchev
* @see #doService
* @see #setContextClass
* @see #setContextConfigLocation
@@ -190,7 +194,6 @@ public abstract class FrameworkServlet extends HttpServletBean {
private ArrayListrefresh()
will
* be called automatically after this method returns.
* onApplicationEvent
on the FrameworkServlet instance.
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/HandlerExecutionChain.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/HandlerExecutionChain.java
index 9c8e6d8949b3..d078e1706f39 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/HandlerExecutionChain.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/HandlerExecutionChain.java
@@ -20,7 +20,13 @@
import java.util.Arrays;
import java.util.List;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
import org.springframework.util.CollectionUtils;
+import org.springframework.web.context.request.async.AsyncExecutionChain;
/**
* Handler execution chain, consisting of handler object and any handler interceptors.
@@ -32,12 +38,15 @@
*/
public class HandlerExecutionChain {
+ private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);
+
private final Object handler;
private HandlerInterceptor[] interceptors;
private Listtrue
if the execution chain should proceed with the
+ * next interceptor or the handler itself. Else, DispatcherServlet assumes
+ * that this interceptor has already dealt with the response itself.
+ */
+ boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response)
+ throws Exception {
+
+ if (getInterceptors() != null) {
+ for (int i = 0; i < getInterceptors().length; i++) {
+ HandlerInterceptor interceptor = getInterceptors()[i];
+ if (!interceptor.preHandle(request, response, this.handler)) {
+ triggerAfterCompletion(request, response, null);
+ return false;
+ }
+ this.interceptorIndex = i;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Apply postHandle methods of registered interceptors.
+ */
+ void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv)
+ throws Exception {
+
+ if (getInterceptors() == null) {
+ return;
+ }
+ for (int i = getInterceptors().length - 1; i >= 0; i--) {
+ HandlerInterceptor interceptor = getInterceptors()[i];
+ interceptor.postHandle(request, response, this.handler, mv);
+ }
+ }
+
+ /**
+ * Add delegating, async Callable instances to the {@link AsyncExecutionChain}
+ * for use in case of asynchronous request processing.
+ */
+ void addDelegatingCallables(HttpServletRequest request, HttpServletResponse response)
+ throws Exception {
+
+ if (getInterceptors() == null) {
+ return;
+ }
+ for (int i = getInterceptors().length - 1; i >= 0; i--) {
+ HandlerInterceptor interceptor = getInterceptors()[i];
+ if (interceptor instanceof AsyncHandlerInterceptor) {
+ try {
+ AsyncHandlerInterceptor asyncInterceptor = (AsyncHandlerInterceptor) interceptor;
+ AsyncExecutionChain chain = AsyncExecutionChain.getForCurrentRequest(request);
+ chain.addDelegatingCallable(asyncInterceptor.getAsyncCallable(request, response, this.handler));
+ }
+ catch (Throwable ex) {
+ logger.error("HandlerInterceptor.addAsyncCallables threw exception", ex);
+ }
+ }
+ }
+ }
+
+ /**
+ * Trigger postHandleAsyncStarted callbacks on the mapped HandlerInterceptors.
+ */
+ void applyPostHandleAsyncStarted(HttpServletRequest request, HttpServletResponse response)
+ throws Exception {
+
+ if (getInterceptors() == null) {
+ return;
+ }
+ for (int i = getInterceptors().length - 1; i >= 0; i--) {
+ HandlerInterceptor interceptor = getInterceptors()[i];
+ if (interceptor instanceof AsyncHandlerInterceptor) {
+ try {
+ ((AsyncHandlerInterceptor) interceptor).postHandleAsyncStarted(request, response, this.handler);
+ }
+ catch (Throwable ex) {
+ logger.error("HandlerInterceptor.postHandleAsyncStarted threw exception", ex);
+ }
+ }
+ }
+ }
+
+ /**
+ * Trigger afterCompletion callbacks on the mapped HandlerInterceptors.
+ * Will just invoke afterCompletion for all interceptors whose preHandle invocation
+ * has successfully completed and returned true.
+ */
+ void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
+ throws Exception {
+
+ if (getInterceptors() == null) {
+ return;
+ }
+ for (int i = this.interceptorIndex; i >= 0; i--) {
+ HandlerInterceptor interceptor = getInterceptors()[i];
+ try {
+ interceptor.afterCompletion(request, response, this.handler, ex);
+ }
+ catch (Throwable ex2) {
+ logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
+ }
+ }
+ }
+
/**
* Delegates to the handler's toString()
.
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 f0d09aaff75e..080565ea16a6 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-2010 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,6 +35,7 @@
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.feed.AtomFeedHttpMessageConverter;
import org.springframework.http.converter.feed.RssChannelHttpMessageConverter;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
@@ -65,52 +66,52 @@
import org.w3c.dom.Element;
/**
- * A {@link BeanDefinitionParser} that provides the configuration for the
+ * A {@link BeanDefinitionParser} that provides the configuration for the
* {@code
- *
*
- *
- *
- *
+ *
*
- *
- *
- *
*
- *
+ *
* @author Keith Donald
* @author Juergen Hoeller
* @author Arjen Poutsma
@@ -125,6 +126,10 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
private static final boolean jaxb2Present =
ClassUtils.isPresent("javax.xml.bind.Binder", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
+ private static final boolean jackson2Present =
+ ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", AnnotationDrivenBeanDefinitionParser.class.getClassLoader()) &&
+ ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
+
private static final boolean jacksonPresent =
ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", AnnotationDrivenBeanDefinitionParser.class.getClassLoader()) &&
ClassUtils.isPresent("org.codehaus.jackson.JsonGenerator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
@@ -158,7 +163,7 @@ public BeanDefinition parse(Element element, ParserContext parserContext) {
ManagedList> messageConverters = getMessageConverters(element, source, parserContext);
ManagedList> argumentResolvers = getArgumentResolvers(element, source, parserContext);
ManagedList> returnValueHandlers = getReturnValueHandlers(element, source, parserContext);
-
+
RootBeanDefinition methodAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
methodAdapterDef.setSource(source);
methodAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
@@ -215,7 +220,7 @@ public BeanDefinition parse(Element element, ParserContext parserContext) {
parserContext.registerComponent(new BeanComponentDefinition(defaultExceptionResolver, defaultExceptionResolverName));
parserContext.registerComponent(new BeanComponentDefinition(mappedCsInterceptorDef, mappedInterceptorName));
- // Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
+ // Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
MvcNamespaceUtils.registerDefaultComponents(parserContext, source);
parserContext.popAndRegisterContainingComponent();
@@ -309,7 +314,10 @@ private ManagedList> getMessageConverters(Element element, Object source, Pars
messageConverters
.add(createConverterBeanDefinition(Jaxb2RootElementHttpMessageConverter.class, source));
}
- if (jacksonPresent) {
+ if (jackson2Present) {
+ messageConverters.add(createConverterBeanDefinition(MappingJackson2HttpMessageConverter.class, source));
+ }
+ else if (jacksonPresent) {
messageConverters.add(createConverterBeanDefinition(MappingJacksonHttpMessageConverter.class, source));
}
if (romePresent) {
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 03c8480bba0f..55d418d80719 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
@@ -41,6 +41,7 @@
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.feed.AtomFeedHttpMessageConverter;
import org.springframework.http.converter.feed.RssChannelHttpMessageConverter;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
@@ -74,59 +75,59 @@
/**
* This is the main class providing the configuration behind the MVC Java config.
- * It is typically imported by adding {@link EnableWebMvc @EnableWebMvc} to an
- * application {@link Configuration @Configuration} class. An alternative more
+ * It is typically imported by adding {@link EnableWebMvc @EnableWebMvc} to an
+ * application {@link Configuration @Configuration} class. An alternative more
* advanced option is to extend directly from this class and override methods as
- * necessary remembering to add {@link Configuration @Configuration} to the
+ * necessary remembering to add {@link Configuration @Configuration} to the
* subclass and {@link Bean @Bean} to overridden {@link Bean @Bean} methods.
* For more details see the Javadoc of {@link EnableWebMvc @EnableWebMvc}.
- *
+ *
*
- *
*
*
- *
*
*
- *
*
- *
*
- *
+ *
* @see EnableWebMvc
* @see WebMvcConfigurer
* @see WebMvcConfigurerAdapter
@@ -151,9 +152,9 @@ public void setServletContext(ServletContext servletContext) {
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
-
+
/**
- * Return a {@link RequestMappingHandlerMapping} ordered at 0 for mapping
+ * Return a {@link RequestMappingHandlerMapping} ordered at 0 for mapping
* requests to annotated controllers.
*/
@Bean
@@ -163,11 +164,11 @@ public RequestMappingHandlerMapping requestMappingHandlerMapping() {
handlerMapping.setInterceptors(getInterceptors());
return handlerMapping;
}
-
+
/**
- * Provide access to the shared handler interceptors used to configure
- * {@link HandlerMapping} instances with. This method cannot be overridden,
- * use {@link #addInterceptors(InterceptorRegistry)} instead.
+ * Provide access to the shared handler interceptors used to configure
+ * {@link HandlerMapping} instances with. This method cannot be overridden,
+ * use {@link #addInterceptors(InterceptorRegistry)} instead.
*/
protected final Object[] getInterceptors() {
if (interceptors == null) {
@@ -178,9 +179,9 @@ protected final Object[] getInterceptors() {
}
return interceptors.toArray();
}
-
+
/**
- * Override this method to add Spring MVC interceptors for
+ * Override this method to add Spring MVC interceptors for
* pre- and post-processing of controller invocation.
* @see InterceptorRegistry
*/
@@ -188,15 +189,15 @@ protected void addInterceptors(InterceptorRegistry registry) {
}
/**
- * Return a handler mapping ordered at 1 to map URL paths directly to
- * view names. To configure view controllers, override
- * {@link #addViewControllers}.
+ * Return a handler mapping ordered at 1 to map URL paths directly to
+ * view names. To configure view controllers, override
+ * {@link #addViewControllers}.
*/
@Bean
public HandlerMapping viewControllerHandlerMapping() {
ViewControllerRegistry registry = new ViewControllerRegistry();
addViewControllers(registry);
-
+
AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
handlerMapping = handlerMapping != null ? handlerMapping : new EmptyHandlerMapping();
handlerMapping.setInterceptors(getInterceptors());
@@ -209,9 +210,9 @@ public HandlerMapping viewControllerHandlerMapping() {
*/
protected void addViewControllers(ViewControllerRegistry registry) {
}
-
+
/**
- * Return a {@link BeanNameUrlHandlerMapping} ordered at 2 to map URL
+ * Return a {@link BeanNameUrlHandlerMapping} ordered at 2 to map URL
* paths to controller bean names.
*/
@Bean
@@ -223,8 +224,8 @@ public BeanNameUrlHandlerMapping beanNameHandlerMapping() {
}
/**
- * Return a handler mapping ordered at Integer.MAX_VALUE-1 with mapped
- * resource handlers. To configure resource handling, override
+ * Return a handler mapping ordered at Integer.MAX_VALUE-1 with mapped
+ * resource handlers. To configure resource handling, override
* {@link #addResourceHandlers}.
*/
@Bean
@@ -237,16 +238,16 @@ public HandlerMapping resourceHandlerMapping() {
}
/**
- * Override this method to add resource handlers for serving static resources.
+ * Override this method to add resource handlers for serving static resources.
* @see ResourceHandlerRegistry
*/
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
}
/**
- * Return a handler mapping ordered at Integer.MAX_VALUE with a mapped
- * default servlet handler. To configure "default" Servlet handling,
- * override {@link #configureDefaultServletHandling}.
+ * Return a handler mapping ordered at Integer.MAX_VALUE with a mapped
+ * default servlet handler. To configure "default" Servlet handling,
+ * override {@link #configureDefaultServletHandling}.
*/
@Bean
public HandlerMapping defaultServletHandlerMapping() {
@@ -258,15 +259,15 @@ public HandlerMapping defaultServletHandlerMapping() {
}
/**
- * Override this method to configure "default" Servlet handling.
+ * Override this method to configure "default" Servlet handling.
* @see DefaultServletHandlerConfigurer
*/
protected void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
}
/**
- * Returns a {@link RequestMappingHandlerAdapter} for processing requests
- * through annotated controller methods. Consider overriding one of these
+ * Returns a {@link RequestMappingHandlerAdapter} for processing requests
+ * through annotated controller methods. Consider overriding one of these
* other more fine-grained methods:
*
*
- *
*/
@@ -524,11 +528,11 @@ protected final void addDefaultHandlerExceptionResolvers(List@SessionAttributes
annotated handlers
* for the given number of seconds. Default is 0, preventing caching completely.
- * @SessionAttributes
annotated handlers),
+ * @SessionAttributes
annotated handlers),
* this setting will apply to @SessionAttributes
handlers only.
* @see #setCacheSeconds
* @see org.springframework.web.bind.annotation.SessionAttributes
@@ -349,25 +361,25 @@ public void setSynchronizeOnSession(boolean synchronizeOnSession) {
}
/**
- * Set the ParameterNameDiscoverer to use for resolving method parameter
- * names if needed (e.g. for default attribute names). Default is a
+ * Set the ParameterNameDiscoverer to use for resolving method parameter
+ * names if needed (e.g. for default attribute names). Default is a
* {@link org.springframework.core.LocalVariableTableParameterNameDiscoverer}.
*/
public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {
this.parameterNameDiscoverer = parameterNameDiscoverer;
}
-
+
/**
- * By default the content of the "default" model is used both during
- * rendering and redirect scenarios. Alternatively a controller method
+ * By default the content of the "default" model is used both during
+ * rendering and redirect scenarios. Alternatively a controller method
* can declare a {@link RedirectAttributes} argument and use it to provide
* attributes for a redirect.
- *
- *
- * null
, using the request attributes
* for Errors retrieval)
* @see org.springframework.web.servlet.DispatcherServlet
- * @see #RequestContext(javax.servlet.http.HttpServletRequest, javax.servlet.ServletContext, Map)
+ * @see #RequestContext(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, javax.servlet.ServletContext, Map)
*/
public RequestContext(HttpServletRequest request, Map{}
).
* For example, send in a relative URL foo/{bar}?spam={spam}
and a parameter map
* {bar=baz,spam=nuts}
and the result will be [contextpath]/foo/baz?spam=nuts
.
- *
+ *
* @param relativeUrl the relative URL part
- * @param a map of parameters to insert as placeholders in the url
+ * @param params a map of parameters to insert as placeholders in the url
* @return a URL that points back to the server with an absolute path (also URL-encoded accordingly)
*/
public String getContextUrl(String relativeUrl, Map
+ * ObjectMapper mapper = new ObjectMapper();
+ * mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
+ * converter.setObjectMapper(mapper);
+ *
+ *
+ * ObjectMapper mapper = new ObjectMapper();
+ * mapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true);
+ * converter.setObjectMapper(mapper);
+ *
+ *