diff --git a/build.gradle b/build.gradle index b6e1b8a03fcd..bf5df93e859c 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ buildscript { } dependencies { classpath("org.springframework.build.gradle:propdeps-plugin:0.0.3") - classpath("org.springframework.build.gradle:docbook-reference-plugin:0.2.4") + classpath("org.springframework.build.gradle:docbook-reference-plugin:0.2.6") } } @@ -12,7 +12,7 @@ configure(allprojects) { project -> group = "org.springframework" version = qualifyVersionIfNecessary(version) - ext.aspectjVersion = "1.7.1" + ext.aspectjVersion = "1.7.2" ext.easymockVersion = "2.5.2" ext.hsqldbVersion = "1.8.0.10" ext.junitVersion = "4.11" diff --git a/buildSrc/src/main/groovy/org/springframework/build/gradle/MergePlugin.groovy b/buildSrc/src/main/groovy/org/springframework/build/gradle/MergePlugin.groovy index 510a2698c6ae..4143780702b6 100644 --- a/buildSrc/src/main/groovy/org/springframework/build/gradle/MergePlugin.groovy +++ b/buildSrc/src/main/groovy/org/springframework/build/gradle/MergePlugin.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -110,7 +110,7 @@ class MergePlugin implements Plugin { // update 'into' project artifacts to contain the source artifact contents project.merge.into.sourcesJar.from(project.sourcesJar.source) - project.merge.into.jar.from(project.jar.source) + project.merge.into.jar.from(project.sourceSets.main.output) project.merge.into.javadoc { source += project.javadoc.source classpath += project.javadoc.classpath diff --git a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java index 96237b86d173..7fbfbf560445 100644 --- a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java +++ b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,8 @@ package org.springframework.aop.interceptor; import java.lang.reflect.Method; -import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import org.springframework.beans.BeansException; @@ -45,7 +45,7 @@ */ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware { - private final Map executors = new HashMap(); + private final Map executors = new ConcurrentHashMap(16); private Executor defaultExecutor; @@ -59,7 +59,7 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware { * @param defaultExecutor the executor to use when executing asynchronous methods */ public AsyncExecutionAspectSupport(Executor defaultExecutor) { - this.setExecutor(defaultExecutor); + this.defaultExecutor = defaultExecutor; } @@ -90,24 +90,25 @@ public void setBeanFactory(BeanFactory beanFactory) throws BeansException { * @return the executor to use (never {@code null}) */ protected AsyncTaskExecutor determineAsyncExecutor(Method method) { - if (!this.executors.containsKey(method)) { - Executor executor = this.defaultExecutor; + AsyncTaskExecutor executor = this.executors.get(method); + if (executor == null) { + Executor executorToUse = this.defaultExecutor; String qualifier = getExecutorQualifier(method); if (StringUtils.hasLength(qualifier)) { - Assert.notNull(this.beanFactory, - "BeanFactory must be set on " + this.getClass().getSimpleName() + - " to access qualified executor [" + qualifier + "]"); - executor = BeanFactoryAnnotationUtils.qualifiedBeanOfType( + Assert.notNull(this.beanFactory, "BeanFactory must be set on " + getClass().getSimpleName() + + " to access qualified executor '" + qualifier + "'"); + executorToUse = BeanFactoryAnnotationUtils.qualifiedBeanOfType( this.beanFactory, Executor.class, qualifier); } - if (executor instanceof AsyncTaskExecutor) { - this.executors.put(method, (AsyncTaskExecutor) executor); - } - else if (executor != null) { - this.executors.put(method, new TaskExecutorAdapter(executor)); + else if (executorToUse == null) { + throw new IllegalStateException("No executor qualifier specified and no default executor set on " + + getClass().getSimpleName() + " either"); } + executor = (executorToUse instanceof AsyncTaskExecutor ? + (AsyncTaskExecutor) executorToUse : new TaskExecutorAdapter(executorToUse)); + this.executors.put(method, executor); } - return this.executors.get(method); + return executor; } /** 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 792639b1c6f0..daa23a329364 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ package org.springframework.aop.interceptor; import java.lang.reflect.Method; - import java.util.concurrent.Callable; import java.util.concurrent.Executor; import java.util.concurrent.Future; @@ -25,8 +24,11 @@ import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; +import org.springframework.aop.support.AopUtils; +import org.springframework.core.BridgeMethodResolver; import org.springframework.core.Ordered; import org.springframework.core.task.AsyncTaskExecutor; +import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; /** @@ -76,7 +78,11 @@ public AsyncExecutionInterceptor(Executor executor) { * otherwise. */ public Object invoke(final MethodInvocation invocation) throws Throwable { - Future result = this.determineAsyncExecutor(invocation.getMethod()).submit( + Class targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null); + Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass); + specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod); + + Future result = determineAsyncExecutor(specificMethod).submit( new Callable() { public Object call() throws Exception { try { @@ -91,6 +97,7 @@ public Object call() throws Exception { return null; } }); + if (Future.class.isAssignableFrom(invocation.getMethod().getReturnType())) { return result; } @@ -100,10 +107,9 @@ public Object call() throws Exception { } /** - * {@inheritDoc} - *

This implementation is a no-op for compatibility in Spring 3.1.2. Subclasses may - * override to provide support for extracting qualifier information, e.g. via an - * annotation on the given method. + * This implementation is a no-op for compatibility in Spring 3.1.2. + * Subclasses may override to provide support for extracting qualifier information, + * e.g. via an annotation on the given method. * @return always {@code null} * @see #determineAsyncExecutor(Method) * @since 3.1.2 diff --git a/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AbstractBeanConfigurerAspect.aj b/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AbstractBeanConfigurerAspect.aj index 85342e838b85..033a083016d2 100644 --- a/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AbstractBeanConfigurerAspect.aj +++ b/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AbstractBeanConfigurerAspect.aj @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - + package org.springframework.beans.factory.aspectj; import org.aspectj.lang.annotation.SuppressAjWarnings; @@ -23,12 +23,12 @@ import org.springframework.beans.factory.wiring.BeanConfigurerSupport; * Abstract superaspect for AspectJ aspects that can perform Dependency * Injection on objects, however they may be created. Define the beanCreation() * pointcut in subaspects. - * + * *

Subaspects may also need a metadata resolution strategy, in the - * BeanWiringInfoResolver interface. The default implementation + * {@code BeanWiringInfoResolver} interface. The default implementation * looks for a bean with the same name as the FQN. This is the default name * of a bean in a Spring container if the id value is not supplied explicitly. - * + * * @author Rob Harrop * @author Rod Johnson * @author Adrian Colyer @@ -62,7 +62,7 @@ public abstract aspect AbstractBeanConfigurerAspect extends BeanConfigurerSuppor /** * The initialization of a new object. - * + * *

WARNING: Although this pointcut is non-abstract for backwards * compatibility reasons, it is meant to be overridden to select * initialization of any configurable bean. diff --git a/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AbstractDependencyInjectionAspect.aj b/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AbstractDependencyInjectionAspect.aj index 23b012ecc753..fa8fc6441f3a 100644 --- a/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AbstractDependencyInjectionAspect.aj +++ b/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AbstractDependencyInjectionAspect.aj @@ -17,11 +17,12 @@ package org.springframework.beans.factory.aspectj; import org.aspectj.lang.annotation.SuppressAjWarnings; +import org.aspectj.lang.annotation.control.CodeGenerationHint; /** * Abstract base aspect that can perform Dependency * Injection on objects, however they may be created. - * + * * @author Ramnivas Laddad * @since 2.5.2 */ @@ -29,28 +30,29 @@ public abstract aspect AbstractDependencyInjectionAspect { /** * Select construction join points for objects to inject dependencies */ - public abstract pointcut beanConstruction(Object bean); + public abstract pointcut beanConstruction(Object bean); /** * Select deserialization join points for objects to inject dependencies */ public abstract pointcut beanDeserialization(Object bean); - + /** * Select join points in a configurable bean */ public abstract pointcut inConfigurableBean(); - + /** * Select join points in beans to be configured prior to construction? * By default, use post-construction injection matching the default in the Configurable annotation. */ public pointcut preConstructionConfiguration() : if(false); - + /** - * Select the most-specific initialization join point + * Select the most-specific initialization join point * (most concrete class) for the initialization of an instance. */ + @CodeGenerationHint(ifNameSuffix="6f1") public pointcut mostSpecificSubTypeConstruction() : if(thisJoinPoint.getSignature().getDeclaringType() == thisJoinPoint.getThis().getClass()); @@ -58,25 +60,25 @@ public abstract aspect AbstractDependencyInjectionAspect { * Select least specific super type that is marked for DI (so that injection occurs only once with pre-construction inejection */ public abstract pointcut leastSpecificSuperTypeConstruction(); - + /** * Configure the bean */ public abstract void configureBean(Object bean); - - private pointcut preConstructionCondition() : + + private pointcut preConstructionCondition() : leastSpecificSuperTypeConstruction() && preConstructionConfiguration(); - + private pointcut postConstructionCondition() : mostSpecificSubTypeConstruction() && !preConstructionConfiguration(); - + /** * Pre-construction configuration. */ @SuppressAjWarnings("adviceDidNotMatch") - before(Object bean) : - beanConstruction(bean) && preConstructionCondition() && inConfigurableBean() { + before(Object bean) : + beanConstruction(bean) && preConstructionCondition() && inConfigurableBean() { configureBean(bean); } @@ -84,18 +86,18 @@ public abstract aspect AbstractDependencyInjectionAspect { * Post-construction configuration. */ @SuppressAjWarnings("adviceDidNotMatch") - after(Object bean) returning : + after(Object bean) returning : beanConstruction(bean) && postConstructionCondition() && inConfigurableBean() { configureBean(bean); } - + /** * Post-deserialization configuration. */ @SuppressAjWarnings("adviceDidNotMatch") - after(Object bean) returning : + after(Object bean) returning : beanDeserialization(bean) && inConfigurableBean() { configureBean(bean); } - + } diff --git a/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AbstractInterfaceDrivenDependencyInjectionAspect.aj b/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AbstractInterfaceDrivenDependencyInjectionAspect.aj index 8e8b634ef0fb..8270d6962d5b 100644 --- a/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AbstractInterfaceDrivenDependencyInjectionAspect.aj +++ b/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AbstractInterfaceDrivenDependencyInjectionAspect.aj @@ -26,36 +26,36 @@ import java.io.Serializable; * upon deserialization. Subaspects need to simply provide definition for the configureBean() method. This * method may be implemented without relying on Spring container if so desired. *

- *

+ *

* There are two cases that needs to be handled: *

    - *
  1. Normal object creation via the 'new' operator: this is - * taken care of by advising initialization() join points.
  2. + *
  3. Normal object creation via the '{@code new}' operator: this is + * taken care of by advising {@code initialization()} join points.
  4. *
  5. Object creation through deserialization: since no constructor is * invoked during deserialization, the aspect needs to advise a method that a * deserialization mechanism is going to invoke. Ideally, we should not * require user classes to implement any specific method. This implies that * we need to introduce the chosen method. We should also handle the cases * where the chosen method is already implemented in classes (in which case, - * the user's implementation for that method should take precedence over the + * the user's implementation for that method should take precedence over the * introduced implementation). There are a few choices for the chosen method: *
      *
    • readObject(ObjectOutputStream): Java requires that the method must be - * private

      . Since aspects cannot introduce a private member, + * {@code private}

      . Since aspects cannot introduce a private member, * while preserving its name, this option is ruled out.
    • - *
    • readResolve(): Java doesn't pose any restriction on an access specifier. - * Problem solved! There is one (minor) limitation of this approach in - * that if a user class already has this method, that method must be - * public. However, this shouldn't be a big burden, since - * use cases that need classes to implement readResolve() (custom enums, + *
    • readResolve(): Java doesn't pose any restriction on an access specifier. + * Problem solved! There is one (minor) limitation of this approach in + * that if a user class already has this method, that method must be + * {@code public}. However, this shouldn't be a big burden, since + * use cases that need classes to implement readResolve() (custom enums, * for example) are unlikely to be marked as @Configurable, and - * in any case asking to make that method public should not + * in any case asking to make that method {@code public} should not * pose any undue burden.
    • *
    - * The minor collaboration needed by user classes (i.e., that the - * implementation of readResolve(), if any, must be - * public) can be lifted as well if we were to use an - * experimental feature in AspectJ - the hasmethod() PCD.
  6. + * The minor collaboration needed by user classes (i.e., that the + * implementation of {@code readResolve()}, if any, must be + * {@code public}) can be lifted as well if we were to use an + * experimental feature in AspectJ - the {@code hasmethod()} PCD. *
*

@@ -63,7 +63,7 @@ import java.io.Serializable; * is to use a 'declare parents' statement another aspect (a subaspect of this aspect would be a logical choice) * that declares the classes that need to be configured by supplying the {@link ConfigurableObject} interface. *

- * + * * @author Ramnivas Laddad * @since 2.5.2 */ @@ -71,8 +71,8 @@ public abstract aspect AbstractInterfaceDrivenDependencyInjectionAspect extends /** * Select initialization join point as object construction */ - public pointcut beanConstruction(Object bean) : - initialization(ConfigurableObject+.new(..)) && this(bean); + public pointcut beanConstruction(Object bean) : + initialization(ConfigurableObject+.new(..)) && this(bean); /** * Select deserialization join point made available through ITDs for ConfigurableDeserializationSupport @@ -80,40 +80,40 @@ public abstract aspect AbstractInterfaceDrivenDependencyInjectionAspect extends public pointcut beanDeserialization(Object bean) : execution(Object ConfigurableDeserializationSupport+.readResolve()) && this(bean); - + public pointcut leastSpecificSuperTypeConstruction() : initialization(ConfigurableObject.new(..)); - - - + + + // Implementation to support re-injecting dependencies once an object is deserialized /** - * Declare any class implementing Serializable and ConfigurableObject as also implementing - * ConfigurableDeserializationSupport. This allows us to introduce the readResolve() + * Declare any class implementing Serializable and ConfigurableObject as also implementing + * ConfigurableDeserializationSupport. This allows us to introduce the readResolve() * method and select it with the beanDeserialization() pointcut. - * + * *

Here is an improved version that uses the hasmethod() pointcut and lifts * even the minor requirement on user classes: * *

declare parents: ConfigurableObject+ Serializable+
-	 *		            && !hasmethod(Object readResolve() throws ObjectStreamException) 
+	 *		            && !hasmethod(Object readResolve() throws ObjectStreamException)
 	 *		            implements ConfigurableDeserializationSupport;
 	 * 
*/ - declare parents: + declare parents: ConfigurableObject+ && Serializable+ implements ConfigurableDeserializationSupport; - + /** - * A marker interface to which the readResolve() is introduced. + * A marker interface to which the {@code readResolve()} is introduced. */ static interface ConfigurableDeserializationSupport extends Serializable { } - + /** - * Introduce the readResolve() method so that we can advise its + * Introduce the {@code readResolve()} method so that we can advise its * execution to configure the object. - * + * *

Note if a method with the same signature already exists in a - * Serializable class of ConfigurableObject type, + * {@code Serializable} class of ConfigurableObject type, * that implementation will take precedence (a good thing, since we are * merely interested in an opportunity to detect deserialization.) */ diff --git a/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AnnotationBeanConfigurerAspect.aj b/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AnnotationBeanConfigurerAspect.aj index 4cdc292dbbcc..2f1e91ccd23e 100644 --- a/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AnnotationBeanConfigurerAspect.aj +++ b/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AnnotationBeanConfigurerAspect.aj @@ -18,6 +18,7 @@ package org.springframework.beans.factory.aspectj; import java.io.Serializable; +import org.aspectj.lang.annotation.control.CodeGenerationHint; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; @@ -32,7 +33,7 @@ import org.springframework.beans.factory.wiring.BeanConfigurerSupport; * annotation to identify which classes need autowiring. * *

The bean name to look up will be taken from the - * @Configurable annotation if specified, otherwise the + * {@code @Configurable} annotation if specified, otherwise the * default bean name to look up will be the FQN of the class being configured. * * @author Rod Johnson @@ -43,7 +44,7 @@ import org.springframework.beans.factory.wiring.BeanConfigurerSupport; * @see org.springframework.beans.factory.annotation.Configurable * @see org.springframework.beans.factory.annotation.AnnotationBeanWiringInfoResolver */ -public aspect AnnotationBeanConfigurerAspect +public aspect AnnotationBeanConfigurerAspect extends AbstractInterfaceDrivenDependencyInjectionAspect implements BeanFactoryAware, InitializingBean, DisposableBean { @@ -51,7 +52,7 @@ public aspect AnnotationBeanConfigurerAspect public pointcut inConfigurableBean() : @this(Configurable); - public pointcut preConstructionConfiguration() : preConstructionConfigurationSupport(*); + public pointcut preConstructionConfiguration() : preConstructionConfigurationSupport(*); declare parents: @Configurable * implements ConfigurableObject; @@ -77,13 +78,14 @@ public aspect AnnotationBeanConfigurerAspect /* * An intermediary to match preConstructionConfiguration signature (that doesn't expose the annotation object) */ + @CodeGenerationHint(ifNameSuffix="bb0") private pointcut preConstructionConfigurationSupport(Configurable c) : @this(c) && if(c.preConstruction()); /* - * This declaration shouldn't be needed, + * This declaration shouldn't be needed, * except for an AspectJ bug (https://bugs.eclipse.org/bugs/show_bug.cgi?id=214559) */ - declare parents: @Configurable Serializable+ + declare parents: @Configurable Serializable+ implements ConfigurableDeserializationSupport; } diff --git a/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/GenericInterfaceDrivenDependencyInjectionAspect.aj b/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/GenericInterfaceDrivenDependencyInjectionAspect.aj index 03f446ca7893..b4a3d93d1d3a 100644 --- a/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/GenericInterfaceDrivenDependencyInjectionAspect.aj +++ b/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/GenericInterfaceDrivenDependencyInjectionAspect.aj @@ -17,24 +17,24 @@ package org.springframework.beans.factory.aspectj; /** * Generic-based dependency injection aspect. - *

- * This aspect allows users to implement efficient, type-safe dependency injection without + *

+ * This aspect allows users to implement efficient, type-safe dependency injection without * the use of the @Configurable annotation. - * - * The subaspect of this aspect doesn't need to include any AOP constructs. - * For example, here is a subaspect that configures the PricingStrategyClient objects. + * + * The subaspect of this aspect doesn't need to include any AOP constructs. + * For example, here is a subaspect that configures the {@code PricingStrategyClient} objects. *

- * aspect PricingStrategyDependencyInjectionAspect 
+ * aspect PricingStrategyDependencyInjectionAspect
  *        extends GenericInterfaceDrivenDependencyInjectionAspect {
  *     private PricingStrategy pricingStrategy;
- *     
- *     public void configure(PricingStrategyClient bean) { 
- *         bean.setPricingStrategy(pricingStrategy); 
+ *
+ *     public void configure(PricingStrategyClient bean) {
+ *         bean.setPricingStrategy(pricingStrategy);
+ *     }
+ *
+ *     public void setPricingStrategy(PricingStrategy pricingStrategy) {
+ *         this.pricingStrategy = pricingStrategy;
  *     }
- *     
- *     public void setPricingStrategy(PricingStrategy pricingStrategy) { 
- *         this.pricingStrategy = pricingStrategy; 
- *     } 
  * }
  * 
* @author Ramnivas Laddad @@ -42,13 +42,13 @@ package org.springframework.beans.factory.aspectj; */ public abstract aspect GenericInterfaceDrivenDependencyInjectionAspect extends AbstractInterfaceDrivenDependencyInjectionAspect { declare parents: I implements ConfigurableObject; - + public pointcut inConfigurableBean() : within(I+); - + public final void configureBean(Object bean) { configure((I)bean); } - - // Unfortunately, erasure used with generics won't allow to use the same named method + + // Unfortunately, erasure used with generics won't allow to use the same named method protected abstract void configure(I bean); } diff --git a/spring-aspects/src/main/java/org/springframework/cache/aspectj/AbstractCacheAspect.aj b/spring-aspects/src/main/java/org/springframework/cache/aspectj/AbstractCacheAspect.aj index dbe15b8fb3a2..ce68942d4058 100644 --- a/spring-aspects/src/main/java/org/springframework/cache/aspectj/AbstractCacheAspect.aj +++ b/spring-aspects/src/main/java/org/springframework/cache/aspectj/AbstractCacheAspect.aj @@ -62,7 +62,7 @@ public abstract aspect AbstractCacheAspect extends CacheAspectSupport { } }; - return execute(aspectJInvoker, thisJoinPoint.getTarget(), method, thisJoinPoint.getArgs()); + return execute(aspectJInvoker, thisJoinPoint.getTarget(), method, thisJoinPoint.getArgs()); } /** diff --git a/spring-aspects/src/main/java/org/springframework/mock/staticmock/AbstractMethodMockingControl.aj b/spring-aspects/src/main/java/org/springframework/mock/staticmock/AbstractMethodMockingControl.aj index 7ab3fcd0c34a..972a32485939 100644 --- a/spring-aspects/src/main/java/org/springframework/mock/staticmock/AbstractMethodMockingControl.aj +++ b/spring-aspects/src/main/java/org/springframework/mock/staticmock/AbstractMethodMockingControl.aj @@ -23,9 +23,9 @@ import java.util.List; /** * Abstract aspect to enable mocking of methods picked out by a pointcut. * Sub-aspects must define the mockStaticsTestMethod() pointcut to - * indicate call stacks when mocking should be triggered, and the + * indicate call stacks when mocking should be triggered, and the * methodToMock() pointcut to pick out a method invocations to mock. - * + * * @author Rod Johnson * @author Ramnivas Laddad */ @@ -42,7 +42,7 @@ public abstract aspect AbstractMethodMockingControl percflow(mockStaticsTestMeth // Represents a list of expected calls to static entity methods // Public to allow inserted code to access: is this normal?? public class Expectations { - + // Represents an expected call to a static entity method private class Call { private final String signature; @@ -50,21 +50,21 @@ public abstract aspect AbstractMethodMockingControl percflow(mockStaticsTestMeth private Object responseObject; // return value or throwable private CallResponse responseType = CallResponse.nothing; - + public Call(String name, Object[] args) { this.signature = name; this.args = args; } - + public boolean hasResponseSpecified() { return responseType != CallResponse.nothing; } - + public void setReturnVal(Object retVal) { this.responseObject = retVal; responseType = CallResponse.return_; } - + public void setThrow(Throwable throwable) { this.responseObject = throwable; responseType = CallResponse.throw_; @@ -89,7 +89,7 @@ public abstract aspect AbstractMethodMockingControl percflow(mockStaticsTestMeth } } } - + private List calls = new LinkedList(); // Calls already verified @@ -101,7 +101,7 @@ public abstract aspect AbstractMethodMockingControl percflow(mockStaticsTestMeth + " calls, received " + verified); } } - + /** * Validate the call and provide the expected return value * @param lastSig @@ -175,7 +175,7 @@ public abstract aspect AbstractMethodMockingControl percflow(mockStaticsTestMeth return expectations.respond(thisJoinPointStaticPart.toLongString(), thisJoinPoint.getArgs()); } } - + public void expectReturnInternal(Object retVal) { if (!recording) { throw new IllegalStateException("Not recording: Cannot set return value"); diff --git a/spring-aspects/src/main/java/org/springframework/mock/staticmock/AnnotationDrivenStaticEntityMockingControl.aj b/spring-aspects/src/main/java/org/springframework/mock/staticmock/AnnotationDrivenStaticEntityMockingControl.aj index 031978ca91f5..816f6a1e424a 100644 --- a/spring-aspects/src/main/java/org/springframework/mock/staticmock/AnnotationDrivenStaticEntityMockingControl.aj +++ b/spring-aspects/src/main/java/org/springframework/mock/staticmock/AnnotationDrivenStaticEntityMockingControl.aj @@ -18,16 +18,16 @@ package org.springframework.mock.staticmock; /** * Annotation-based aspect to use in test build to enable mocking static methods - * on JPA-annotated @Entity classes, as used by Roo for finders. + * on JPA-annotated {@code @Entity} classes, as used by Roo for finders. * - *

Mocking will occur in the call stack of any method in a class (typically a test class) - * that is annotated with the @MockStaticEntityMethods annotation. + *

Mocking will occur in the call stack of any method in a class (typically a test class) + * that is annotated with the @MockStaticEntityMethods annotation. * *

Also provides static methods to simplify the programming model for * entering playback mode and setting expected return values. * *

Usage: - *

    + *
      *
    1. Annotate a test class with @MockStaticEntityMethods. *
    2. In each test method, AnnotationDrivenStaticEntityMockingControl will begin in recording mode. * Invoke static methods on Entity classes, with each recording-mode invocation @@ -37,20 +37,20 @@ package org.springframework.mock.staticmock; *
    3. Call the code you wish to test that uses the static methods. Verification will * occur automatically. *
    - * + * * @author Rod Johnson * @author Ramnivas Laddad * @see MockStaticEntityMethods */ public aspect AnnotationDrivenStaticEntityMockingControl extends AbstractMethodMockingControl { - + /** * Stop recording mock calls and enter playback state */ public static void playback() { AnnotationDrivenStaticEntityMockingControl.aspectOf().playbackInternal(); } - + public static void expectReturn(Object retVal) { AnnotationDrivenStaticEntityMockingControl.aspectOf().expectReturnInternal(retVal); } diff --git a/spring-aspects/src/main/java/org/springframework/orm/jpa/aspectj/JpaExceptionTranslatorAspect.aj b/spring-aspects/src/main/java/org/springframework/orm/jpa/aspectj/JpaExceptionTranslatorAspect.aj index 6ff44249d082..131c82beefdc 100644 --- a/spring-aspects/src/main/java/org/springframework/orm/jpa/aspectj/JpaExceptionTranslatorAspect.aj +++ b/spring-aspects/src/main/java/org/springframework/orm/jpa/aspectj/JpaExceptionTranslatorAspect.aj @@ -1,3 +1,19 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.springframework.orm.jpa.aspectj; import javax.persistence.EntityManager; @@ -9,14 +25,17 @@ import org.springframework.dao.DataAccessException; import org.springframework.orm.jpa.EntityManagerFactoryUtils; public aspect JpaExceptionTranslatorAspect { - pointcut entityManagerCall(): call(* EntityManager.*(..)) || call(* EntityManagerFactory.*(..)) || call(* EntityTransaction.*(..)) || call(* Query.*(..)); - - after() throwing(RuntimeException re): entityManagerCall() { - DataAccessException dex = EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(re); - if (dex != null) { - throw dex; - } else { - throw re; - } - } + pointcut entityManagerCall(): + call(* EntityManager.*(..)) || call(* EntityManagerFactory.*(..)) || + call(* EntityTransaction.*(..)) || call(* Query.*(..)); + + after() throwing(RuntimeException re): entityManagerCall() { + DataAccessException dex = EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(re); + if (dex != null) { + throw dex; + } + else { + throw re; + } + } } \ No newline at end of file diff --git a/spring-aspects/src/main/java/org/springframework/scheduling/aspectj/AbstractAsyncExecutionAspect.aj b/spring-aspects/src/main/java/org/springframework/scheduling/aspectj/AbstractAsyncExecutionAspect.aj index 6cb3ad60e398..660b35c13614 100644 --- a/spring-aspects/src/main/java/org/springframework/scheduling/aspectj/AbstractAsyncExecutionAspect.aj +++ b/spring-aspects/src/main/java/org/springframework/scheduling/aspectj/AbstractAsyncExecutionAspect.aj @@ -28,7 +28,7 @@ import org.springframework.core.task.AsyncTaskExecutor; /** * Abstract aspect that routes selected methods asynchronously. * - *

    This aspect needs to be injected with an implementation of + *

    This aspect needs to be injected with an implementation of * {@link Executor} to activate it for a specific thread pool. * Otherwise it will simply delegate all calls synchronously. * diff --git a/spring-aspects/src/main/java/org/springframework/transaction/aspectj/AbstractTransactionAspect.aj b/spring-aspects/src/main/java/org/springframework/transaction/aspectj/AbstractTransactionAspect.aj index 54bd4ef7464f..f9c70661ae1f 100644 --- a/spring-aspects/src/main/java/org/springframework/transaction/aspectj/AbstractTransactionAspect.aj +++ b/spring-aspects/src/main/java/org/springframework/transaction/aspectj/AbstractTransactionAspect.aj @@ -16,8 +16,6 @@ package org.springframework.transaction.aspectj; -import java.lang.reflect.Method; - import org.aspectj.lang.annotation.SuppressAjWarnings; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.transaction.interceptor.TransactionAspectSupport; @@ -25,7 +23,7 @@ import org.springframework.transaction.interceptor.TransactionAttributeSource; /** * Abstract superaspect for AspectJ transaction aspects. Concrete - * subaspects will implement the transactionalMethodExecution() + * subaspects will implement the {@code transactionalMethodExecution()} * pointcut using a strategy such as Java 5 annotations. * *

    Suitable for use inside or outside the Spring IoC container. @@ -42,45 +40,42 @@ import org.springframework.transaction.interceptor.TransactionAttributeSource; * * @author Rod Johnson * @author Ramnivas Laddad + * @author Juergen Hoeller * @since 2.0 */ public abstract aspect AbstractTransactionAspect extends TransactionAspectSupport { /** - * Construct object using the given transaction metadata retrieval strategy. + * Construct the aspect using the given transaction metadata retrieval strategy. * @param tas TransactionAttributeSource implementation, retrieving Spring - * transaction metadata for each joinpoint. Write the subclass to pass in null - * if it's intended to be configured by Setter Injection. + * transaction metadata for each joinpoint. Implement the subclass to pass in + * {@code null} if it is intended to be configured through Setter Injection. */ protected AbstractTransactionAspect(TransactionAttributeSource tas) { setTransactionAttributeSource(tas); } @SuppressAjWarnings("adviceDidNotMatch") - before(Object txObject) : transactionalMethodExecution(txObject) { + Object around(final Object txObject): transactionalMethodExecution(txObject) { MethodSignature methodSignature = (MethodSignature) thisJoinPoint.getSignature(); - Method method = methodSignature.getMethod(); - createTransactionIfNecessary(method, txObject.getClass()); - } - - @SuppressAjWarnings("adviceDidNotMatch") - after(Object txObject) throwing(Throwable t) : transactionalMethodExecution(txObject) { + // Adapt to TransactionAspectSupport's invokeWithinTransaction... try { - completeTransactionAfterThrowing(TransactionAspectSupport.currentTransactionInfo(), t); + return invokeWithinTransaction(methodSignature.getMethod(), txObject.getClass(), new InvocationCallback() { + public Object proceedWithInvocation() throws Throwable { + return proceed(txObject); + } + }); } - catch (Throwable t2) { - logger.error("Failed to close transaction after throwing in a transactional method", t2); + catch (RuntimeException ex) { + throw ex; + } + catch (Error err) { + throw err; + } + catch (Throwable thr) { + Rethrower.rethrow(thr); + throw new IllegalStateException("Should never get here", thr); } - } - - @SuppressAjWarnings("adviceDidNotMatch") - after(Object txObject) returning() : transactionalMethodExecution(txObject) { - commitTransactionAfterReturning(TransactionAspectSupport.currentTransactionInfo()); - } - - @SuppressAjWarnings("adviceDidNotMatch") - after(Object txObject) : transactionalMethodExecution(txObject) { - cleanupTransactionInfo(TransactionAspectSupport.currentTransactionInfo()); } /** @@ -90,4 +85,22 @@ public abstract aspect AbstractTransactionAspect extends TransactionAspectSuppor */ protected abstract pointcut transactionalMethodExecution(Object txObject); + + /** + * Ugly but safe workaround: We need to be able to propagate checked exceptions, + * despite AspectJ around advice supporting specifically declared exceptions only. + */ + private static class Rethrower { + + public static void rethrow(final Throwable exception) { + class CheckedExceptionRethrower { + @SuppressWarnings("unchecked") + private void rethrow(Throwable exception) throws T { + throw (T) exception; + } + } + new CheckedExceptionRethrower().rethrow(exception); + } + } + } diff --git a/spring-aspects/src/main/java/org/springframework/transaction/aspectj/AnnotationTransactionAspect.aj b/spring-aspects/src/main/java/org/springframework/transaction/aspectj/AnnotationTransactionAspect.aj index 2ea8f9e3f584..70a82b5f4eef 100644 --- a/spring-aspects/src/main/java/org/springframework/transaction/aspectj/AnnotationTransactionAspect.aj +++ b/spring-aspects/src/main/java/org/springframework/transaction/aspectj/AnnotationTransactionAspect.aj @@ -21,17 +21,17 @@ import org.springframework.transaction.annotation.Transactional; /** * Concrete AspectJ transaction aspect using Spring's @Transactional annotation. - * + * *

    When using this aspect, you must annotate the implementation class * (and/or methods within that class), not the interface (if any) that - * the class implements. AspectJ follows Java's rule that annotations on + * the class implements. AspectJ follows Java's rule that annotations on * interfaces are not inherited. * *

    An @Transactional annotation on a class specifies the default transaction * semantics for the execution of any public operation in the class. * *

    An @Transactional annotation on a method within the class overrides the - * default transaction semantics given by the class annotation (if present). + * default transaction semantics given by the class annotation (if present). * Any method may be annotated (regardless of visibility). * Annotating non-public methods directly is the only way * to get transaction demarcation for the execution of such operations. @@ -49,16 +49,14 @@ public aspect AnnotationTransactionAspect extends AbstractTransactionAspect { } /** - * Matches the execution of any public method in a type with the - * Transactional annotation, or any subtype of a type with the - * Transactional annotation. + * Matches the execution of any public method in a type with the Transactional + * annotation, or any subtype of a type with the Transactional annotation. */ private pointcut executionOfAnyPublicMethodInAtTransactionalType() : execution(public * ((@Transactional *)+).*(..)) && within(@Transactional *); /** - * Matches the execution of any method with the - * Transactional annotation. + * Matches the execution of any method with the Transactional annotation. */ private pointcut executionOfTransactionalMethod() : execution(@Transactional * *(..)); @@ -66,7 +64,7 @@ public aspect AnnotationTransactionAspect extends AbstractTransactionAspect { /** * Definition of pointcut from super aspect - matched join points * will have Spring transaction management applied. - */ + */ protected pointcut transactionalMethodExecution(Object txObject) : (executionOfAnyPublicMethodInAtTransactionalType() || executionOfTransactionalMethod() ) diff --git a/spring-aspects/src/test/java/org/springframework/transaction/aspectj/TransactionAspectTests.java b/spring-aspects/src/test/java/org/springframework/transaction/aspectj/TransactionAspectTests.java index 70892e39493d..b69e861933ac 100644 --- a/spring-aspects/src/test/java/org/springframework/transaction/aspectj/TransactionAspectTests.java +++ b/spring-aspects/src/test/java/org/springframework/transaction/aspectj/TransactionAspectTests.java @@ -16,19 +16,18 @@ package org.springframework.transaction.aspectj; -import java.lang.reflect.Method; - -import junit.framework.AssertionFailedError; - import org.springframework.test.AbstractDependencyInjectionSpringContextTests; import org.springframework.tests.transaction.CallCountingTransactionManager; import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource; import org.springframework.transaction.interceptor.TransactionAspectSupport; import org.springframework.transaction.interceptor.TransactionAttribute; +import java.lang.reflect.Method; + /** * @author Rod Johnson * @author Ramnivas Laddad + * @author Juergen Hoeller */ public class TransactionAspectTests extends AbstractDependencyInjectionSpringContextTests { @@ -123,50 +122,63 @@ public void testNotTransactional() throws Throwable { public void testDefaultCommitOnAnnotatedClass() throws Throwable { - testRollback(new TransactionOperationCallback() { - public Object performTransactionalOperation() throws Throwable { - return annotationOnlyOnClassWithNoInterface.echo(new Exception()); - } - }, false); + final Exception ex = new Exception(); + try { + testRollback(new TransactionOperationCallback() { + public Object performTransactionalOperation() throws Throwable { + return annotationOnlyOnClassWithNoInterface.echo(ex); + } + }, false); + fail("Should have thrown Exception"); + } + catch (Exception ex2) { + assertSame(ex, ex2); + } } public void testDefaultRollbackOnAnnotatedClass() throws Throwable { - testRollback(new TransactionOperationCallback() { - public Object performTransactionalOperation() throws Throwable { - return annotationOnlyOnClassWithNoInterface.echo(new RuntimeException()); - } - }, true); + final RuntimeException ex = new RuntimeException(); + try { + testRollback(new TransactionOperationCallback() { + public Object performTransactionalOperation() throws Throwable { + return annotationOnlyOnClassWithNoInterface.echo(ex); + } + }, true); + fail("Should have thrown RuntimeException"); + } + catch (RuntimeException ex2) { + assertSame(ex, ex2); + } } - public static class SubclassOfClassWithTransactionalAnnotation extends TransactionalAnnotationOnlyOnClassWithNoInterface { - } - public void testDefaultCommitOnSubclassOfAnnotatedClass() throws Throwable { - testRollback(new TransactionOperationCallback() { - public Object performTransactionalOperation() throws Throwable { - return new SubclassOfClassWithTransactionalAnnotation().echo(new Exception()); - } - }, false); - } - - public static class SubclassOfClassWithTransactionalMethodAnnotation extends MethodAnnotationOnClassWithNoInterface { + final Exception ex = new Exception(); + try { + testRollback(new TransactionOperationCallback() { + public Object performTransactionalOperation() throws Throwable { + return new SubclassOfClassWithTransactionalAnnotation().echo(ex); + } + }, false); + fail("Should have thrown Exception"); + } + catch (Exception ex2) { + assertSame(ex, ex2); + } } public void testDefaultCommitOnSubclassOfClassWithTransactionalMethodAnnotated() throws Throwable { - testRollback(new TransactionOperationCallback() { - public Object performTransactionalOperation() throws Throwable { - return new SubclassOfClassWithTransactionalMethodAnnotation().echo(new Exception()); - } - }, false); - } - - public static class ImplementsAnnotatedInterface implements ITransactional { - public Object echo(Throwable t) throws Throwable { - if (t != null) { - throw t; - } - return t; + final Exception ex = new Exception(); + try { + testRollback(new TransactionOperationCallback() { + public Object performTransactionalOperation() throws Throwable { + return new SubclassOfClassWithTransactionalMethodAnnotation().echo(ex); + } + }, false); + fail("Should have thrown Exception"); + } + catch (Exception ex2) { + assertSame(ex, ex2); } } @@ -221,18 +233,12 @@ protected void testRollback(TransactionOperationCallback toc, boolean rollback) assertEquals(0, txManager.begun); try { toc.performTransactionalOperation(); - assertEquals(1, txManager.commits); - } - catch (Throwable caught) { - if (caught instanceof AssertionFailedError) { - return; - } } - - if (rollback) { - assertEquals(1, txManager.rollbacks); + finally { + assertEquals(1, txManager.begun); + assertEquals(rollback ? 0 : 1, txManager.commits); + assertEquals(rollback ? 1 : 0, txManager.rollbacks); } - assertEquals(1, txManager.begun); } protected void testNotTransactional(TransactionOperationCallback toc, Throwable expected) throws Throwable { @@ -258,4 +264,23 @@ private interface TransactionOperationCallback { Object performTransactionalOperation() throws Throwable; } + + public static class SubclassOfClassWithTransactionalAnnotation extends TransactionalAnnotationOnlyOnClassWithNoInterface { + } + + + public static class SubclassOfClassWithTransactionalMethodAnnotation extends MethodAnnotationOnClassWithNoInterface { + } + + + public static class ImplementsAnnotatedInterface implements ITransactional { + + public Object echo(Throwable t) throws Throwable { + if (t != null) { + throw t; + } + return t; + } + } + } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index 9d11eeb520c7..09faf0c3b256 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -572,29 +572,30 @@ else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { } @Override - protected Class predictBeanType(String beanName, RootBeanDefinition mbd, Class... typesToMatch) { - Class beanClass; - if (mbd.getFactoryMethodName() != null) { - beanClass = getTypeForFactoryMethod(beanName, mbd, typesToMatch); - } - else { - beanClass = resolveBeanClass(mbd, beanName, typesToMatch); + protected Class predictBeanType(String beanName, RootBeanDefinition mbd, Class... typesToMatch) { + Class targetType = mbd.getTargetType(); + if (targetType == null) { + targetType = (mbd.getFactoryMethodName() != null ? getTypeForFactoryMethod(beanName, mbd, typesToMatch) : + resolveBeanClass(mbd, beanName, typesToMatch)); + if (ObjectUtils.isEmpty(typesToMatch) || getTempClassLoader() == null) { + mbd.setTargetType(targetType); + } } // Apply SmartInstantiationAwareBeanPostProcessors to predict the // eventual type after a before-instantiation shortcut. - if (beanClass != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { + if (targetType != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; - Class predictedType = ibp.predictBeanType(beanClass, beanName); - if (predictedType != null && (typesToMatch.length > 1 || - !FactoryBean.class.equals(typesToMatch[0]) || FactoryBean.class.isAssignableFrom(predictedType))) { - return predictedType; + Class predicted = ibp.predictBeanType(targetType, beanName); + if (predicted != null && (typesToMatch.length != 1 || !FactoryBean.class.equals(typesToMatch[0]) || + FactoryBean.class.isAssignableFrom(predicted))) { + return predicted; } } } } - return beanClass; + return targetType; } /** @@ -611,8 +612,8 @@ protected Class predictBeanType(String beanName, RootBeanDefinition mbd, Class.. * @return the type for the bean if determinable, or {@code null} else * @see #createBean */ - protected Class getTypeForFactoryMethod(String beanName, RootBeanDefinition mbd, Class[] typesToMatch) { - Class factoryClass; + protected Class getTypeForFactoryMethod(String beanName, RootBeanDefinition mbd, Class[] typesToMatch) { + Class factoryClass; boolean isStatic = true; String factoryBeanName = mbd.getFactoryBeanName(); @@ -686,7 +687,7 @@ class Holder { Class value = null; } if (factoryBeanName != null && factoryMethodName != null) { // Try to obtain the FactoryBean's object type without instantiating it at all. BeanDefinition fbDef = getBeanDefinition(factoryBeanName); - if (fbDef instanceof AbstractBeanDefinition) { + if (fbDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) fbDef).hasBeanClass()) { Class fbClass = ((AbstractBeanDefinition) fbDef).getBeanClass(); if (ClassUtils.isCglibProxyClass(fbClass)) { // CGLIB subclass methods hide generic parameters. look at the superclass. diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/PropertiesBeanDefinitionReader.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/PropertiesBeanDefinitionReader.java index 7b7822f0df0e..d51dc55fa723 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/PropertiesBeanDefinitionReader.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/PropertiesBeanDefinitionReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -165,7 +165,7 @@ public PropertiesBeanDefinitionReader(BeanDefinitionRegistry registry) { * class can still override this. *

    Strictly speaking, the rule that a default parent setting does * not apply to a bean definition that carries a class is there for - * backwards compatiblity reasons. It still matches the typical use case. + * backwards compatibility reasons. It still matches the typical use case. */ public void setDefaultParentBean(String defaultParentBean) { this.defaultParentBean = defaultParentBean; diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java index 7eaba17a5468..64d0555d4913 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -61,8 +61,12 @@ public class RootBeanDefinition extends AbstractBeanDefinition { boolean allowCaching = true; + private volatile Class targetType; + boolean isFactoryMethodUnique = false; + final Object constructorArgumentLock = new Object(); + /** Package-visible field for caching the resolved constructor or factory method */ Object resolvedConstructorOrFactoryMethod; @@ -75,15 +79,13 @@ public class RootBeanDefinition extends AbstractBeanDefinition { /** Package-visible field for caching partly prepared constructor arguments */ Object[] preparedConstructorArguments; - final Object constructorArgumentLock = new Object(); - - /** Package-visible field that indicates a before-instantiation post-processor having kicked in */ - volatile Boolean beforeInstantiationResolved; + final Object postProcessingLock = new Object(); /** Package-visible field that indicates MergedBeanDefinitionPostProcessor having been applied */ boolean postProcessed = false; - final Object postProcessingLock = new Object(); + /** Package-visible field that indicates a before-instantiation post-processor having kicked in */ + volatile Boolean beforeInstantiationResolved; /** @@ -236,6 +238,8 @@ public RootBeanDefinition(RootBeanDefinition original) { if (original instanceof RootBeanDefinition) { RootBeanDefinition originalRbd = (RootBeanDefinition) original; this.decoratedDefinition = originalRbd.decoratedDefinition; + this.allowCaching = originalRbd.allowCaching; + this.targetType = originalRbd.targetType; this.isFactoryMethodUnique = originalRbd.isFactoryMethodUnique; } } @@ -251,6 +255,21 @@ public void setParentName(String parentName) { } } + /** + * Specify the target type of this bean definition, if known in advance. + */ + public void setTargetType(Class targetType) { + this.targetType = targetType; + } + + /** + * Return the target type of this bean definition, if known + * (either specified in advance or resolved on first instantiation). + */ + public Class getTargetType() { + return this.targetType; + } + /** * Specify a factory method name that refers to a non-overloaded method. */ diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/wiring/BeanConfigurerSupport.java b/spring-beans/src/main/java/org/springframework/beans/factory/wiring/BeanConfigurerSupport.java index 3413b457c061..81fa4242b2f6 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/wiring/BeanConfigurerSupport.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/wiring/BeanConfigurerSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,14 +30,13 @@ import org.springframework.util.ClassUtils; /** - * Convenient base class for configurers that can perform Dependency Injection + * Convenient base class for bean configurers that can perform Dependency Injection * on objects (however they may be created). Typically subclassed by AspectJ aspects. * *

    Subclasses may also need a custom metadata resolution strategy, in the - * {@link BeanWiringInfoResolver} interface. The default implementation looks - * for a bean with the same name as the fully-qualified class name. (This is - * the default name of the bean in a Spring XML file if the '{@code id}' - * attribute is not used.) + * {@link BeanWiringInfoResolver} interface. The default implementation looks for + * a bean with the same name as the fully-qualified class name. (This is the default + * name of the bean in a Spring XML file if the '{@code id}' attribute is not used.) * @author Rob Harrop * @author Rod Johnson @@ -113,8 +112,7 @@ public void destroy() { /** * Configure the bean instance. *

    Subclasses can override this to provide custom configuration logic. - * Typically called by an aspect, for all bean instances matched by a - * pointcut. + * Typically called by an aspect, for all bean instances matched by a pointcut. * @param beanInstance the bean instance to configure (must not be {@code null}) */ public void configureBean(Object beanInstance) { diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java index 4045578c8c44..94f6376ce18f 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java @@ -591,7 +591,6 @@ public void testPossibleMatches() { fail("Should throw exception on invalid property"); } catch (BeanCreationException ex) { - ex.printStackTrace(); assertTrue(ex.getCause() instanceof NotWritablePropertyException); NotWritablePropertyException cause = (NotWritablePropertyException) ex.getCause(); // expected diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/parsing/CustomProblemReporterTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/parsing/CustomProblemReporterTests.java index cc009917fd36..550e87375b23 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/parsing/CustomProblemReporterTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/parsing/CustomProblemReporterTests.java @@ -29,7 +29,6 @@ import org.springframework.core.io.Resource; import org.springframework.tests.sample.beans.TestBean; - /** * @author Rob Harrop * @author Chris Beams @@ -78,7 +77,6 @@ public void fatal(Problem problem) { @Override public void error(Problem problem) { - System.out.println(problem); this.errors.add(problem); } @@ -88,7 +86,6 @@ public Problem[] getErrors() { @Override public void warning(Problem problem) { - System.out.println(problem); this.warnings.add(problem); } } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/security/CallbacksSecurityTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/security/CallbacksSecurityTests.java index c8c6595e455a..d0439fa93eff 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/security/CallbacksSecurityTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/security/CallbacksSecurityTests.java @@ -16,12 +16,6 @@ package org.springframework.beans.factory.support.security; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - import java.lang.reflect.Method; import java.net.URL; import java.security.AccessControlContext; @@ -39,8 +33,8 @@ import javax.security.auth.Subject; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; + import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanCreationException; @@ -60,6 +54,8 @@ import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; +import static org.junit.Assert.*; + /** * Security test case. Checks whether the container uses its privileges for its * internal work but does not leak them when touching/calling user code. @@ -456,9 +452,6 @@ public void testConstructor() throws Exception { } @Test - @Ignore("passes under Eclipse, but fails under Gradle with https://gist.github.com/1664133") - // TODO [SPR-10074] passes under Eclipse, but fails under Gradle with - // https://gist.github.com/1664133 public void testContainerPrivileges() throws Exception { AccessControlContext acc = provider.getAccessControlContext(); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/security/support/ConstructorBean.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/security/support/ConstructorBean.java index 0b65929fdd31..a68028c5921d 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/security/support/ConstructorBean.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/security/support/ConstructorBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.beans.factory.support.security.support; /** @@ -25,6 +26,5 @@ public ConstructorBean() { } public ConstructorBean(Object obj) { - System.out.println("Received object " + obj); } } diff --git a/spring-beans/src/test/resources/log4j.xml b/spring-beans/src/test/resources/log4j.xml index 37f573d2aef2..6f012e992400 100644 --- a/spring-beans/src/test/resources/log4j.xml +++ b/spring-beans/src/test/resources/log4j.xml @@ -11,21 +11,13 @@ - - - - - - - - - + - + diff --git a/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheFactoryBean.java b/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheFactoryBean.java index fc2ad0002229..dcec4047eeec 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheFactoryBean.java +++ b/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,9 +40,9 @@ import org.springframework.util.Assert; /** - * {@link FactoryBean} that creates a named EHCache {@link net.sf.ehcache.Cache} instance + * {@link FactoryBean} that creates a named EhCache {@link net.sf.ehcache.Cache} instance * (or a decorator that implements the {@link net.sf.ehcache.Ehcache} interface), - * representing a cache region within an EHCache {@link net.sf.ehcache.CacheManager}. + * representing a cache region within an EhCache {@link net.sf.ehcache.CacheManager}. * *

    If the specified named cache is not configured in the cache configuration descriptor, * this FactoryBean will construct an instance of a Cache with the provided name and the @@ -52,7 +52,8 @@ *

    Note: If the named Cache instance is found, the properties will be ignored and the * Cache instance will be retrieved from the CacheManager. * - *

    Note: As of Spring 3.0, Spring's EHCache support requires EHCache 1.3 or higher. + *

    Note: As of Spring 3.0, Spring's EhCache support requires EhCache 1.3 or higher. + * As of Spring 3.2, we recommend using EhCache 2.1 or higher. * @author Dmitriy Kopylenko * @author Juergen Hoeller @@ -117,7 +118,7 @@ public class EhCacheFactoryBean implements FactoryBean, BeanNameAware, * properly handle the shutdown of the CacheManager: Set up a separate * EhCacheManagerFactoryBean and pass a reference to this bean property. *

    A separate EhCacheManagerFactoryBean is also necessary for loading - * EHCache configuration from a non-default config location. + * EhCache configuration from a non-default config location. * @see EhCacheManagerFactoryBean * @see net.sf.ehcache.CacheManager#getInstance */ @@ -152,7 +153,7 @@ public void setMaxElementsOnDisk(int maxElementsOnDisk) { /** * Set the memory style eviction policy for this cache. *

    Supported values are "LRU", "LFU" and "FIFO", according to the - * constants defined in EHCache's MemoryStoreEvictionPolicy class. + * constants defined in EhCache's MemoryStoreEvictionPolicy class. * Default is "LRU". */ public void setMemoryStoreEvictionPolicy(MemoryStoreEvictionPolicy memoryStoreEvictionPolicy) { @@ -239,9 +240,9 @@ public void setBlocking(boolean blocking) { } /** - * Set an EHCache {@link net.sf.ehcache.constructs.blocking.CacheEntryFactory} + * Set an EhCache {@link net.sf.ehcache.constructs.blocking.CacheEntryFactory} * to use for a self-populating cache. If such a factory is specified, - * the cache will be decorated with EHCache's + * the cache will be decorated with EhCache's * {@link net.sf.ehcache.constructs.blocking.SelfPopulatingCache}. *

    The specified factory can be of type * {@link net.sf.ehcache.constructs.blocking.UpdatingCacheEntryFactory}, @@ -257,7 +258,7 @@ public void setCacheEntryFactory(CacheEntryFactory cacheEntryFactory) { } /** - * Set an EHCache {@link net.sf.ehcache.bootstrap.BootstrapCacheLoader} + * Set an EhCache {@link net.sf.ehcache.bootstrap.BootstrapCacheLoader} * for this cache, if any. */ public void setBootstrapCacheLoader(BootstrapCacheLoader bootstrapCacheLoader) { @@ -265,7 +266,7 @@ public void setBootstrapCacheLoader(BootstrapCacheLoader bootstrapCacheLoader) { } /** - * Specify EHCache {@link net.sf.ehcache.event.CacheEventListener cache event listeners} + * Specify EhCache {@link net.sf.ehcache.event.CacheEventListener cache event listeners} * to registered with this cache. */ public void setCacheEventListeners(Set cacheEventListeners) { @@ -305,7 +306,7 @@ public void afterPropertiesSet() throws CacheException, IOException { // If no CacheManager given, fetch the default. if (this.cacheManager == null) { if (logger.isDebugEnabled()) { - logger.debug("Using default EHCache CacheManager for cache region '" + this.cacheName + "'"); + logger.debug("Using default EhCache CacheManager for cache region '" + this.cacheName + "'"); } this.cacheManager = CacheManager.getInstance(); } @@ -320,13 +321,13 @@ public void afterPropertiesSet() throws CacheException, IOException { Ehcache rawCache; if (this.cacheManager.cacheExists(this.cacheName)) { if (logger.isDebugEnabled()) { - logger.debug("Using existing EHCache cache region '" + this.cacheName + "'"); + logger.debug("Using existing EhCache cache region '" + this.cacheName + "'"); } rawCache = this.cacheManager.getEhcache(this.cacheName); } else { if (logger.isDebugEnabled()) { - logger.debug("Creating new EHCache cache region '" + this.cacheName + "'"); + logger.debug("Creating new EhCache cache region '" + this.cacheName + "'"); } rawCache = createCache(); this.cacheManager.addCache(rawCache); @@ -359,7 +360,7 @@ public void afterPropertiesSet() throws CacheException, IOException { * Create a raw Cache object based on the configuration of this FactoryBean. */ protected Cache createCache() { - // Only call EHCache 1.6 constructor if actually necessary (for compatibility with EHCache 1.3+) + // Only call EhCache 1.6 constructor if actually necessary (for compatibility with EhCache 1.3+) return (!this.clearOnFlush) ? new Cache(this.cacheName, this.maxElementsInMemory, this.memoryStoreEvictionPolicy, this.overflowToDisk, null, this.eternal, this.timeToLive, this.timeToIdle, diff --git a/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheManagerFactoryBean.java b/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheManagerFactoryBean.java index a170eb84f85d..a94c1fe2cb17 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheManagerFactoryBean.java +++ b/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheManagerFactoryBean.java @@ -35,19 +35,20 @@ import org.springframework.util.ReflectionUtils; /** - * {@link FactoryBean} that exposes an EHCache {@link net.sf.ehcache.CacheManager} + * {@link FactoryBean} that exposes an EhCache {@link net.sf.ehcache.CacheManager} * instance (independent or shared), configured from a specified config location. * *

    If no config location is specified, a CacheManager will be configured from - * "ehcache.xml" in the root of the class path (that is, default EHCache initialization - * - as defined in the EHCache docs - will apply). + * "ehcache.xml" in the root of the class path (that is, default EhCache initialization + * - as defined in the EhCache docs - will apply). * *

    Setting up a separate EhCacheManagerFactoryBean is also advisable when using * EhCacheFactoryBean, as it provides a (by default) independent CacheManager instance * and cares for proper shutdown of the CacheManager. EhCacheManagerFactoryBean is - * also necessary for loading EHCache configuration from a non-default config location. + * also necessary for loading EhCache configuration from a non-default config location. * - *

    Note: As of Spring 3.0, Spring's EHCache support requires EHCache 1.3 or higher. + *

    Note: As of Spring 3.0, Spring's EhCache support requires EhCache 1.3 or higher. + * As of Spring 3.2, we recommend using EhCache 2.1 or higher. * * @author Dmitriy Kopylenko * @author Juergen Hoeller @@ -59,7 +60,7 @@ */ public class EhCacheManagerFactoryBean implements FactoryBean, InitializingBean, DisposableBean { - // Check whether EHCache 2.1+ CacheManager.create(Configuration) method is available... + // Check whether EhCache 2.1+ CacheManager.create(Configuration) method is available... private static final Method createWithConfiguration = ClassUtils.getMethodIfAvailable(CacheManager.class, "create", Configuration.class); @@ -75,9 +76,9 @@ public class EhCacheManagerFactoryBean implements FactoryBean, Ini /** - * Set the location of the EHCache config file. A typical value is "/WEB-INF/ehcache.xml". + * Set the location of the EhCache config file. A typical value is "/WEB-INF/ehcache.xml". *

    Default is "ehcache.xml" in the root of the class path, or if not found, - * "ehcache-failsafe.xml" in the EHCache jar (default EHCache initialization). + * "ehcache-failsafe.xml" in the EhCache jar (default EhCache initialization). * @see net.sf.ehcache.CacheManager#create(java.io.InputStream) * @see net.sf.ehcache.CacheManager#CacheManager(java.io.InputStream) */ @@ -86,7 +87,7 @@ public void setConfigLocation(Resource configLocation) { } /** - * Set whether the EHCache CacheManager should be shared (as a singleton at the VM level) + * Set whether the EhCache CacheManager should be shared (as a singleton at the VM level) * or independent (typically local within the application). Default is "false", creating * an independent instance. * @see net.sf.ehcache.CacheManager#create() @@ -97,7 +98,7 @@ public void setShared(boolean shared) { } /** - * Set the name of the EHCache CacheManager (if a specific name is desired). + * Set the name of the EhCache CacheManager (if a specific name is desired). * @see net.sf.ehcache.CacheManager#setName(String) */ public void setCacheManagerName(String cacheManagerName) { @@ -106,14 +107,14 @@ public void setCacheManagerName(String cacheManagerName) { public void afterPropertiesSet() throws IOException, CacheException { - logger.info("Initializing EHCache CacheManager"); + logger.info("Initializing EhCache CacheManager"); InputStream is = (this.configLocation != null ? this.configLocation.getInputStream() : null); try { - // A bit convoluted for EHCache 1.x/2.0 compatibility. - // To be much simpler once we require EHCache 2.1+ + // A bit convoluted for EhCache 1.x/2.0 compatibility. + // To be much simpler once we require EhCache 2.1+ if (this.cacheManagerName != null) { if (this.shared && createWithConfiguration == null) { - // No CacheManager.create(Configuration) method available before EHCache 2.1; + // No CacheManager.create(Configuration) method available before EhCache 2.1; // can only set CacheManager name after creation. this.cacheManager = (is != null ? CacheManager.create(is) : CacheManager.create()); this.cacheManager.setName(this.cacheManagerName); @@ -160,7 +161,7 @@ public boolean isSingleton() { public void destroy() { - logger.info("Shutting down EHCache CacheManager"); + logger.info("Shutting down EhCache CacheManager"); this.cacheManager.shutdown(); } diff --git a/spring-context-support/src/test/java/org/springframework/cache/ehcache/EhCacheSupportTests.java b/spring-context-support/src/test/java/org/springframework/cache/ehcache/EhCacheSupportTests.java index fcfe22164250..6e8efcd81656 100644 --- a/spring-context-support/src/test/java/org/springframework/cache/ehcache/EhCacheSupportTests.java +++ b/spring-context-support/src/test/java/org/springframework/cache/ehcache/EhCacheSupportTests.java @@ -133,7 +133,6 @@ private void doTestEhCacheFactoryBean(boolean useCacheManagerFb) throws Exceptio cacheFb.setBeanName("undefinedCache2"); cacheFb.setMaxElementsInMemory(5); cacheFb.setOverflowToDisk(false); - cacheFb.setEternal(true); cacheFb.setTimeToLive(8); cacheFb.setTimeToIdle(7); cacheFb.setDiskPersistent(true); @@ -145,7 +144,6 @@ private void doTestEhCacheFactoryBean(boolean useCacheManagerFb) throws Exceptio assertEquals("undefinedCache2", cache.getName()); assertTrue("overridden maxElements is correct", config.getMaxElementsInMemory() == 5); assertFalse("overridden overflowToDisk is correct", config.isOverflowToDisk()); - assertTrue("overridden eternal is correct", config.isEternal()); assertTrue("default timeToLive is correct", config.getTimeToLiveSeconds() == 8); assertTrue("default timeToIdle is correct", config.getTimeToIdleSeconds() == 7); assertTrue("overridden diskPersistent is correct", config.isDiskPersistent()); diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/Caching.java b/spring-context/src/main/java/org/springframework/cache/annotation/Caching.java index 7d3e77d3192c..6bad879b76f8 100644 --- a/spring-context/src/main/java/org/springframework/cache/annotation/Caching.java +++ b/spring-context/src/main/java/org/springframework/cache/annotation/Caching.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,12 +16,7 @@ package org.springframework.cache.annotation; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; +import java.lang.annotation.*; /** * Group annotation for multiple cache annotations (of different or the same type). @@ -30,7 +25,7 @@ * @author Chris Beams * @since 3.1 */ -@Target({ ElementType.METHOD, ElementType.TYPE }) +@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @@ -41,4 +36,5 @@ CachePut[] put() default {}; CacheEvict[] evict() default {}; + } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/Bean.java b/spring-context/src/main/java/org/springframework/context/annotation/Bean.java index a0c8ab11593a..679352decf1c 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/Bean.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/Bean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -177,7 +177,7 @@ * @see org.springframework.beans.factory.annotation.Autowired * @see org.springframework.beans.factory.annotation.Value */ -@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Bean { @@ -190,7 +190,7 @@ String[] name() default {}; /** - * Are dependencies to be injected via autowiring? + * Are dependencies to be injected via convention-based autowiring by name or type? */ Autowire autowire() default Autowire.NO; diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java index 99678159d336..37c74ce2a1fb 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java @@ -17,17 +17,11 @@ package org.springframework.context.annotation; import java.lang.reflect.Method; +import java.util.Arrays; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.cglib.proxy.Callback; -import org.springframework.cglib.proxy.CallbackFilter; -import org.springframework.cglib.proxy.Enhancer; -import org.springframework.cglib.proxy.MethodInterceptor; -import org.springframework.cglib.proxy.MethodProxy; -import org.springframework.cglib.proxy.NoOp; - import org.springframework.aop.scope.ScopedProxyFactoryBean; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.DisposableBean; @@ -35,6 +29,12 @@ import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.support.SimpleInstantiationStrategy; +import org.springframework.cglib.proxy.Callback; +import org.springframework.cglib.proxy.CallbackFilter; +import org.springframework.cglib.proxy.Enhancer; +import org.springframework.cglib.proxy.MethodInterceptor; +import org.springframework.cglib.proxy.MethodProxy; +import org.springframework.cglib.proxy.NoOp; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.Assert; @@ -52,8 +52,8 @@ class ConfigurationClassEnhancer { private static final Log logger = LogFactory.getLog(ConfigurationClassEnhancer.class); - private static final Class[] CALLBACK_TYPES = { BeanMethodInterceptor.class, - DisposableBeanMethodInterceptor.class, NoOp.class }; + private static final Class[] CALLBACK_TYPES = {BeanMethodInterceptor.class, + DisposableBeanMethodInterceptor.class, NoOp.class}; private static final CallbackFilter CALLBACK_FILTER = new CallbackFilter() { public int accept(Method candidateMethod) { @@ -80,10 +80,8 @@ public int accept(Method candidateMethod) { public ConfigurationClassEnhancer(ConfigurableBeanFactory beanFactory) { Assert.notNull(beanFactory, "BeanFactory must not be null"); // Callback instances must be ordered in the same way as CALLBACK_TYPES and CALLBACK_FILTER - this.callbackInstances = new Callback[] { - new BeanMethodInterceptor(beanFactory), - DISPOSABLE_BEAN_METHOD_INTERCEPTOR, - NoOp.INSTANCE }; + this.callbackInstances = new Callback[] + {new BeanMethodInterceptor(beanFactory), DISPOSABLE_BEAN_METHOD_INTERCEPTOR, NoOp.INSTANCE}; } /** @@ -169,9 +167,8 @@ public GetObjectMethodInterceptor(ConfigurableBeanFactory beanFactory, String be } public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { - return beanFactory.getBean(beanName); + return this.beanFactory.getBean(this.beanName); } - } @@ -209,19 +206,16 @@ public static boolean isDestroyMethod(Method candidateMethod) { */ private static class BeanMethodInterceptor implements MethodInterceptor { - private static final Class[] CALLBACK_TYPES = { - GetObjectMethodInterceptor.class, NoOp.class }; + private static final Class[] CALLBACK_TYPES = {GetObjectMethodInterceptor.class, NoOp.class}; - private static final CallbackFilter CALLBACK_FITLER = new CallbackFilter() { + private static final CallbackFilter CALLBACK_FILTER = new CallbackFilter() { public int accept(Method method) { - return method.getName().equals("getObject") ? 0 : 1; + return (method.getName().equals("getObject") ? 0 : 1); } }; - private final ConfigurableBeanFactory beanFactory; - public BeanMethodInterceptor(ConfigurableBeanFactory beanFactory) { this.beanFactory = beanFactory; } @@ -229,7 +223,6 @@ public BeanMethodInterceptor(ConfigurableBeanFactory beanFactory) { /** * Enhance a {@link Bean @Bean} method to check the supplied BeanFactory for the * existence of this bean object. - * * @throws Throwable as a catch-all for any exception that may be thrown when * invoking the super implementation of the proxied method i.e., the actual * {@code @Bean} method. @@ -255,8 +248,8 @@ public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object // proxy that intercepts calls to getObject() and returns any cached bean instance. // this ensures that the semantics of calling a FactoryBean from within @Bean methods // is the same as that of referring to a FactoryBean within XML. See SPR-6602. - if (factoryContainsBean('&'+beanName) && factoryContainsBean(beanName)) { - Object factoryBean = this.beanFactory.getBean('&'+beanName); + if (factoryContainsBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName) && factoryContainsBean(beanName)) { + Object factoryBean = this.beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName); if (factoryBean instanceof ScopedProxyFactoryBean) { // pass through - scoped proxy factory beans are a special case and should not // be further proxied @@ -267,9 +260,7 @@ public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object } } - boolean factoryIsCaller = beanMethod.equals(SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod()); - boolean factoryAlreadyContainsSingleton = this.beanFactory.containsSingleton(beanName); - if (factoryIsCaller && !factoryAlreadyContainsSingleton) { + if (isCurrentlyInvokedFactoryMethod(beanMethod) && !this.beanFactory.containsSingleton(beanName)) { // the factory is calling the bean method in order to instantiate and register the bean // (i.e. via a getBean() call) -> invoke the super implementation of the method to actually // create the bean instance. @@ -306,7 +297,7 @@ public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object } /** - * Check the beanFactory to see whether the bean named beanName already + * Check the BeanFactory to see whether the bean named beanName already * exists. Accounts for the fact that the requested bean may be "in creation", i.e.: * we're in the middle of servicing the initial request for this bean. From an enhanced * factory method's perspective, this means that the bean does not actually yet exist, @@ -319,9 +310,19 @@ public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object * @return whether beanName already exists in the factory */ private boolean factoryContainsBean(String beanName) { - boolean containsBean = this.beanFactory.containsBean(beanName); - boolean currentlyInCreation = this.beanFactory.isCurrentlyInCreation(beanName); - return (containsBean && !currentlyInCreation); + return (this.beanFactory.containsBean(beanName) && !this.beanFactory.isCurrentlyInCreation(beanName)); + } + + /** + * Check whether the given method corresponds to the container's currently invoked + * factory method. Compares method name and parameter types only in order to work + * around a potential problem with covariant return types (currently only known + * to happen on Groovy classes). + */ + private boolean isCurrentlyInvokedFactoryMethod(Method method) { + Method currentlyInvoked = SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod(); + return (currentlyInvoked != null && method.getName().equals(currentlyInvoked.getName()) && + Arrays.equals(method.getParameterTypes(), currentlyInvoked.getParameterTypes())); } /** @@ -335,13 +336,12 @@ private Object enhanceFactoryBean(Class fbClass, String beanName) throws Inst Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(fbClass); enhancer.setUseFactory(false); - enhancer.setCallbackFilter(CALLBACK_FITLER); + enhancer.setCallbackFilter(CALLBACK_FILTER); // Callback instances must be ordered in the same way as CALLBACK_TYPES and CALLBACK_FILTER Callback[] callbackInstances = new Callback[] { new GetObjectMethodInterceptor(this.beanFactory, beanName), NoOp.INSTANCE }; - enhancer.setCallbackTypes(CALLBACK_TYPES); Class fbSubclass = enhancer.createClass(); Enhancer.registerCallbacks(fbSubclass, callbackInstances); diff --git a/spring-context/src/main/java/org/springframework/format/datetime/DateFormatterRegistrar.java b/spring-context/src/main/java/org/springframework/format/datetime/DateFormatterRegistrar.java index aedf1184373a..b74b53262b5b 100644 --- a/spring-context/src/main/java/org/springframework/format/datetime/DateFormatterRegistrar.java +++ b/spring-context/src/main/java/org/springframework/format/datetime/DateFormatterRegistrar.java @@ -40,20 +40,24 @@ public class DateFormatterRegistrar implements FormatterRegistrar { - private DateFormatter dateFormatter = new DateFormatter(); + private DateFormatter dateFormatter; public void registerFormatters(FormatterRegistry registry) { addDateConverters(registry); - registry.addFormatter(dateFormatter); - registry.addFormatterForFieldType(Calendar.class, dateFormatter); registry.addFormatterForFieldAnnotation(new DateTimeFormatAnnotationFormatterFactory()); + + // In order to retain back compatibility we only register Date/Calendar + // types when a user defined formatter is specified (see SPR-10105) + if(this.dateFormatter != null) { + registry.addFormatter(this.dateFormatter); + registry.addFormatterForFieldType(Calendar.class, this.dateFormatter); + } } /** - * Set the date formatter to register. If not specified the default {@link DateFormatter} - * will be used. This method can be used if additional formatter configuration is - * required. + * Set the date formatter to register. If not specified no formatter is registered. + * This method can be used if global formatter configuration is required. * @param dateFormatter the date formatter */ public void setFormatter(DateFormatter dateFormatter) { @@ -117,7 +121,7 @@ private static class LongToCalendarConverter implements Converter asyncAnnotationTy this.pointcut = buildPointcut(asyncAnnotationTypes); } + /** + * Set the {@code BeanFactory} to be used when looking up executors by qualifier. + */ + public void setBeanFactory(BeanFactory beanFactory) { + if (this.advice instanceof BeanFactoryAware) { + ((BeanFactoryAware) this.advice).setBeanFactory(beanFactory); + } + } + public Advice getAdvice() { return this.advice; diff --git a/spring-context/src/main/java/org/springframework/scheduling/support/CronSequenceGenerator.java b/spring-context/src/main/java/org/springframework/scheduling/support/CronSequenceGenerator.java index 5f5cf4e28099..68d127c72f33 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/support/CronSequenceGenerator.java +++ b/spring-context/src/main/java/org/springframework/scheduling/support/CronSequenceGenerator.java @@ -71,7 +71,19 @@ public class CronSequenceGenerator { /** - * Construct a {@link CronSequenceGenerator} from the pattern provided. + * Construct a {@link CronSequenceGenerator} from the pattern provided, + * using the default {@link TimeZone}. + * @param expression a space-separated list of time fields + * @throws IllegalArgumentException if the pattern cannot be parsed + * @see java.util.TimeZone#getDefault() + */ + public CronSequenceGenerator(String expression) { + this(expression, TimeZone.getDefault()); + } + + /** + * Construct a {@link CronSequenceGenerator} from the pattern provided, + * using the specified {@link TimeZone}. * @param expression a space-separated list of time fields * @param timeZone the TimeZone to use for generated trigger times * @throws IllegalArgumentException if the pattern cannot be parsed @@ -114,12 +126,17 @@ public Date next(Date date) { calendar.setTimeZone(this.timeZone); calendar.setTime(date); - // Truncate to the next whole second - calendar.add(Calendar.SECOND, 1); + // First, just reset the milliseconds and try to calculate from there... calendar.set(Calendar.MILLISECOND, 0); - + long originalTimestamp = calendar.getTimeInMillis(); doNext(calendar, calendar.get(Calendar.YEAR)); + if (calendar.getTimeInMillis() == originalTimestamp) { + // We arrived at the original timestamp - round up to the next whole second and try again... + calendar.add(Calendar.SECOND, 1); + doNext(calendar, calendar.get(Calendar.YEAR)); + } + return calendar.getTime(); } diff --git a/spring-context/src/main/java/org/springframework/scheduling/support/CronTrigger.java b/spring-context/src/main/java/org/springframework/scheduling/support/CronTrigger.java index eca37982e6d4..94f6385a1a9c 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/support/CronTrigger.java +++ b/spring-context/src/main/java/org/springframework/scheduling/support/CronTrigger.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,12 +16,12 @@ package org.springframework.scheduling.support; -import java.util.Date; -import java.util.TimeZone; - import org.springframework.scheduling.Trigger; import org.springframework.scheduling.TriggerContext; +import java.util.Date; +import java.util.TimeZone; + /** * {@link Trigger} implementation for cron expressions. * Wraps a {@link CronSequenceGenerator}. @@ -41,7 +41,7 @@ public class CronTrigger implements Trigger { * following cron expression conventions */ public CronTrigger(String cronExpression) { - this(cronExpression, TimeZone.getDefault()); + this.sequenceGenerator = new CronSequenceGenerator(cronExpression); } /** @@ -89,7 +89,7 @@ public int hashCode() { @Override public String toString() { - return sequenceGenerator.toString(); + return this.sequenceGenerator.toString(); } } diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationInterceptor.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationInterceptor.java index 58cd161175be..3d4b907fa2fb 100644 --- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationInterceptor.java +++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,8 +46,8 @@ * of that class. By default, JSR-303 will validate against its default group only. * *

    As of Spring 3.1, this functionality requires Hibernate Validator 4.2 or higher. - * In Spring 3.1.2, this class will autodetect a Bean Validation 1.1 compliant provider - * and automatically use the standard method validation support there (once available). + * Once Bean Validation 1.1 becomes available, this class will autodetect a compliant + * provider and automatically use the standard method validation support there. * * @author Juergen Hoeller * @since 3.1 diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java index 7565e82099cb..f3c45fc2fa2b 100644 --- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java +++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java @@ -130,7 +130,8 @@ protected void processConstraintViolations(Set> viol } else { Object invalidValue = violation.getInvalidValue(); - if (field.contains(".") && !field.contains("[]")) { + if (!"".equals(field) && (invalidValue == violation.getLeafBean() || + (field.contains(".") && !field.contains("[]")))) { // Possibly a bean constraint with property path: retrieve the actual property value. // However, explicitly avoid this for "address[]" style paths that we can't handle. invalidValue = bindingResult.getRawFieldValue(field); diff --git a/spring-context/src/test/java/org/springframework/cache/interceptor/ExpressionEvalutatorTest.java b/spring-context/src/test/java/org/springframework/cache/interceptor/ExpressionEvaluatorTests.java similarity index 99% rename from spring-context/src/test/java/org/springframework/cache/interceptor/ExpressionEvalutatorTest.java rename to spring-context/src/test/java/org/springframework/cache/interceptor/ExpressionEvaluatorTests.java index 0a664afac0ec..44762719bcf0 100644 --- a/spring-context/src/test/java/org/springframework/cache/interceptor/ExpressionEvalutatorTest.java +++ b/spring-context/src/test/java/org/springframework/cache/interceptor/ExpressionEvaluatorTests.java @@ -44,7 +44,7 @@ * @author Costin Leau * @author Phillip Webb */ -public class ExpressionEvalutatorTest { +public class ExpressionEvaluatorTests { @Rule public ExpectedException thrown = ExpectedException.none(); diff --git a/spring-context/src/test/java/org/springframework/format/datetime/DateFormattingTests.java b/spring-context/src/test/java/org/springframework/format/datetime/DateFormattingTests.java index 5698ed06518e..2803f0dc9f9d 100644 --- a/spring-context/src/test/java/org/springframework/format/datetime/DateFormattingTests.java +++ b/spring-context/src/test/java/org/springframework/format/datetime/DateFormattingTests.java @@ -16,7 +16,10 @@ package org.springframework.format.datetime; +import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; import java.util.ArrayList; import java.util.Calendar; @@ -50,9 +53,12 @@ public class DateFormattingTests { @Before public void setUp() { - DefaultConversionService.addDefaultConverters(conversionService); - DateFormatterRegistrar registrar = new DateFormatterRegistrar(); + setUp(registrar); + } + + private void setUp(DateFormatterRegistrar registrar) { + DefaultConversionService.addDefaultConverters(conversionService); registrar.registerFormatters(conversionService); SimpleDateBean bean = new SimpleDateBean(); @@ -187,13 +193,48 @@ public void testBindNestedDateAnnotated() { } @Test - public void dateToString() throws Exception { + public void dateToStringWithoutGlobalFormat() throws Exception { + Date date = new Date(); + Object actual = this.conversionService.convert(date, TypeDescriptor.valueOf(Date.class), TypeDescriptor.valueOf(String.class)); + String expected = date.toString(); + assertEquals(expected, actual); + } + + @Test + public void dateToStringWithGlobalFormat() throws Exception { + DateFormatterRegistrar registrar = new DateFormatterRegistrar(); + registrar.setFormatter(new DateFormatter()); + setUp(registrar); Date date = new Date(); Object actual = this.conversionService.convert(date, TypeDescriptor.valueOf(Date.class), TypeDescriptor.valueOf(String.class)); String expected = new DateFormatter().print(date, Locale.US); assertEquals(expected, actual); } + @Test + @SuppressWarnings("deprecation") + public void stringToDateWithoutGlobalFormat() throws Exception { + // SPR-10105 + String string = "Sat, 12 Aug 1995 13:30:00 GM"; + Date date = this.conversionService.convert(string, Date.class); + assertThat(date, equalTo(new Date(string))); + } + + @Test + public void stringToDateWithGlobalFormat() throws Exception { + // SPR-10105 + DateFormatterRegistrar registrar = new DateFormatterRegistrar(); + DateFormatter dateFormatter = new DateFormatter(); + dateFormatter.setIso(ISO.DATE_TIME); + registrar.setFormatter(dateFormatter); + setUp(registrar); + // This is a format that cannot be parsed by new Date(String) + String string = "2009-06-01T14:23:05.003+0000"; + Date date = this.conversionService.convert(string, Date.class); + assertNotNull(date); + } + + @SuppressWarnings("unused") private static class SimpleDateBean { diff --git a/spring-context/src/test/java/org/springframework/format/datetime/joda/JodaTimeFormattingTests.java b/spring-context/src/test/java/org/springframework/format/datetime/joda/JodaTimeFormattingTests.java index 237df0509f3f..c2aaf8d36a3c 100644 --- a/spring-context/src/test/java/org/springframework/format/datetime/joda/JodaTimeFormattingTests.java +++ b/spring-context/src/test/java/org/springframework/format/datetime/joda/JodaTimeFormattingTests.java @@ -16,6 +16,11 @@ package org.springframework.format.datetime.joda; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; + import java.util.ArrayList; import java.util.Calendar; import java.util.Date; @@ -32,7 +37,6 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; - import org.springframework.beans.MutablePropertyValues; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.core.convert.TypeDescriptor; @@ -42,8 +46,6 @@ import org.springframework.format.support.FormattingConversionService; import org.springframework.validation.DataBinder; -import static org.junit.Assert.*; - /** * @author Keith Donald * @author Juergen Hoeller @@ -459,13 +461,40 @@ public void testBindMutableDateTimeAnnotated() { } @Test - public void dateToString() throws Exception { + public void dateToStringWithFormat() throws Exception { + JodaTimeFormatterRegistrar registrar = new JodaTimeFormatterRegistrar(); + registrar.setDateTimeFormatter(org.joda.time.format.DateTimeFormat.shortDateTime()); + setUp(registrar); Date date = new Date(); Object actual = this.conversionService.convert(date, TypeDescriptor.valueOf(Date.class), TypeDescriptor.valueOf(String.class)); String expected = JodaTimeContextHolder.getFormatter(org.joda.time.format.DateTimeFormat.shortDateTime(), Locale.US).print(new DateTime(date)); assertEquals(expected, actual); } + @Test + @SuppressWarnings("deprecation") + public void stringToDateWithoutGlobalFormat() throws Exception { + // SPR-10105 + String string = "Sat, 12 Aug 1995 13:30:00 GM"; + Date date = this.conversionService.convert(string, Date.class); + assertThat(date, equalTo(new Date(string))); + } + + @Test + public void stringToDateWithGlobalFormat() throws Exception { + // SPR-10105 + JodaTimeFormatterRegistrar registrar = new JodaTimeFormatterRegistrar(); + DateTimeFormatterFactory factory = new DateTimeFormatterFactory(); + factory.setIso(ISO.DATE_TIME); + registrar.setDateTimeFormatter(factory.createDateTimeFormatter()); + setUp(registrar); + // This is a format that cannot be parsed by new Date(String) + String string = "2009-10-31T07:00:00.000-05:00"; + Date date = this.conversionService.convert(string, Date.class); + assertNotNull(date); + } + + @SuppressWarnings("unused") private static class JodaTimeBean { diff --git a/spring-context/src/test/java/org/springframework/jmx/AbstractMBeanServerTests.java b/spring-context/src/test/java/org/springframework/jmx/AbstractMBeanServerTests.java index 12dbf05a50c3..ab05a3ea6ed0 100644 --- a/spring-context/src/test/java/org/springframework/jmx/AbstractMBeanServerTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/AbstractMBeanServerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,32 +20,38 @@ import javax.management.MBeanServerFactory; import javax.management.ObjectName; -import junit.framework.TestCase; - +import org.junit.After; +import org.junit.Before; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.GenericApplicationContext; +import org.springframework.tests.TestGroup; import org.springframework.util.MBeanTestUtils; +import static org.junit.Assert.*; + /** - * Note: the JMX test suite requires the presence of the - * {@code jmxremote_optional.jar} in your classpath. Thus, if you - * run into the "Unsupported protocol: jmxmp" error, you will - * need to download the - * JMX Remote API 1.0.1_04 Reference Implementation - * from Oracle and extract {@code jmxremote_optional.jar} into your - * classpath, for example in the {@code lib/ext} folder of your JVM. + * Note: certain tests throughout this hierarchy require the presence of + * the {@code jmxremote_optional.jar} in your classpath. For this reason, these tests are + * run only if {@link TestGroup#JMXMP} is enabled. If you wish to run these tests, follow + * the instructions in the TestGroup class to enable JMXMP tests. If you run into the + * "Unsupported protocol: jmxmp" error, you will need to download the + * + * JMX Remote API 1.0.1_04 Reference Implementation from Oracle and extract + * {@code jmxremote_optional.jar} into your classpath, for example in the {@code lib/ext} + * folder of your JVM. * See also EBR-349. * * @author Rob Harrop * @author Juergen Hoeller * @author Sam Brannen + * @author Chris Beams */ -public abstract class AbstractMBeanServerTests extends TestCase { +public abstract class AbstractMBeanServerTests { protected MBeanServer server; - @Override + @Before public final void setUp() throws Exception { this.server = MBeanServerFactory.createMBeanServer(); try { @@ -64,8 +70,8 @@ protected ConfigurableApplicationContext loadContext(String configLocation) { return ctx; } - @Override - protected void tearDown() throws Exception { + @After + public void tearDown() throws Exception { releaseServer(); onTearDown(); } diff --git a/spring-context/src/test/java/org/springframework/jmx/access/MBeanClientInterceptorTests.java b/spring-context/src/test/java/org/springframework/jmx/access/MBeanClientInterceptorTests.java index 7842ee328cb2..e1bd521a1df6 100644 --- a/spring-context/src/test/java/org/springframework/jmx/access/MBeanClientInterceptorTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/access/MBeanClientInterceptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of @@ -29,18 +29,23 @@ import javax.management.remote.JMXConnectorServerFactory; import javax.management.remote.JMXServiceURL; -import org.junit.Ignore; +import org.junit.Test; import org.springframework.jmx.AbstractMBeanServerTests; import org.springframework.jmx.IJmxTestBean; import org.springframework.jmx.JmxException; import org.springframework.jmx.JmxTestBean; import org.springframework.jmx.export.MBeanExporter; import org.springframework.jmx.export.assembler.AbstractReflectiveMBeanInfoAssembler; +import org.springframework.tests.Assume; +import org.springframework.tests.TestGroup; + +import static org.junit.Assert.*; /** * @author Rob Harrop * @author Juergen Hoeller * @author Sam Brannen + * @author Chris Beams */ public class MBeanClientInterceptorTests extends AbstractMBeanServerTests { @@ -78,6 +83,7 @@ protected IJmxTestBean getProxy() throws Exception { return (IJmxTestBean) factory.getObject(); } + @Test public void testProxyClassIsDifferent() throws Exception { if (!runTests) return; @@ -85,6 +91,7 @@ public void testProxyClassIsDifferent() throws Exception { assertTrue("The proxy class should be different than the base class", (proxy.getClass() != IJmxTestBean.class)); } + @Test public void testDifferentProxiesSameClass() throws Exception { if (!runTests) return; @@ -95,6 +102,7 @@ public void testDifferentProxiesSameClass() throws Exception { assertSame("The proxy classes should be the same", proxy1.getClass(), proxy2.getClass()); } + @Test public void testGetAttributeValue() throws Exception { if (!runTests) return; @@ -103,6 +111,7 @@ public void testGetAttributeValue() throws Exception { assertEquals("The age should be 100", 100, age); } + @Test public void testSetAttributeValue() throws Exception { if (!runTests) return; @@ -111,6 +120,7 @@ public void testSetAttributeValue() throws Exception { assertEquals("The name of the bean should have been updated", "Rob Harrop", target.getName()); } + @Test public void testSetAttributeValueWithRuntimeException() throws Exception { if (!runTests) return; @@ -123,6 +133,7 @@ public void testSetAttributeValueWithRuntimeException() throws Exception { } } + @Test public void testSetAttributeValueWithCheckedException() throws Exception { if (!runTests) return; @@ -135,6 +146,7 @@ public void testSetAttributeValueWithCheckedException() throws Exception { } } + @Test public void testSetAttributeValueWithIOException() throws Exception { if (!runTests) return; @@ -147,6 +159,7 @@ public void testSetAttributeValueWithIOException() throws Exception { } } + @Test public void testSetReadOnlyAttribute() throws Exception { if (!runTests) return; @@ -159,6 +172,7 @@ public void testSetReadOnlyAttribute() throws Exception { } } + @Test public void testInvokeNoArgs() throws Exception { if (!runTests) return; @@ -167,6 +181,7 @@ public void testInvokeNoArgs() throws Exception { assertEquals("The operation should return 1", 1, result); } + @Test public void testInvokeArgs() throws Exception { if (!runTests) return; @@ -175,6 +190,7 @@ public void testInvokeArgs() throws Exception { assertEquals("The operation should return 3", 3, result); } + @Test public void testInvokeUnexposedMethodWithException() throws Exception { if (!runTests) return; @@ -187,19 +203,13 @@ public void testInvokeUnexposedMethodWithException() throws Exception { } } - // TODO [SPR-8089] Clean up ignored JMX tests. - // - // @Ignore should have no effect for JUnit 3.8 tests; however, it appears - // that tests on the CI server -- as well as those in Eclipse -- do in - // fact get ignored. So we leave @Ignore here so that developers can - // easily search for ignored tests. - // - // Once fixed, renamed to test* instead of ignore*. - @Ignore("Requires jmxremote_optional.jar; see comments in AbstractMBeanServerTests for details.") - public void ignoreTestLazyConnectionToRemote() throws Exception { + @Test + public void testTestLazyConnectionToRemote() throws Exception { if (!runTests) return; + Assume.group(TestGroup.JMXMP); + JMXServiceURL url = new JMXServiceURL("service:jmx:jmxmp://localhost:9876"); JMXConnectorServer connector = JMXConnectorServerFactory.newJMXConnectorServer(url, null, getServer()); diff --git a/spring-context/src/test/java/org/springframework/jmx/access/RemoteMBeanClientInterceptorTestsIgnore.java b/spring-context/src/test/java/org/springframework/jmx/access/RemoteMBeanClientInterceptorTests.java similarity index 75% rename from spring-context/src/test/java/org/springframework/jmx/access/RemoteMBeanClientInterceptorTestsIgnore.java rename to spring-context/src/test/java/org/springframework/jmx/access/RemoteMBeanClientInterceptorTests.java index 7082efec132e..74a29b0be018 100644 --- a/spring-context/src/test/java/org/springframework/jmx/access/RemoteMBeanClientInterceptorTestsIgnore.java +++ b/spring-context/src/test/java/org/springframework/jmx/access/RemoteMBeanClientInterceptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of @@ -26,19 +26,14 @@ import javax.management.remote.JMXConnectorServerFactory; import javax.management.remote.JMXServiceURL; -import org.junit.Ignore; +import org.springframework.tests.Assume; +import org.springframework.tests.TestGroup; /** * @author Rob Harrop + * @author Chris Beams */ -// TODO [SPR-8089] Clean up ignored JMX tests. -// -// @Ignore should have no effect for JUnit 3.8 tests; however, it appears -// that tests on the CI server -- as well as those in Eclipse -- do in -// fact get ignored. So we leave @Ignore here so that developers can -// easily search for ignored tests. -@Ignore("Requires jmxremote_optional.jar; see comments in AbstractMBeanServerTests for details.") -public class RemoteMBeanClientInterceptorTestsIgnore extends MBeanClientInterceptorTests { +public class RemoteMBeanClientInterceptorTests extends MBeanClientInterceptorTests { private static final String SERVICE_URL = "service:jmx:jmxmp://localhost:9876"; @@ -48,6 +43,9 @@ public class RemoteMBeanClientInterceptorTestsIgnore extends MBeanClientIntercep @Override public void onSetUp() throws Exception { + runTests = false; + Assume.group(TestGroup.JMXMP); + runTests = true; super.onSetUp(); this.connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(getServiceUrl(), null, getServer()); try { @@ -73,8 +71,12 @@ public void tearDown() throws Exception { if (this.connector != null) { this.connector.close(); } - this.connectorServer.stop(); - super.tearDown(); + if (this.connectorServer != null) { + this.connectorServer.stop(); + } + if (runTests) { + super.tearDown(); + } } } diff --git a/spring-context/src/test/java/org/springframework/jmx/export/CustomEditorConfigurerTests.java b/spring-context/src/test/java/org/springframework/jmx/export/CustomEditorConfigurerTests.java index 2c71236d78a7..190190111422 100644 --- a/spring-context/src/test/java/org/springframework/jmx/export/CustomEditorConfigurerTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/export/CustomEditorConfigurerTests.java @@ -22,8 +22,11 @@ import javax.management.ObjectName; +import org.junit.Test; import org.springframework.jmx.AbstractJmxTests; +import static org.junit.Assert.*; + /** * @author Rob Harrop */ @@ -36,6 +39,7 @@ protected String getApplicationContextPath() { return "org/springframework/jmx/export/customConfigurer.xml"; } + @Test public void testDatesInJmx() throws Exception { // System.out.println(getServer().getClass().getName()); ObjectName oname = new ObjectName("bean:name=dateRange"); @@ -47,6 +51,7 @@ public void testDatesInJmx() throws Exception { assertEquals("endDate ", getEndDate(), endJmx); } + @Test public void testGetDates() throws Exception { DateRange dr = (DateRange) getContext().getBean("dateRange"); diff --git a/spring-context/src/test/java/org/springframework/jmx/export/MBeanExporterOperationsTests.java b/spring-context/src/test/java/org/springframework/jmx/export/MBeanExporterOperationsTests.java index 59740fa0db44..f2bee326688c 100644 --- a/spring-context/src/test/java/org/springframework/jmx/export/MBeanExporterOperationsTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/export/MBeanExporterOperationsTests.java @@ -28,6 +28,8 @@ import org.springframework.jmx.export.naming.ObjectNamingStrategy; import org.springframework.jmx.support.ObjectNameManager; +import static org.junit.Assert.*; + /** * @author Rob Harrop * @author Juergen Hoeller diff --git a/spring-context/src/test/java/org/springframework/jmx/export/MBeanExporterTests.java b/spring-context/src/test/java/org/springframework/jmx/export/MBeanExporterTests.java index 2d7d9e26398d..c08a9f73b2f7 100644 --- a/spring-context/src/test/java/org/springframework/jmx/export/MBeanExporterTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/export/MBeanExporterTests.java @@ -33,6 +33,7 @@ import javax.management.ObjectName; import javax.management.modelmbean.ModelMBeanInfo; +import org.junit.Test; import org.springframework.aop.framework.ProxyFactory; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.DefaultListableBeanFactory; @@ -50,6 +51,8 @@ import org.springframework.tests.aop.interceptor.NopInterceptor; import org.springframework.tests.sample.beans.TestBean; +import static org.junit.Assert.*; + /** * Integration tests for the {@link MBeanExporter} class. * @@ -66,6 +69,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { private static final String OBJECT_NAME = "spring:test=jmxMBeanAdaptor"; @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test public void testRegisterNonNotificationListenerType() throws Exception { Map listeners = new HashMap(); // put a non-NotificationListener instance in as a value... @@ -80,6 +84,7 @@ public void testRegisterNonNotificationListenerType() throws Exception { } @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test public void testRegisterNullNotificationListenerType() throws Exception { Map listeners = new HashMap(); // put null in as a value... @@ -94,6 +99,7 @@ public void testRegisterNullNotificationListenerType() throws Exception { } @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test public void testRegisterNotificationListenerForNonExistentMBean() throws Exception { Map listeners = new HashMap(); NotificationListener dummyListener = new NotificationListener() { @@ -117,6 +123,7 @@ public void handleNotification(Notification notification, Object handback) { } } + @Test public void testWithSuppliedMBeanServer() throws Exception { MBeanExporter exporter = new MBeanExporter(); exporter.setBeans(getBeanMap()); @@ -127,6 +134,7 @@ public void testWithSuppliedMBeanServer() throws Exception { } /** Fails if JVM platform MBean server has been started already + @Test public void testWithLocatedMBeanServer() throws Exception { MBeanExporter adaptor = new MBeanExporter(); adaptor.setBeans(getBeanMap()); @@ -136,6 +144,7 @@ public void testWithLocatedMBeanServer() throws Exception { } */ + @Test public void testUserCreatedMBeanRegWithDynamicMBean() throws Exception { Map map = new HashMap(); map.put("spring:name=dynBean", new TestDynamicMBean()); @@ -153,6 +162,7 @@ public void testUserCreatedMBeanRegWithDynamicMBean() throws Exception { assertFalse("Assembler should not have been invoked", asm.invoked); } + @Test public void testAutodetectMBeans() throws Exception { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); new XmlBeanDefinitionReader(bf).loadBeanDefinitions(new ClassPathResource("autodetectMBeans.xml", getClass())); @@ -170,6 +180,7 @@ public void testAutodetectMBeans() throws Exception { } } + @Test public void testAutodetectWithExclude() throws Exception { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); new XmlBeanDefinitionReader(bf).loadBeanDefinitions(new ClassPathResource("autodetectMBeans.xml", getClass())); @@ -189,6 +200,7 @@ public void testAutodetectWithExclude() throws Exception { } } + @Test public void testAutodetectLazyMBeans() throws Exception { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); new XmlBeanDefinitionReader(bf).loadBeanDefinitions(new ClassPathResource("autodetectLazyMBeans.xml", getClass())); @@ -210,6 +222,7 @@ public void testAutodetectLazyMBeans() throws Exception { } } + @Test public void testAutodetectNoMBeans() throws Exception { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); new XmlBeanDefinitionReader(bf).loadBeanDefinitions(new ClassPathResource("autodetectNoMBeans.xml", getClass())); @@ -220,6 +233,7 @@ public void testAutodetectNoMBeans() throws Exception { } } + @Test public void testWithMBeanExporterListeners() throws Exception { MockMBeanExporterListener listener1 = new MockMBeanExporterListener(); MockMBeanExporterListener listener2 = new MockMBeanExporterListener(); @@ -235,6 +249,7 @@ public void testWithMBeanExporterListeners() throws Exception { assertListener(listener2); } + @Test public void testExportJdkProxy() throws Exception { JmxTestBean bean = new JmxTestBean(); bean.setName("Rob Harrop"); @@ -260,6 +275,7 @@ public void testExportJdkProxy() throws Exception { assertEquals("Rob Harrop", nameValue); } + @Test public void testSelfNaming() throws Exception { ObjectName objectName = ObjectNameManager.getInstance(OBJECT_NAME); SelfNamingTestBean testBean = new SelfNamingTestBean(); @@ -278,6 +294,7 @@ public void testSelfNaming() throws Exception { assertNotNull(instance); } + @Test public void testRegisterIgnoreExisting() throws Exception { ObjectName objectName = ObjectNameManager.getInstance(OBJECT_NAME); @@ -311,6 +328,7 @@ public void testRegisterIgnoreExisting() throws Exception { assertEquals("Rob Harrop", server.getAttribute(objectName, "Name")); } + @Test public void testRegisterReplaceExisting() throws Exception { ObjectName objectName = ObjectNameManager.getInstance(OBJECT_NAME); @@ -339,6 +357,7 @@ public void testRegisterReplaceExisting() throws Exception { assertEquals("Sally Greenwood", server.getAttribute(objectName, "Name")); } + @Test public void testWithExposeClassLoader() throws Exception { String name = "Rob Harrop"; String otherName = "Juergen Hoeller"; @@ -368,6 +387,7 @@ public void testWithExposeClassLoader() throws Exception { assertEquals("Incorrect updated name.", otherName, bean.getName()); } + @Test public void testBonaFideMBeanIsNotExportedWhenAutodetectIsTotallyTurnedOff() throws Exception { BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(Person.class); DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); @@ -386,6 +406,7 @@ public void testBonaFideMBeanIsNotExportedWhenAutodetectIsTotallyTurnedOff() thr exporter.afterPropertiesSet(); } + @Test public void testOnlyBonaFideMBeanIsExportedWhenAutodetectIsMBeanOnly() throws Exception { BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(Person.class); DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); @@ -405,6 +426,7 @@ public void testOnlyBonaFideMBeanIsExportedWhenAutodetectIsMBeanOnly() throws Ex ObjectNameManager.getInstance(exportedBeanName)); } + @Test public void testBonaFideMBeanAndRegularBeanExporterWithAutodetectAll() throws Exception { BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(Person.class); DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); @@ -428,6 +450,7 @@ public void testBonaFideMBeanAndRegularBeanExporterWithAutodetectAll() throws Ex ObjectNameManager.getInstance(notToBeExportedBeanName)); } + @Test public void testBonaFideMBeanIsNotExportedWithAutodetectAssembler() throws Exception { BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(Person.class); DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); @@ -450,6 +473,7 @@ public void testBonaFideMBeanIsNotExportedWithAutodetectAssembler() throws Excep /** * Want to ensure that said MBean is not exported twice. */ + @Test public void testBonaFideMBeanExplicitlyExportedAndAutodetectionIsOn() throws Exception { BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(Person.class); DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); @@ -468,6 +492,7 @@ public void testBonaFideMBeanExplicitlyExportedAndAutodetectionIsOn() throws Exc ObjectNameManager.getInstance(OBJECT_NAME)); } + @Test public void testSetAutodetectModeToOutOfRangeNegativeValue() throws Exception { try { MBeanExporter exporter = new MBeanExporter(); @@ -478,6 +503,7 @@ public void testSetAutodetectModeToOutOfRangeNegativeValue() throws Exception { } } + @Test public void testSetAutodetectModeToOutOfRangePositiveValue() throws Exception { try { MBeanExporter exporter = new MBeanExporter(); @@ -488,6 +514,7 @@ public void testSetAutodetectModeToOutOfRangePositiveValue() throws Exception { } } + @Test public void testSetAutodetectModeNameToNull() throws Exception { try { MBeanExporter exporter = new MBeanExporter(); @@ -498,6 +525,7 @@ public void testSetAutodetectModeNameToNull() throws Exception { } } + @Test public void testSetAutodetectModeNameToAnEmptyString() throws Exception { try { MBeanExporter exporter = new MBeanExporter(); @@ -508,6 +536,7 @@ public void testSetAutodetectModeNameToAnEmptyString() throws Exception { } } + @Test public void testSetAutodetectModeNameToAWhitespacedString() throws Exception { try { MBeanExporter exporter = new MBeanExporter(); @@ -518,6 +547,7 @@ public void testSetAutodetectModeNameToAWhitespacedString() throws Exception { } } + @Test public void testSetAutodetectModeNameToARubbishValue() throws Exception { try { MBeanExporter exporter = new MBeanExporter(); @@ -528,6 +558,7 @@ public void testSetAutodetectModeNameToARubbishValue() throws Exception { } } + @Test public void testNotRunningInBeanFactoryAndPassedBeanNameToExport() throws Exception { try { MBeanExporter exporter = new MBeanExporter(); @@ -541,6 +572,7 @@ public void testNotRunningInBeanFactoryAndPassedBeanNameToExport() throws Except } } + @Test public void testNotRunningInBeanFactoryAndAutodetectionIsOn() throws Exception { try { MBeanExporter exporter = new MBeanExporter(); @@ -555,6 +587,7 @@ public void testNotRunningInBeanFactoryAndAutodetectionIsOn() throws Exception { /** * SPR-2158 */ + @Test public void testMBeanIsNotUnregisteredSpuriouslyIfSomeExternalProcessHasUnregisteredMBean() throws Exception { MBeanExporter exporter = new MBeanExporter(); exporter.setBeans(getBeanMap()); @@ -574,6 +607,7 @@ public void testMBeanIsNotUnregisteredSpuriouslyIfSomeExternalProcessHasUnregist /** * SPR-3302 */ + @Test public void testBeanNameCanBeUsedInNotificationListenersMap() throws Exception { String beanName = "charlesDexterWard"; BeanDefinitionBuilder testBean = BeanDefinitionBuilder.rootBeanDefinition(JmxTestBean.class); @@ -595,6 +629,7 @@ public void testBeanNameCanBeUsedInNotificationListenersMap() throws Exception { exporter.afterPropertiesSet(); } + @Test public void testWildcardCanBeUsedInNotificationListenersMap() throws Exception { String beanName = "charlesDexterWard"; BeanDefinitionBuilder testBean = BeanDefinitionBuilder.rootBeanDefinition(JmxTestBean.class); @@ -619,6 +654,7 @@ public void testWildcardCanBeUsedInNotificationListenersMap() throws Exception { /* * SPR-3625 */ + @Test public void testMBeanIsUnregisteredForRuntimeExceptionDuringInitialization() throws Exception { BeanDefinitionBuilder builder1 = BeanDefinitionBuilder.rootBeanDefinition(Person.class); BeanDefinitionBuilder builder2 = BeanDefinitionBuilder diff --git a/spring-context/src/test/java/org/springframework/jmx/export/NotificationListenerTests.java b/spring-context/src/test/java/org/springframework/jmx/export/NotificationListenerTests.java index 45244123079c..a6423dcee8db 100644 --- a/spring-context/src/test/java/org/springframework/jmx/export/NotificationListenerTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/export/NotificationListenerTests.java @@ -26,6 +26,7 @@ import javax.management.NotificationListener; import javax.management.ObjectName; +import org.junit.Test; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.jmx.AbstractMBeanServerTests; import org.springframework.jmx.JmxTestBean; @@ -33,6 +34,8 @@ import org.springframework.jmx.export.naming.SelfNaming; import org.springframework.jmx.support.ObjectNameManager; +import static org.junit.Assert.*; + /** * @author Rob Harrop * @author Mark Fisher @@ -41,6 +44,7 @@ public class NotificationListenerTests extends AbstractMBeanServerTests { @SuppressWarnings({"rawtypes", "unchecked"}) + @Test public void testRegisterNotificationListenerForMBean() throws Exception { ObjectName objectName = ObjectName.getInstance("spring:name=Test"); JmxTestBean bean = new JmxTestBean(); @@ -66,6 +70,7 @@ public void testRegisterNotificationListenerForMBean() throws Exception { } @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test public void testRegisterNotificationListenerWithWildcard() throws Exception { ObjectName objectName = ObjectName.getInstance("spring:name=Test"); JmxTestBean bean = new JmxTestBean(); @@ -90,6 +95,7 @@ public void testRegisterNotificationListenerWithWildcard() throws Exception { assertEquals("Listener not notified", 1, listener.getCount(attributeName)); } + @Test public void testRegisterNotificationListenerWithHandback() throws Exception { String objectName = "spring:name=Test"; JmxTestBean bean = new JmxTestBean(); @@ -120,6 +126,7 @@ public void testRegisterNotificationListenerWithHandback() throws Exception { assertEquals("Handback object not transmitted correctly", handback, listener.getLastHandback(attributeName)); } + @Test public void testRegisterNotificationListenerForAllMBeans() throws Exception { ObjectName objectName = ObjectName.getInstance("spring:name=Test"); JmxTestBean bean = new JmxTestBean(); @@ -146,6 +153,7 @@ public void testRegisterNotificationListenerForAllMBeans() throws Exception { } @SuppressWarnings("serial") + @Test public void testRegisterNotificationListenerWithFilter() throws Exception { ObjectName objectName = ObjectName.getInstance("spring:name=Test"); JmxTestBean bean = new JmxTestBean(); @@ -186,6 +194,7 @@ public boolean isNotificationEnabled(Notification notification) { assertEquals("Listener incorrectly notified for Age", 0, listener.getCount(ageAttribute)); } + @Test public void testCreationWithNoNotificationListenerSet() { try { new NotificationListenerBean().afterPropertiesSet(); @@ -195,6 +204,7 @@ public void testCreationWithNoNotificationListenerSet() { } @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test public void testRegisterNotificationListenerWithBeanNameAndBeanNameInBeansMap() throws Exception { String beanName = "testBean"; ObjectName objectName = ObjectName.getInstance("spring:name=Test"); @@ -225,6 +235,7 @@ public void testRegisterNotificationListenerWithBeanNameAndBeanNameInBeansMap() } @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test public void testRegisterNotificationListenerWithBeanNameAndBeanInstanceInBeansMap() throws Exception { String beanName = "testBean"; ObjectName objectName = ObjectName.getInstance("spring:name=Test"); @@ -255,6 +266,7 @@ public void testRegisterNotificationListenerWithBeanNameAndBeanInstanceInBeansMa } @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test public void testRegisterNotificationListenerWithBeanNameBeforeObjectNameMappedToSameBeanInstance() throws Exception { String beanName = "testBean"; ObjectName objectName = ObjectName.getInstance("spring:name=Test"); @@ -286,6 +298,7 @@ public void testRegisterNotificationListenerWithBeanNameBeforeObjectNameMappedTo } @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test public void testRegisterNotificationListenerWithObjectNameBeforeBeanNameMappedToSameBeanInstance() throws Exception { String beanName = "testBean"; ObjectName objectName = ObjectName.getInstance("spring:name=Test"); @@ -317,6 +330,7 @@ public void testRegisterNotificationListenerWithObjectNameBeforeBeanNameMappedTo } @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test public void testRegisterNotificationListenerWithTwoBeanNamesMappedToDifferentBeanInstances() throws Exception { String beanName1 = "testBean1"; String beanName2 = "testBean2"; @@ -359,6 +373,7 @@ public void testRegisterNotificationListenerWithTwoBeanNamesMappedToDifferentBea assertEquals("Listener not notified for testBean2", 2, listener.getCount("Age")); } + @Test public void testNotificationListenerRegistrar() throws Exception { ObjectName objectName = ObjectName.getInstance("spring:name=Test"); JmxTestBean bean = new JmxTestBean(); @@ -391,6 +406,7 @@ public void testNotificationListenerRegistrar() throws Exception { assertEquals("Listener notified after destruction", 1, listener.getCount(attributeName)); } + @Test public void testNotificationListenerRegistrarWithMultipleNames() throws Exception { ObjectName objectName = ObjectName.getInstance("spring:name=Test"); ObjectName objectName2 = ObjectName.getInstance("spring:name=Test2"); diff --git a/spring-context/src/test/java/org/springframework/jmx/export/NotificationPublisherTests.java b/spring-context/src/test/java/org/springframework/jmx/export/NotificationPublisherTests.java index a6abda7ed2ef..2237fcc15533 100644 --- a/spring-context/src/test/java/org/springframework/jmx/export/NotificationPublisherTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/export/NotificationPublisherTests.java @@ -32,12 +32,15 @@ import javax.management.NotificationListener; import javax.management.ReflectionException; +import org.junit.Test; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.jmx.AbstractMBeanServerTests; import org.springframework.jmx.export.notification.NotificationPublisher; import org.springframework.jmx.export.notification.NotificationPublisherAware; import org.springframework.jmx.support.ObjectNameManager; +import static org.junit.Assert.*; + /** * Integration tests for the Spring JMX {@link NotificationPublisher} functionality. * @@ -48,6 +51,7 @@ public class NotificationPublisherTests extends AbstractMBeanServerTests { private CountingNotificationListener listener = new CountingNotificationListener(); + @Test public void testSimpleBean() throws Exception { // start the MBeanExporter ConfigurableApplicationContext ctx = loadContext("org/springframework/jmx/export/notificationPublisherTests.xml"); @@ -60,6 +64,7 @@ public void testSimpleBean() throws Exception { assertEquals("Notification not sent", 1, listener.count); } + @Test public void testSimpleBeanRegisteredManually() throws Exception { // start the MBeanExporter ConfigurableApplicationContext ctx = loadContext("org/springframework/jmx/export/notificationPublisherTests.xml"); @@ -74,6 +79,7 @@ public void testSimpleBeanRegisteredManually() throws Exception { assertEquals("Notification not sent", 1, listener.count); } + @Test public void testMBean() throws Exception { // start the MBeanExporter ConfigurableApplicationContext ctx = loadContext("org/springframework/jmx/export/notificationPublisherTests.xml"); @@ -86,6 +92,7 @@ public void testMBean() throws Exception { } /* + @Test public void testStandardMBean() throws Exception { // start the MBeanExporter ApplicationContext ctx = new ClassPathXmlApplicationContext("org/springframework/jmx/export/notificationPublisherTests.xml"); @@ -97,6 +104,7 @@ public void testStandardMBean() throws Exception { } */ + @Test public void testLazyInit() throws Exception { // start the MBeanExporter ConfigurableApplicationContext ctx = loadContext("org/springframework/jmx/export/notificationPublisherLazyTests.xml"); diff --git a/spring-context/src/test/java/org/springframework/jmx/export/PropertyPlaceholderConfigurerTests.java b/spring-context/src/test/java/org/springframework/jmx/export/PropertyPlaceholderConfigurerTests.java index c95000c59da2..256372eb1bfd 100644 --- a/spring-context/src/test/java/org/springframework/jmx/export/PropertyPlaceholderConfigurerTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/export/PropertyPlaceholderConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of @@ -18,11 +18,15 @@ import javax.management.ObjectName; +import org.junit.Test; import org.springframework.jmx.AbstractJmxTests; import org.springframework.jmx.IJmxTestBean; +import static org.junit.Assert.*; + /** * @author Rob Harrop + * @author Chris Beams */ public class PropertyPlaceholderConfigurerTests extends AbstractJmxTests { @@ -31,6 +35,7 @@ protected String getApplicationContextPath() { return "org/springframework/jmx/export/propertyPlaceholderConfigurer.xml"; } + @Test public void testPropertiesReplaced() { IJmxTestBean bean = (IJmxTestBean) getContext().getBean("testBean"); @@ -38,6 +43,7 @@ public void testPropertiesReplaced() { assertEquals("Age is incorrect", 100, bean.getAge()); } + @Test public void testPropertiesCorrectInJmx() throws Exception { ObjectName oname = new ObjectName("bean:name=proxyTestBean1"); Object name = getServer().getAttribute(oname, "Name"); diff --git a/spring-context/src/test/java/org/springframework/jmx/export/annotation/AnnotationMetadataAssemblerTests.java b/spring-context/src/test/java/org/springframework/jmx/export/annotation/AnnotationMetadataAssemblerTests.java index d74787b7f81f..11c68dcb99fb 100644 --- a/spring-context/src/test/java/org/springframework/jmx/export/annotation/AnnotationMetadataAssemblerTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/export/annotation/AnnotationMetadataAssemblerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of @@ -20,15 +20,22 @@ import javax.management.modelmbean.ModelMBeanAttributeInfo; import javax.management.modelmbean.ModelMBeanOperationInfo; +import org.junit.Test; import org.springframework.jmx.IJmxTestBean; import org.springframework.jmx.export.assembler.AbstractMetadataAssemblerTests; import org.springframework.jmx.export.metadata.JmxAttributeSource; -/** @author Rob Harrop */ +import static org.junit.Assert.*; + +/** + * @author Rob Harrop + * @author Chris Beams + */ public class AnnotationMetadataAssemblerTests extends AbstractMetadataAssemblerTests { private static final String OBJECT_NAME = "bean:name=testBean4"; + @Test public void testAttributeFromInterface() throws Exception { ModelMBeanInfo inf = getMBeanInfoFromAssembler(); ModelMBeanAttributeInfo attr = inf.getAttribute("Colour"); @@ -36,12 +43,14 @@ public void testAttributeFromInterface() throws Exception { assertTrue("The name attribute should be readable", attr.isReadable()); } + @Test public void testOperationFromInterface() throws Exception { ModelMBeanInfo inf = getMBeanInfoFromAssembler(); ModelMBeanOperationInfo op = inf.getOperation("fromInterface"); assertNotNull(op); } + @Test public void testOperationOnGetter() throws Exception { ModelMBeanInfo inf = getMBeanInfoFromAssembler(); ModelMBeanOperationInfo op = inf.getOperation("getExpensiveToCalculate"); diff --git a/spring-context/src/test/java/org/springframework/jmx/export/assembler/AbstractJmxAssemblerTests.java b/spring-context/src/test/java/org/springframework/jmx/export/assembler/AbstractJmxAssemblerTests.java index 2c4a06e0bee4..42962c5f6426 100644 --- a/spring-context/src/test/java/org/springframework/jmx/export/assembler/AbstractJmxAssemblerTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/export/assembler/AbstractJmxAssemblerTests.java @@ -28,12 +28,16 @@ import javax.management.modelmbean.ModelMBeanInfo; import javax.management.modelmbean.ModelMBeanOperationInfo; +import org.junit.Test; import org.springframework.jmx.AbstractJmxTests; import org.springframework.jmx.IJmxTestBean; import org.springframework.jmx.support.ObjectNameManager; +import static org.junit.Assert.*; + /** * @author Rob Harrop + * @author Chris Beams */ public abstract class AbstractJmxAssemblerTests extends AbstractJmxTests { @@ -43,12 +47,14 @@ public abstract class AbstractJmxAssemblerTests extends AbstractJmxTests { protected abstract String getObjectName(); + @Test public void testMBeanRegistration() throws Exception { // beans are registered at this point - just grab them from the server ObjectInstance instance = getObjectInstance(); assertNotNull("Bean should not be null", instance); } + @Test public void testRegisterOperations() throws Exception { IJmxTestBean bean = getBean(); assertNotNull(bean); @@ -57,6 +63,7 @@ public void testRegisterOperations() throws Exception { getExpectedOperationCount(), inf.getOperations().length); } + @Test public void testRegisterAttributes() throws Exception { IJmxTestBean bean = getBean(); assertNotNull(bean); @@ -65,11 +72,13 @@ public void testRegisterAttributes() throws Exception { getExpectedAttributeCount(), inf.getAttributes().length); } + @Test public void testGetMBeanInfo() throws Exception { ModelMBeanInfo info = getMBeanInfoFromAssembler(); assertNotNull("MBeanInfo should not be null", info); } + @Test public void testGetMBeanAttributeInfo() throws Exception { ModelMBeanInfo info = getMBeanInfoFromAssembler(); MBeanAttributeInfo[] inf = info.getAttributes(); @@ -84,6 +93,7 @@ public void testGetMBeanAttributeInfo() throws Exception { } } + @Test public void testGetMBeanOperationInfo() throws Exception { ModelMBeanInfo info = getMBeanInfoFromAssembler(); MBeanOperationInfo[] inf = info.getOperations(); @@ -98,6 +108,7 @@ public void testGetMBeanOperationInfo() throws Exception { } } + @Test public void testDescriptionNotNull() throws Exception { ModelMBeanInfo info = getMBeanInfoFromAssembler(); @@ -105,6 +116,7 @@ public void testDescriptionNotNull() throws Exception { info.getDescription()); } + @Test public void testSetAttribute() throws Exception { ObjectName objectName = ObjectNameManager.getInstance(getObjectName()); getServer().setAttribute(objectName, new Attribute(NAME_ATTRIBUTE, "Rob Harrop")); @@ -112,6 +124,7 @@ public void testSetAttribute() throws Exception { assertEquals("Rob Harrop", bean.getName()); } + @Test public void testGetAttribute() throws Exception { ObjectName objectName = ObjectNameManager.getInstance(getObjectName()); getBean().setName("John Smith"); @@ -119,6 +132,7 @@ public void testGetAttribute() throws Exception { assertEquals("Incorrect result", "John Smith", val); } + @Test public void testOperationInvocation() throws Exception{ ObjectName objectName = ObjectNameManager.getInstance(getObjectName()); Object result = getServer().invoke(objectName, "add", @@ -126,6 +140,7 @@ public void testOperationInvocation() throws Exception{ assertEquals("Incorrect result", new Integer(50), result); } + @Test public void testAttributeInfoHasDescriptors() throws Exception { ModelMBeanInfo info = getMBeanInfoFromAssembler(); @@ -141,6 +156,7 @@ public void testAttributeInfoHasDescriptors() throws Exception { desc.getFieldValue("setMethod")); } + @Test public void testAttributeHasCorrespondingOperations() throws Exception { ModelMBeanInfo info = getMBeanInfoFromAssembler(); @@ -159,6 +175,7 @@ public void testAttributeHasCorrespondingOperations() throws Exception { assertEquals("set operation should have role \"setter\"", "setter", set.getDescriptor().getFieldValue("role")); } + @Test public void testNotificationMetadata() throws Exception { ModelMBeanInfo info = (ModelMBeanInfo) getMBeanInfo(); MBeanNotificationInfo[] notifications = info.getNotifications(); diff --git a/spring-context/src/test/java/org/springframework/jmx/export/assembler/AbstractMetadataAssemblerTests.java b/spring-context/src/test/java/org/springframework/jmx/export/assembler/AbstractMetadataAssemblerTests.java index 99cefe715835..0c3ece78395f 100644 --- a/spring-context/src/test/java/org/springframework/jmx/export/assembler/AbstractMetadataAssemblerTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/export/assembler/AbstractMetadataAssemblerTests.java @@ -26,6 +26,7 @@ import javax.management.modelmbean.ModelMBeanInfo; import javax.management.modelmbean.ModelMBeanOperationInfo; +import org.junit.Test; import org.springframework.aop.framework.ProxyFactory; import org.springframework.jmx.IJmxTestBean; import org.springframework.jmx.JmxTestBean; @@ -34,6 +35,8 @@ import org.springframework.jmx.support.ObjectNameManager; import org.springframework.tests.aop.interceptor.NopInterceptor; +import static org.junit.Assert.*; + /** * @author Rob Harrop * @author Chris Beams @@ -44,12 +47,14 @@ public abstract class AbstractMetadataAssemblerTests extends AbstractJmxAssemble protected static final String CACHE_ENTRIES_METRIC = "CacheEntries"; + @Test public void testDescription() throws Exception { ModelMBeanInfo info = getMBeanInfoFromAssembler(); assertEquals("The descriptions are not the same", "My Managed Bean", info.getDescription()); } + @Test public void testAttributeDescriptionOnSetter() throws Exception { ModelMBeanInfo inf = getMBeanInfoFromAssembler(); ModelMBeanAttributeInfo attr = inf.getAttribute(AGE_ATTRIBUTE); @@ -57,6 +62,7 @@ public void testAttributeDescriptionOnSetter() throws Exception { "The Age Attribute", attr.getDescription()); } + @Test public void testAttributeDescriptionOnGetter() throws Exception { ModelMBeanInfo inf = getMBeanInfoFromAssembler(); ModelMBeanAttributeInfo attr = inf.getAttribute(NAME_ATTRIBUTE); @@ -67,12 +73,14 @@ public void testAttributeDescriptionOnGetter() throws Exception { /** * Tests the situation where the attribute is only defined on the getter. */ + @Test public void testReadOnlyAttribute() throws Exception { ModelMBeanInfo inf = getMBeanInfoFromAssembler(); ModelMBeanAttributeInfo attr = inf.getAttribute(AGE_ATTRIBUTE); assertFalse("The age attribute should not be writable", attr.isWritable()); } + @Test public void testReadWriteAttribute() throws Exception { ModelMBeanInfo inf = getMBeanInfoFromAssembler(); ModelMBeanAttributeInfo attr = inf.getAttribute(NAME_ATTRIBUTE); @@ -83,6 +91,7 @@ public void testReadWriteAttribute() throws Exception { /** * Tests the situation where the property only has a getter. */ + @Test public void testWithOnlySetter() throws Exception { ModelMBeanInfo inf = getMBeanInfoFromAssembler(); ModelMBeanAttributeInfo attr = inf.getAttribute("NickName"); @@ -92,12 +101,14 @@ public void testWithOnlySetter() throws Exception { /** * Tests the situation where the property only has a setter. */ + @Test public void testWithOnlyGetter() throws Exception { ModelMBeanInfo info = getMBeanInfoFromAssembler(); ModelMBeanAttributeInfo attr = info.getAttribute("Superman"); assertNotNull("Attribute should not be null", attr); } + @Test public void testManagedResourceDescriptor() throws Exception { ModelMBeanInfo info = getMBeanInfoFromAssembler(); Descriptor desc = info.getMBeanDescriptor(); @@ -111,6 +122,7 @@ public void testManagedResourceDescriptor() throws Exception { assertEquals("Persist Name should be bar", "bar.jmx", desc.getFieldValue("persistName")); } + @Test public void testAttributeDescriptor() throws Exception { ModelMBeanInfo info = getMBeanInfoFromAssembler(); Descriptor desc = info.getAttribute(NAME_ATTRIBUTE).getDescriptor(); @@ -121,6 +133,7 @@ public void testAttributeDescriptor() throws Exception { assertEquals("Persist Period should be 300", "300", desc.getFieldValue("persistPeriod")); } + @Test public void testOperationDescriptor() throws Exception { ModelMBeanInfo info = getMBeanInfoFromAssembler(); Descriptor desc = info.getOperation("myOperation").getDescriptor(); @@ -129,6 +142,7 @@ public void testOperationDescriptor() throws Exception { assertEquals("Role should be \"operation\"", "operation", desc.getFieldValue("role")); } + @Test public void testOperationParameterMetadata() throws Exception { ModelMBeanInfo info = getMBeanInfoFromAssembler(); ModelMBeanOperationInfo oper = info.getOperation("add"); @@ -142,6 +156,7 @@ public void testOperationParameterMetadata() throws Exception { assertEquals("Incorrect type for y param", int.class.getName(), params[1].getType()); } + @Test public void testWithCglibProxy() throws Exception { IJmxTestBean tb = createJmxTestBean(); ProxyFactory pf = new ProxyFactory(); @@ -169,6 +184,7 @@ public void testWithCglibProxy() throws Exception { assertTrue("Not included in autodetection", assembler.includeBean(proxy.getClass(), "some bean name")); } + @Test public void testMetricDescription() throws Exception { ModelMBeanInfo inf = getMBeanInfoFromAssembler(); ModelMBeanAttributeInfo metric = inf.getAttribute(QUEUE_SIZE_METRIC); @@ -179,6 +195,7 @@ public void testMetricDescription() throws Exception { "The QueueSize metric", operation.getDescription()); } + @Test public void testMetricDescriptor() throws Exception { ModelMBeanInfo info = getMBeanInfoFromAssembler(); Descriptor desc = info.getAttribute(QUEUE_SIZE_METRIC).getDescriptor(); @@ -191,6 +208,7 @@ public void testMetricDescriptor() throws Exception { assertEquals("Metric Category should be utilization", "utilization",desc.getFieldValue("metricCategory")); } + @Test public void testMetricDescriptorDefaults() throws Exception { ModelMBeanInfo info = getMBeanInfoFromAssembler(); Descriptor desc = info.getAttribute(CACHE_ENTRIES_METRIC).getDescriptor(); diff --git a/spring-context/src/test/java/org/springframework/jmx/export/assembler/InterfaceBasedMBeanInfoAssemblerCustomTests.java b/spring-context/src/test/java/org/springframework/jmx/export/assembler/InterfaceBasedMBeanInfoAssemblerCustomTests.java index d4034ed4494c..e7bb1d68c9e5 100644 --- a/spring-context/src/test/java/org/springframework/jmx/export/assembler/InterfaceBasedMBeanInfoAssemblerCustomTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/export/assembler/InterfaceBasedMBeanInfoAssemblerCustomTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of @@ -19,8 +19,13 @@ import javax.management.modelmbean.ModelMBeanAttributeInfo; import javax.management.modelmbean.ModelMBeanInfo; +import org.junit.Test; + +import static org.junit.Assert.*; + /** * @author Rob Harrop + * @author Chris Beams */ public class InterfaceBasedMBeanInfoAssemblerCustomTests extends AbstractJmxAssemblerTests { @@ -48,6 +53,7 @@ protected MBeanInfoAssembler getAssembler() { return assembler; } + @Test public void testGetAgeIsReadOnly() throws Exception { ModelMBeanInfo info = getMBeanInfoFromAssembler(); ModelMBeanAttributeInfo attr = info.getAttribute(AGE_ATTRIBUTE); diff --git a/spring-context/src/test/java/org/springframework/jmx/export/assembler/InterfaceBasedMBeanInfoAssemblerMappedTests.java b/spring-context/src/test/java/org/springframework/jmx/export/assembler/InterfaceBasedMBeanInfoAssemblerMappedTests.java index 5cd8488eb7a7..1a485f377e6e 100644 --- a/spring-context/src/test/java/org/springframework/jmx/export/assembler/InterfaceBasedMBeanInfoAssemblerMappedTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/export/assembler/InterfaceBasedMBeanInfoAssemblerMappedTests.java @@ -22,13 +22,19 @@ import javax.management.modelmbean.ModelMBeanAttributeInfo; import javax.management.modelmbean.ModelMBeanInfo; +import org.junit.Test; + +import static org.junit.Assert.*; + /** * @author Rob Harrop + * @author Chris Beams */ public class InterfaceBasedMBeanInfoAssemblerMappedTests extends AbstractJmxAssemblerTests { protected static final String OBJECT_NAME = "bean:name=testBean4"; + @Test public void testGetAgeIsReadOnly() throws Exception { ModelMBeanInfo info = getMBeanInfoFromAssembler(); ModelMBeanAttributeInfo attr = info.getAttribute(AGE_ATTRIBUTE); @@ -37,6 +43,7 @@ public void testGetAgeIsReadOnly() throws Exception { assertFalse("Age is not writable", attr.isWritable()); } + @Test public void testWithUnknownClass() throws Exception { try { getWithMapping("com.foo.bar.Unknown"); @@ -47,6 +54,7 @@ public void testWithUnknownClass() throws Exception { } } + @Test public void testWithNonInterface() throws Exception { try { getWithMapping("JmxTestBean"); @@ -57,7 +65,8 @@ public void testWithNonInterface() throws Exception { } } - public void ignoreTestWithFallThrough() throws Exception { + @Test + public void testWithFallThrough() throws Exception { InterfaceBasedMBeanInfoAssembler assembler = getWithMapping("foobar", "org.springframework.jmx.export.assembler.ICustomJmxBean"); assembler.setManagedInterfaces(new Class[] {IAdditionalTestMethods.class}); @@ -68,6 +77,7 @@ public void ignoreTestWithFallThrough() throws Exception { assertNickName(attr); } + @Test public void testNickNameIsExposed() throws Exception { ModelMBeanInfo inf = (ModelMBeanInfo) getMBeanInfo(); MBeanAttributeInfo attr = inf.getAttribute("NickName"); diff --git a/spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodExclusionMBeanInfoAssemblerComboTests.java b/spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodExclusionMBeanInfoAssemblerComboTests.java index 2c2a8618eb12..ea234b09e8ca 100644 --- a/spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodExclusionMBeanInfoAssemblerComboTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodExclusionMBeanInfoAssemblerComboTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,14 +22,20 @@ import javax.management.modelmbean.ModelMBeanAttributeInfo; import javax.management.modelmbean.ModelMBeanInfo; +import org.junit.Test; + +import static org.junit.Assert.*; + /** * @author Juergen Hoeller * @author Rob Harrop + * @author Chris Beams */ public class MethodExclusionMBeanInfoAssemblerComboTests extends AbstractJmxAssemblerTests { protected static final String OBJECT_NAME = "bean:name=testBean4"; + @Test public void testGetAgeIsReadOnly() throws Exception { ModelMBeanInfo info = getMBeanInfoFromAssembler(); ModelMBeanAttributeInfo attr = info.getAttribute(AGE_ATTRIBUTE); @@ -37,6 +43,7 @@ public void testGetAgeIsReadOnly() throws Exception { assertFalse("Age is not writable", attr.isWritable()); } + @Test public void testNickNameIsExposed() throws Exception { ModelMBeanInfo inf = (ModelMBeanInfo) getMBeanInfo(); MBeanAttributeInfo attr = inf.getAttribute("NickName"); diff --git a/spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodExclusionMBeanInfoAssemblerMappedTests.java b/spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodExclusionMBeanInfoAssemblerMappedTests.java index 35fe1082703b..aa1e2d57c227 100644 --- a/spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodExclusionMBeanInfoAssemblerMappedTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodExclusionMBeanInfoAssemblerMappedTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,13 +22,19 @@ import javax.management.modelmbean.ModelMBeanAttributeInfo; import javax.management.modelmbean.ModelMBeanInfo; +import org.junit.Test; + +import static org.junit.Assert.*; + /** * @author Rob Harrop + * @author Chris Beams */ public class MethodExclusionMBeanInfoAssemblerMappedTests extends AbstractJmxAssemblerTests { protected static final String OBJECT_NAME = "bean:name=testBean4"; + @Test public void testGetAgeIsReadOnly() throws Exception { ModelMBeanInfo info = getMBeanInfoFromAssembler(); ModelMBeanAttributeInfo attr = info.getAttribute(AGE_ATTRIBUTE); @@ -36,6 +42,7 @@ public void testGetAgeIsReadOnly() throws Exception { assertFalse("Age is not writable", attr.isWritable()); } + @Test public void testNickNameIsExposed() throws Exception { ModelMBeanInfo inf = (ModelMBeanInfo) getMBeanInfo(); MBeanAttributeInfo attr = inf.getAttribute("NickName"); diff --git a/spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodExclusionMBeanInfoAssemblerNotMappedTests.java b/spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodExclusionMBeanInfoAssemblerNotMappedTests.java index f037d87bcb7d..6b12c74d971f 100644 --- a/spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodExclusionMBeanInfoAssemblerNotMappedTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodExclusionMBeanInfoAssemblerNotMappedTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,14 +22,20 @@ import javax.management.modelmbean.ModelMBeanAttributeInfo; import javax.management.modelmbean.ModelMBeanInfo; +import org.junit.Test; + +import static org.junit.Assert.*; + /** * @author Juergen Hoeller * @author Rob Harrop + * @author Chris Beams */ public class MethodExclusionMBeanInfoAssemblerNotMappedTests extends AbstractJmxAssemblerTests { protected static final String OBJECT_NAME = "bean:name=testBean4"; + @Test public void testGetAgeIsReadOnly() throws Exception { ModelMBeanInfo info = getMBeanInfoFromAssembler(); ModelMBeanAttributeInfo attr = info.getAttribute(AGE_ATTRIBUTE); @@ -37,6 +43,7 @@ public void testGetAgeIsReadOnly() throws Exception { assertTrue("Age is not writable", attr.isWritable()); } + @Test public void testNickNameIsExposed() throws Exception { ModelMBeanInfo inf = (ModelMBeanInfo) getMBeanInfo(); MBeanAttributeInfo attr = inf.getAttribute("NickName"); diff --git a/spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodExclusionMBeanInfoAssemblerTests.java b/spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodExclusionMBeanInfoAssemblerTests.java index 8a4929300664..53335e50d70f 100644 --- a/spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodExclusionMBeanInfoAssemblerTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodExclusionMBeanInfoAssemblerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,11 +22,15 @@ import javax.management.modelmbean.ModelMBeanAttributeInfo; import javax.management.modelmbean.ModelMBeanInfo; +import org.junit.Test; import org.springframework.jmx.JmxTestBean; +import static org.junit.Assert.*; + /** * @author Rob Harrop * @author Rick Evans + * @author Chris Beams */ public class MethodExclusionMBeanInfoAssemblerTests extends AbstractJmxAssemblerTests { @@ -60,6 +64,7 @@ protected MBeanInfoAssembler getAssembler() { return assembler; } + @Test public void testSupermanIsReadOnly() throws Exception { ModelMBeanInfo info = getMBeanInfoFromAssembler(); ModelMBeanAttributeInfo attr = info.getAttribute("Superman"); @@ -71,6 +76,7 @@ public void testSupermanIsReadOnly() throws Exception { /* * http://opensource.atlassian.com/projects/spring/browse/SPR-2754 */ + @Test public void testIsNotIgnoredDoesntIgnoreUnspecifiedBeanMethods() throws Exception { final String beanKey = "myTestBean"; MethodExclusionMBeanInfoAssembler assembler = new MethodExclusionMBeanInfoAssembler(); diff --git a/spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodNameBasedMBeanInfoAssemblerMappedTests.java b/spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodNameBasedMBeanInfoAssemblerMappedTests.java index 062a45febd79..10b1985515b3 100644 --- a/spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodNameBasedMBeanInfoAssemblerMappedTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodNameBasedMBeanInfoAssemblerMappedTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of @@ -16,18 +16,25 @@ package org.springframework.jmx.export.assembler; +import java.util.Properties; + import javax.management.MBeanAttributeInfo; import javax.management.modelmbean.ModelMBeanAttributeInfo; import javax.management.modelmbean.ModelMBeanInfo; -import java.util.Properties; + +import org.junit.Test; + +import static org.junit.Assert.*; /** * @author Rob Harrop + * @author Chris Beams */ public class MethodNameBasedMBeanInfoAssemblerMappedTests extends AbstractJmxAssemblerTests { protected static final String OBJECT_NAME = "bean:name=testBean4"; + @Test public void testGetAgeIsReadOnly() throws Exception { ModelMBeanInfo info = getMBeanInfoFromAssembler(); ModelMBeanAttributeInfo attr = info.getAttribute(AGE_ATTRIBUTE); @@ -36,6 +43,7 @@ public void testGetAgeIsReadOnly() throws Exception { assertFalse("Age is not writable", attr.isWritable()); } + @Test public void testWithFallThrough() throws Exception { MethodNameBasedMBeanInfoAssembler assembler = getWithMapping("foobar", "add,myOperation,getName,setName,getAge"); @@ -47,6 +55,7 @@ public void testWithFallThrough() throws Exception { assertNickName(attr); } + @Test public void testNickNameIsExposed() throws Exception { ModelMBeanInfo inf = (ModelMBeanInfo) getMBeanInfo(); MBeanAttributeInfo attr = inf.getAttribute("NickName"); diff --git a/spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodNameBasedMBeanInfoAssemblerTests.java b/spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodNameBasedMBeanInfoAssemblerTests.java index 72da54f6f360..d85f42b508e1 100644 --- a/spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodNameBasedMBeanInfoAssemblerTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodNameBasedMBeanInfoAssemblerTests.java @@ -20,9 +20,14 @@ import javax.management.modelmbean.ModelMBeanAttributeInfo; import javax.management.modelmbean.ModelMBeanInfo; +import org.junit.Test; + +import static org.junit.Assert.*; + /** * @author Rob Harrop * @author David Boden + * @author Chris Beams */ public class MethodNameBasedMBeanInfoAssemblerTests extends AbstractJmxAssemblerTests { @@ -50,6 +55,7 @@ protected MBeanInfoAssembler getAssembler() { return assembler; } + @Test public void testGetAgeIsReadOnly() throws Exception { ModelMBeanInfo info = getMBeanInfoFromAssembler(); ModelMBeanAttributeInfo attr = info.getAttribute(AGE_ATTRIBUTE); @@ -58,6 +64,7 @@ public void testGetAgeIsReadOnly() throws Exception { assertFalse(attr.isWritable()); } + @Test public void testSetNameParameterIsNamed() throws Exception { ModelMBeanInfo info = getMBeanInfoFromAssembler(); diff --git a/spring-context/src/test/java/org/springframework/jmx/support/ConnectorServerFactoryBeanTestsIgnore.java b/spring-context/src/test/java/org/springframework/jmx/support/ConnectorServerFactoryBeanTests.java similarity index 86% rename from spring-context/src/test/java/org/springframework/jmx/support/ConnectorServerFactoryBeanTestsIgnore.java rename to spring-context/src/test/java/org/springframework/jmx/support/ConnectorServerFactoryBeanTests.java index ef244b085e56..6d66caa79cf9 100644 --- a/spring-context/src/test/java/org/springframework/jmx/support/ConnectorServerFactoryBeanTestsIgnore.java +++ b/spring-context/src/test/java/org/springframework/jmx/support/ConnectorServerFactoryBeanTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of @@ -28,23 +28,36 @@ import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXServiceURL; -import org.junit.Ignore; +import org.junit.Test; import org.springframework.jmx.AbstractMBeanServerTests; +import org.springframework.tests.Assume; +import org.springframework.tests.TestGroup; + +import static org.junit.Assert.*; /** * @author Rob Harrop + * @author Chris Beams */ -// TODO [SPR-8089] Clean up ignored JMX tests. -// -// @Ignore should have no effect for JUnit 3.8 tests; however, it appears -// that tests on the CI server -- as well as those in Eclipse -- do in -// fact get ignored. So we leave @Ignore here so that developers can -// easily search for ignored tests. -@Ignore("Requires jmxremote_optional.jar; see comments in AbstractMBeanServerTests for details.") -public class ConnectorServerFactoryBeanTestsIgnore extends AbstractMBeanServerTests { +public class ConnectorServerFactoryBeanTests extends AbstractMBeanServerTests { private static final String OBJECT_NAME = "spring:type=connector,name=test"; + private boolean runTests = false; + + @Override + protected void onSetUp() throws Exception { + Assume.group(TestGroup.JMXMP); + runTests = true; + } + + @Override + public void tearDown() throws Exception { + if (runTests) { + super.tearDown(); + } + } + @Test public void testStartupWithLocatedServer() throws Exception { ConnectorServerFactoryBean bean = new ConnectorServerFactoryBean(); bean.afterPropertiesSet(); @@ -56,6 +69,7 @@ public void testStartupWithLocatedServer() throws Exception { } } + @Test public void testStartupWithSuppliedServer() throws Exception { //Added a brief snooze here - seems to fix occasional //java.net.BindException: Address already in use errors @@ -72,6 +86,7 @@ public void testStartupWithSuppliedServer() throws Exception { } } + @Test public void testRegisterWithMBeanServer() throws Exception { //Added a brief snooze here - seems to fix occasional //java.net.BindException: Address already in use errors @@ -89,6 +104,7 @@ public void testRegisterWithMBeanServer() throws Exception { } } + @Test public void testNoRegisterWithMBeanServer() throws Exception { ConnectorServerFactoryBean bean = new ConnectorServerFactoryBean(); bean.afterPropertiesSet(); diff --git a/spring-context/src/test/java/org/springframework/jmx/support/MBeanServerConnectionFactoryBeanTests.java b/spring-context/src/test/java/org/springframework/jmx/support/MBeanServerConnectionFactoryBeanTests.java index 598bc73e8c9b..c9c83cffaeed 100644 --- a/spring-context/src/test/java/org/springframework/jmx/support/MBeanServerConnectionFactoryBeanTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/support/MBeanServerConnectionFactoryBeanTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,9 +23,13 @@ import javax.management.remote.JMXConnectorServerFactory; import javax.management.remote.JMXServiceURL; -import org.junit.Ignore; +import org.junit.Test; import org.springframework.aop.support.AopUtils; import org.springframework.jmx.AbstractMBeanServerTests; +import org.springframework.tests.Assume; +import org.springframework.tests.TestGroup; + +import static org.junit.Assert.*; /** * @author Rob Harrop @@ -43,16 +47,9 @@ private JMXConnectorServer getConnectorServer() throws Exception { return JMXConnectorServerFactory.newJMXConnectorServer(getServiceUrl(), null, getServer()); } - // TODO [SPR-8089] Clean up ignored JMX tests. - // - // @Ignore should have no effect for JUnit 3.8 tests; however, it appears - // that tests on the CI server -- as well as those in Eclipse -- do in - // fact get ignored. So we leave @Ignore here so that developers can - // easily search for ignored tests. - // - // Once fixed, renamed to test* instead of ignore*. - @Ignore("Requires jmxremote_optional.jar; see comments in AbstractMBeanServerTests for details.") - public void ignoreTestValidConnection() throws Exception { + @Test + public void testTestValidConnection() throws Exception { + Assume.group(TestGroup.JMXMP); JMXConnectorServer connectorServer = getConnectorServer(); connectorServer.start(); @@ -75,6 +72,7 @@ public void ignoreTestValidConnection() throws Exception { } } + @Test public void testWithNoServiceUrl() throws Exception { MBeanServerConnectionFactoryBean bean = new MBeanServerConnectionFactoryBean(); try { @@ -85,16 +83,9 @@ public void testWithNoServiceUrl() throws Exception { } } - // TODO [SPR-8089] Clean up ignored JMX tests. - // - // @Ignore should have no effect for JUnit 3.8 tests; however, it appears - // that tests on the CI server -- as well as those in Eclipse -- do in - // fact get ignored. So we leave @Ignore here so that developers can - // easily search for ignored tests. - // - // Once fixed, renamed to test* instead of ignore*. - @Ignore("Requires jmxremote_optional.jar; see comments in AbstractMBeanServerTests for details.") - public void ignoreTestWithLazyConnection() throws Exception { + @Test + public void testTestWithLazyConnection() throws Exception { + Assume.group(TestGroup.JMXMP); MBeanServerConnectionFactoryBean bean = new MBeanServerConnectionFactoryBean(); bean.setServiceUrl(SERVICE_URL); bean.setConnectOnStartup(false); @@ -116,6 +107,7 @@ public void ignoreTestWithLazyConnection() throws Exception { } } + @Test public void testWithLazyConnectionAndNoAccess() throws Exception { MBeanServerConnectionFactoryBean bean = new MBeanServerConnectionFactoryBean(); bean.setServiceUrl(SERVICE_URL); diff --git a/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncExecutionTests.java b/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncExecutionTests.java index f679713eb96d..028f60c559d4 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncExecutionTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncExecutionTests.java @@ -66,6 +66,21 @@ public void asyncMethods() throws Exception { assertEquals("20", future.get()); } + @Test + public void asyncMethodsThroughInterface() throws Exception { + originalThreadName = Thread.currentThread().getName(); + GenericApplicationContext context = new GenericApplicationContext(); + context.registerBeanDefinition("asyncTest", new RootBeanDefinition(SimpleAsyncMethodBean.class)); + context.registerBeanDefinition("autoProxyCreator", new RootBeanDefinition(DefaultAdvisorAutoProxyCreator.class)); + context.registerBeanDefinition("asyncAdvisor", new RootBeanDefinition(AsyncAnnotationAdvisor.class)); + context.refresh(); + SimpleInterface asyncTest = context.getBean("asyncTest", SimpleInterface.class); + asyncTest.doNothing(5); + asyncTest.doSomething(10); + Future future = asyncTest.returnSomething(20); + assertEquals("20", future.get()); + } + @Test public void asyncMethodsWithQualifier() throws Exception { originalThreadName = Thread.currentThread().getName(); @@ -86,6 +101,26 @@ public void asyncMethodsWithQualifier() throws Exception { assertEquals("30", future2.get()); } + @Test + public void asyncMethodsWithQualifierThroughInterface() throws Exception { + originalThreadName = Thread.currentThread().getName(); + GenericApplicationContext context = new GenericApplicationContext(); + context.registerBeanDefinition("asyncTest", new RootBeanDefinition(SimpleAsyncMethodWithQualifierBean.class)); + context.registerBeanDefinition("autoProxyCreator", new RootBeanDefinition(DefaultAdvisorAutoProxyCreator.class)); + context.registerBeanDefinition("asyncAdvisor", new RootBeanDefinition(AsyncAnnotationAdvisor.class)); + context.registerBeanDefinition("e0", new RootBeanDefinition(ThreadPoolTaskExecutor.class)); + context.registerBeanDefinition("e1", new RootBeanDefinition(ThreadPoolTaskExecutor.class)); + context.registerBeanDefinition("e2", new RootBeanDefinition(ThreadPoolTaskExecutor.class)); + context.refresh(); + SimpleInterface asyncTest = context.getBean("asyncTest", SimpleInterface.class); + asyncTest.doNothing(5); + asyncTest.doSomething(10); + Future future = asyncTest.returnSomething(20); + assertEquals("20", future.get()); + Future future2 = asyncTest.returnSomething2(30); + assertEquals("30", future2.get()); + } + @Test public void asyncClass() throws Exception { originalThreadName = Thread.currentThread().getName(); @@ -177,6 +212,18 @@ public void asyncPrototypeClassListener() throws Exception { } + public interface SimpleInterface { + + void doNothing(int i); + + void doSomething(int i); + + Future returnSomething(int i); + + Future returnSomething2(int i); + } + + public static class AsyncMethodBean { public void doNothing(int i) { @@ -196,6 +243,15 @@ public Future returnSomething(int i) { } + public static class SimpleAsyncMethodBean extends AsyncMethodBean implements SimpleInterface { + + @Override + public Future returnSomething2(int i) { + throw new UnsupportedOperationException(); + } + } + + @Async("e0") public static class AsyncMethodWithQualifierBean { @@ -224,6 +280,10 @@ public Future returnSomething2(int i) { } + public static class SimpleAsyncMethodWithQualifierBean extends AsyncMethodWithQualifierBean implements SimpleInterface { + } + + @Async("e2") @Retention(RetentionPolicy.RUNTIME) public @interface MyAsync { diff --git a/spring-context/src/test/java/org/springframework/scheduling/support/CronSequenceGeneratorTests.java b/spring-context/src/test/java/org/springframework/scheduling/support/CronSequenceGeneratorTests.java new file mode 100644 index 000000000000..491706f9e31b --- /dev/null +++ b/spring-context/src/test/java/org/springframework/scheduling/support/CronSequenceGeneratorTests.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.scheduling.support; + +import java.util.Date; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * @author Juergen Hoeller + */ +public class CronSequenceGeneratorTests { + + @Test + public void testAt50Seconds() { + assertEquals(new Date(2012, 6, 2, 1, 0), + new CronSequenceGenerator("*/15 * 1-4 * * *").next(new Date(2012, 6, 1, 9, 53, 50))); + } + + @Test + public void testAt0Seconds() { + assertEquals(new Date(2012, 6, 2, 1, 0), + new CronSequenceGenerator("*/15 * 1-4 * * *").next(new Date(2012, 6, 1, 9, 53))); + } + + @Test + public void testAt0Minutes() { + assertEquals(new Date(2012, 6, 2, 1, 0), + new CronSequenceGenerator("0 */2 1-4 * * *").next(new Date(2012, 6, 1, 9, 0))); + } + +} diff --git a/spring-context/src/test/java/org/springframework/scheduling/support/CronTriggerTests.java b/spring-context/src/test/java/org/springframework/scheduling/support/CronTriggerTests.java index a99bffa1ed8c..90acc959b3b3 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/support/CronTriggerTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/support/CronTriggerTests.java @@ -41,7 +41,7 @@ @RunWith(Parameterized.class) public class CronTriggerTests { - private Calendar calendar = new GregorianCalendar(); + private final Calendar calendar = new GregorianCalendar(); private final Date date; @@ -49,8 +49,8 @@ public class CronTriggerTests { public CronTriggerTests(Date date, TimeZone timeZone) { - this.timeZone = timeZone; this.date = date; + this.timeZone = timeZone; } @Parameters @@ -66,6 +66,7 @@ private void roundup(Calendar calendar) { calendar.set(Calendar.MILLISECOND, 0); } + @Before public void setUp() { calendar.setTimeZone(timeZone); diff --git a/spring-context/src/test/java/org/springframework/validation/beanvalidation/ValidatorFactoryTests.java b/spring-context/src/test/java/org/springframework/validation/beanvalidation/ValidatorFactoryTests.java index 3e6cac575980..be4f45213a09 100644 --- a/spring-context/src/test/java/org/springframework/validation/beanvalidation/ValidatorFactoryTests.java +++ b/spring-context/src/test/java/org/springframework/validation/beanvalidation/ValidatorFactoryTests.java @@ -42,6 +42,7 @@ import org.springframework.validation.FieldError; import org.springframework.validation.ObjectError; +import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; /** @@ -102,6 +103,20 @@ public void testSimpleValidationWithClassLevel() throws Exception { assertTrue(cv.getConstraintDescriptor().getAnnotation() instanceof NameAddressValid); } + @Test + public void testSpringValidationFieldType() throws Exception { + LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean(); + validator.afterPropertiesSet(); + ValidPerson person = new ValidPerson(); + person.setName("Phil"); + person.getAddress().setStreet("Phil's Street"); + BeanPropertyBindingResult errors = new BeanPropertyBindingResult(person, "person"); + validator.validate(person, errors); + assertEquals(1, errors.getErrorCount()); + assertThat("Field/Value type mismatch", errors.getFieldError("address").getRejectedValue(), + instanceOf(ValidAddress.class)); + } + @Test public void testSpringValidation() throws Exception { LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean(); @@ -289,8 +304,13 @@ public void initialize(NameAddressValid constraintAnnotation) { } @Override - public boolean isValid(ValidPerson value, ConstraintValidatorContext constraintValidatorContext) { - return (value.name == null || !value.address.street.contains(value.name)); + public boolean isValid(ValidPerson value, ConstraintValidatorContext context) { + boolean valid = (value.name == null || !value.address.street.contains(value.name)); + if (!valid && "Phil".equals(value.name)) { + context.buildConstraintViolationWithTemplate( + context.getDefaultConstraintMessageTemplate()).addNode("address").addConstraintViolation().disableDefaultConstraintViolation(); + } + return valid; } } diff --git a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java index 3842a741c74f..7d8045b51ba6 100644 --- a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java +++ b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java @@ -369,8 +369,7 @@ static Type getRawType(Type genericType, Map typeVariableMap * all super types, enclosing types and interfaces. */ public static Map getTypeVariableMap(Class clazz) { - Map ref = typeVariableCache.get(clazz); - Map typeVariableMap = (ref != null ? ref : null); + Map typeVariableMap = typeVariableCache.get(clazz); if (typeVariableMap == null) { typeVariableMap = new HashMap(); diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/StringToEnumConverterFactory.java b/spring-core/src/main/java/org/springframework/core/convert/support/StringToEnumConverterFactory.java index 62cf4748eae4..7326b3f045f5 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/StringToEnumConverterFactory.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/StringToEnumConverterFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.ConverterFactory; +import org.springframework.util.Assert; /** * Converts from a String to a java.lang.Enum by calling {@link Enum#valueOf(Class, String)}. @@ -29,7 +30,13 @@ final class StringToEnumConverterFactory implements ConverterFactory { public Converter getConverter(Class targetType) { - return new StringToEnum(targetType); + Class enumType = targetType; + while(enumType != null && !enumType.isEnum()) { + enumType = enumType.getSuperclass(); + } + Assert.notNull(enumType, "The target type " + targetType.getName() + + " does not refer to an enum"); + return new StringToEnum(enumType); } private class StringToEnum implements Converter { diff --git a/spring-core/src/main/java/org/springframework/core/env/SystemEnvironmentPropertySource.java b/spring-core/src/main/java/org/springframework/core/env/SystemEnvironmentPropertySource.java index 09082aad5c22..0baba6a8c196 100644 --- a/spring-core/src/main/java/org/springframework/core/env/SystemEnvironmentPropertySource.java +++ b/spring-core/src/main/java/org/springframework/core/env/SystemEnvironmentPropertySource.java @@ -88,10 +88,6 @@ public boolean containsProperty(String name) { public Object getProperty(String name) { Assert.notNull(name, "property name must not be null"); String actualName = resolvePropertyName(name); - if (actualName == null) { - // at this point we know the property does not exist - return null; - } if (logger.isDebugEnabled() && !name.equals(actualName)) { logger.debug(String.format( "PropertySource [%s] does not contain '%s', but found equivalent '%s'", diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReader.java b/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReader.java index b2bf4405da72..af2d4c62d86b 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReader.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import java.io.InputStream; import org.springframework.asm.ClassReader; +import org.springframework.core.NestedIOException; import org.springframework.core.io.Resource; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.ClassMetadata; @@ -47,10 +48,14 @@ final class SimpleMetadataReader implements MetadataReader { SimpleMetadataReader(Resource resource, ClassLoader classLoader) throws IOException { InputStream is = new BufferedInputStream(resource.getInputStream()); - ClassReader classReader = null; + ClassReader classReader; try { classReader = new ClassReader(is); } + catch (IllegalArgumentException ex) { + throw new NestedIOException("ASM ClassReader failed to parse class file - " + + "probably due to a new Java class file version that isn't supported yet: " + resource, ex); + } finally { is.close(); } @@ -64,6 +69,7 @@ final class SimpleMetadataReader implements MetadataReader { this.resource = resource; } + public Resource getResource() { return this.resource; } diff --git a/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java b/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java index 0ae86e087925..e7fefa175d9d 100644 --- a/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java +++ b/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java @@ -317,7 +317,9 @@ else if (!StringUtils.hasText(pattern1)) { else if (!StringUtils.hasText(pattern2)) { return pattern1; } - else if (!pattern1.equals(pattern2) && !pattern1.contains("{") && match(pattern1, pattern2)) { + + boolean pattern1ContainsUriVar = pattern1.indexOf('{') != -1; + if (!pattern1.equals(pattern2) && !pattern1ContainsUriVar && match(pattern1, pattern2)) { // /* + /hotel -> /hotel ; "/*.*" + "/*.html" -> /*.html // However /user + /user -> /usr/user ; /{foo} + /bar -> /{foo}/bar return pattern2; @@ -344,7 +346,7 @@ else if (pattern1.endsWith("/**")) { } else { int dotPos1 = pattern1.indexOf('.'); - if (dotPos1 == -1) { + if (dotPos1 == -1 || pattern1ContainsUriVar) { // simply concatenate the two patterns if (pattern1.endsWith("/") || pattern2.startsWith("/")) { return pattern1 + pattern2; diff --git a/spring-core/src/main/java/org/springframework/util/FileCopyUtils.java b/spring-core/src/main/java/org/springframework/util/FileCopyUtils.java index 5aac30908663..2c14da3b517d 100644 --- a/spring-core/src/main/java/org/springframework/util/FileCopyUtils.java +++ b/spring-core/src/main/java/org/springframework/util/FileCopyUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,19 +31,19 @@ import java.io.Writer; /** - * Simple utility methods for file and stream copying. - * All copy methods use a block size of 4096 bytes, - * and close all affected streams when done. + * Simple utility methods for file and stream copying. All copy methods use a block size + * of 4096 bytes, and close all affected streams when done. A variation of the copy + * methods from this class that leave streams open can be found in {@link StreamUtils}. * - *

    Mainly for use within the framework, - * but also useful for application code. + *

    Mainly for use within the framework, but also useful for application code. * * @author Juergen Hoeller * @since 06.10.2003 + * @see StreamUtils */ public abstract class FileCopyUtils { - public static final int BUFFER_SIZE = 4096; + public static final int BUFFER_SIZE = StreamUtils.BUFFER_SIZE; //--------------------------------------------------------------------- @@ -106,15 +106,7 @@ public static int copy(InputStream in, OutputStream out) throws IOException { Assert.notNull(in, "No InputStream specified"); Assert.notNull(out, "No OutputStream specified"); try { - int byteCount = 0; - byte[] buffer = new byte[BUFFER_SIZE]; - int bytesRead = -1; - while ((bytesRead = in.read(buffer)) != -1) { - out.write(buffer, 0, bytesRead); - byteCount += bytesRead; - } - out.flush(); - return byteCount; + return StreamUtils.copy(in, out); } finally { try { @@ -208,7 +200,7 @@ public static int copy(Reader in, Writer out) throws IOException { /** * Copy the contents of the given String to the given output Writer. - * Closes the write when done. + * Closes the writer when done. * @param in the String to copy from * @param out the Writer to copy to * @throws IOException in case of I/O errors diff --git a/spring-core/src/main/java/org/springframework/util/StreamUtils.java b/spring-core/src/main/java/org/springframework/util/StreamUtils.java new file mode 100644 index 000000000000..cc3107d81ff0 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/util/StreamUtils.java @@ -0,0 +1,183 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.util; + +import java.io.ByteArrayOutputStream; +import java.io.FilterInputStream; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.Charset; + + +/** + * Simple utility methods for dealing with streams. The copy methods of this class are + * similar to those defined in {@link FileCopyUtils} except that all affected streams are + * left open when done. All copy methods use a block size of 4096 bytes. + * + *

    Mainly for use within the framework, but also useful for application code. + * + * @author Juergen Hoeller + * @author Phillip Webb + * @since 3.2.2 + * @see FileCopyUtils + */ +public abstract class StreamUtils { + + public static final int BUFFER_SIZE = 4096; + + + /** + * Copy the contents of the given InputStream into a new byte array. + * Leaves the stream open when done. + * @param in the stream to copy from + * @return the new byte array that has been copied to + * @throws IOException in case of I/O errors + */ + public static byte[] copyToByteArray(InputStream in) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(BUFFER_SIZE); + copy(in, out); + return out.toByteArray(); + } + + /** + * Copy the contents of the given InputStream into a String. + * Leaves the stream open when done. + * @param in the InputStream to copy from + * @return the String that has been copied to + * @throws IOException in case of I/O errors + */ + public static String copyToString(InputStream in, Charset charset) throws IOException { + Assert.notNull(in, "No InputStream specified"); + StringBuilder out = new StringBuilder(); + InputStreamReader reader = new InputStreamReader(in, charset); + char[] buffer = new char[BUFFER_SIZE]; + int bytesRead = -1; + while ((bytesRead = reader.read(buffer)) != -1) { + out.append(buffer, 0, bytesRead); + } + return out.toString(); + } + + /** + * Copy the contents of the given byte array to the given OutputStream. + * Leaves the stream open when done. + * @param in the byte array to copy from + * @param out the OutputStream to copy to + * @throws IOException in case of I/O errors + */ + public static void copy(byte[] in, OutputStream out) throws IOException { + Assert.notNull(in, "No input byte array specified"); + Assert.notNull(out, "No OutputStream specified"); + out.write(in); + } + + /** + * Copy the contents of the given String to the given output OutputStream. + * Leaves the stream open when done. + * @param in the String to copy from + * @param charset the Charset + * @param out the OutputStream to copy to + * @throws IOException in case of I/O errors + */ + public static void copy(String in, Charset charset, OutputStream out) throws IOException { + Assert.notNull(in, "No input String specified"); + Assert.notNull(charset, "No charset specified"); + Assert.notNull(out, "No OutputStream specified"); + Writer writer = new OutputStreamWriter(out, charset); + writer.write(in); + writer.flush(); + } + + /** + * Copy the contents of the given InputStream to the given OutputStream. + * Leaves both streams open when done. + * @param in the InputStream to copy from + * @param out the OutputStream to copy to + * @return the number of bytes copied + * @throws IOException in case of I/O errors + */ + public static int copy(InputStream in, OutputStream out) throws IOException { + Assert.notNull(in, "No InputStream specified"); + Assert.notNull(out, "No OutputStream specified"); + int byteCount = 0; + byte[] buffer = new byte[BUFFER_SIZE]; + int bytesRead = -1; + while ((bytesRead = in.read(buffer)) != -1) { + out.write(buffer, 0, bytesRead); + byteCount += bytesRead; + } + out.flush(); + return byteCount; + } + + /** + * Returns a variant of the given {@link InputStream} where calling + * {@link InputStream#close() close()} has no effect. + * @param in the InputStream to decorate + * @return a version of the InputStream that ignores calls to close + */ + public static InputStream nonClosing(InputStream in) { + Assert.notNull(in, "No InputStream specified"); + return new NonClosingInputStream(in); + } + + /** + * Returns a variant of the given {@link OutputStream} where calling + * {@link OutputStream#close() close()} has no effect. + * @param out the OutputStream to decorate + * @return a version of the OutputStream that ignores calls to close + */ + public static OutputStream nonClosing(OutputStream out) { + Assert.notNull(out, "No OutputStream specified"); + return new NonClosingOutputStream(out); + } + + + private static class NonClosingInputStream extends FilterInputStream { + + public NonClosingInputStream(InputStream in) { + super(in); + } + + @Override + public void close() throws IOException { + } + } + + + private static class NonClosingOutputStream extends FilterOutputStream { + + public NonClosingOutputStream(OutputStream out) { + super(out); + } + + @Override + public void write(byte[] b, int off, int let) throws IOException { + // It is critical that we override this method for performance + out.write(b, off, let); + } + + @Override + public void close() throws IOException { + } + } +} diff --git a/spring-core/src/main/java/org/springframework/util/xml/StaxResult.java b/spring-core/src/main/java/org/springframework/util/xml/StaxResult.java index c001902252aa..801769cdfab1 100644 --- a/spring-core/src/main/java/org/springframework/util/xml/StaxResult.java +++ b/spring-core/src/main/java/org/springframework/util/xml/StaxResult.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,24 +24,24 @@ import org.xml.sax.ContentHandler; /** - * Implementation of the {@code Result} tagging interface for StAX writers. Can be constructed with a - * {@code XMLEventConsumer} or a {@code XMLStreamWriter}. + * Implementation of the {@code Result} tagging interface for StAX writers. Can be constructed with + * an {@code XMLEventConsumer} or an {@code XMLStreamWriter}. * - *

    This class is necessary because there is no implementation of {@code Source} for StaxReaders in JAXP 1.3. - * There is a {@code StAXResult} in JAXP 1.4 (JDK 1.6), but this class is kept around for back-ward compatibility - * reasons. + *

    This class is necessary because there is no implementation of {@code Source} for StaxReaders + * in JAXP 1.3. There is a {@code StAXResult} in JAXP 1.4 (JDK 1.6), but this class is kept around + * for backwards compatibility reasons. * *

    Even though {@code StaxResult} extends from {@code SAXResult}, calling the methods of - * {@code SAXResult} is not supported. In general, the only supported operation on this class is - * to use the {@code ContentHandler} obtained via {@link #getHandler()} to parse an input source using an - * {@code XMLReader}. Calling {@link #setHandler(org.xml.sax.ContentHandler)} will result in - * {@code UnsupportedOperationException}s. + * {@code SAXResult} is not supported. In general, the only supported operation + * on this class is to use the {@code ContentHandler} obtained via {@link #getHandler()} to parse an + * input source using an {@code XMLReader}. Calling {@link #setHandler(org.xml.sax.ContentHandler)} + * will result in {@code UnsupportedOperationException}s. * * @author Arjen Poutsma + * @since 3.0 * @see XMLEventWriter * @see XMLStreamWriter * @see javax.xml.transform.Transformer - * @since 3.0 */ class StaxResult extends SAXResult { @@ -49,9 +49,9 @@ class StaxResult extends SAXResult { private XMLStreamWriter streamWriter; + /** - * Constructs a new instance of the {@code StaxResult} with the specified {@code XMLStreamWriter}. - * + * Construct a new instance of the {@code StaxResult} with the specified {@code XMLStreamWriter}. * @param streamWriter the {@code XMLStreamWriter} to write to */ StaxResult(XMLStreamWriter streamWriter) { @@ -60,8 +60,7 @@ class StaxResult extends SAXResult { } /** - * Constructs a new instance of the {@code StaxResult} with the specified {@code XMLEventWriter}. - * + * Construct a new instance of the {@code StaxResult} with the specified {@code XMLEventWriter}. * @param eventWriter the {@code XMLEventWriter} to write to */ StaxResult(XMLEventWriter eventWriter) { @@ -70,9 +69,8 @@ class StaxResult extends SAXResult { } /** - * Constructs a new instance of the {@code StaxResult} with the specified {@code XMLEventWriter} and - * {@code XMLEventFactory}. - * + * Construct a new instance of the {@code StaxResult} with the specified {@code XMLEventWriter} + * and {@code XMLEventFactory}. * @param eventWriter the {@code XMLEventWriter} to write to * @param eventFactory the {@code XMLEventFactory} to use for creating events */ @@ -81,35 +79,35 @@ class StaxResult extends SAXResult { this.eventWriter = eventWriter; } + /** - * Returns the {@code XMLEventWriter} used by this {@code StaxResult}. If this {@code StaxResult} was - * created with an {@code XMLStreamWriter}, the result will be {@code null}. - * + * Return the {@code XMLEventWriter} used by this {@code StaxResult}. If this {@code StaxResult} + * was created with an {@code XMLStreamWriter}, the result will be {@code null}. * @return the StAX event writer used by this result * @see #StaxResult(javax.xml.stream.XMLEventWriter) */ XMLEventWriter getXMLEventWriter() { - return eventWriter; + return this.eventWriter; } /** - * Returns the {@code XMLStreamWriter} used by this {@code StaxResult}. If this {@code StaxResult} was - * created with an {@code XMLEventConsumer}, the result will be {@code null}. - * + * Return the {@code XMLStreamWriter} used by this {@code StaxResult}. If this {@code StaxResult} + * was created with an {@code XMLEventConsumer}, the result will be {@code null}. * @return the StAX stream writer used by this result * @see #StaxResult(javax.xml.stream.XMLStreamWriter) */ XMLStreamWriter getXMLStreamWriter() { - return streamWriter; + return this.streamWriter; } + /** - * Throws a {@code UnsupportedOperationException}. - * + * Throws an {@code UnsupportedOperationException}. * @throws UnsupportedOperationException always */ @Override public void setHandler(ContentHandler handler) { throw new UnsupportedOperationException("setHandler is not supported"); } + } diff --git a/spring-core/src/main/java/org/springframework/util/xml/StaxSource.java b/spring-core/src/main/java/org/springframework/util/xml/StaxSource.java index af005ecc1497..5706a4317ed5 100644 --- a/spring-core/src/main/java/org/springframework/util/xml/StaxSource.java +++ b/spring-core/src/main/java/org/springframework/util/xml/StaxSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,24 +24,24 @@ import org.xml.sax.XMLReader; /** - * Implementation of the {@code Source} tagging interface for StAX readers. Can be constructed with a - * {@code XMLEventReader} or a {@code XMLStreamReader}. + * Implementation of the {@code Source} tagging interface for StAX readers. Can be constructed with + * an {@code XMLEventReader} or an {@code XMLStreamReader}. * - *

    This class is necessary because there is no implementation of {@code Source} for StAX Readers in JAXP 1.3. - * There is a {@code StAXSource} in JAXP 1.4 (JDK 1.6), but this class is kept around for back-ward compatibility - * reasons. + *

    This class is necessary because there is no implementation of {@code Source} for StAX Readers + * in JAXP 1.3. There is a {@code StAXSource} in JAXP 1.4 (JDK 1.6), but this class is kept around + * for backwards compatibility reasons. * *

    Even though {@code StaxSource} extends from {@code SAXSource}, calling the methods of - * {@code SAXSource} is not supported. In general, the only supported operation on this class is - * to use the {@code XMLReader} obtained via {@link #getXMLReader()} to parse the input source obtained via {@link - * #getInputSource()}. Calling {@link #setXMLReader(XMLReader)} or {@link #setInputSource(InputSource)} will result in - * {@code UnsupportedOperationException}s. + * {@code SAXSource} is not supported. In general, the only supported operation + * on this class is to use the {@code XMLReader} obtained via {@link #getXMLReader()} to parse the + * input source obtained via {@link #getInputSource()}. Calling {@link #setXMLReader(XMLReader)} + * or {@link #setInputSource(InputSource)} will result in {@code UnsupportedOperationException}s. * * @author Arjen Poutsma + * @since 3.0 * @see XMLEventReader * @see XMLStreamReader * @see javax.xml.transform.Transformer - * @since 3.0 */ class StaxSource extends SAXSource { @@ -49,11 +49,11 @@ class StaxSource extends SAXSource { private XMLStreamReader streamReader; + /** - * Constructs a new instance of the {@code StaxSource} with the specified {@code XMLStreamReader}. The - * supplied stream reader must be in {@code XMLStreamConstants.START_DOCUMENT} or + * Construct a new instance of the {@code StaxSource} with the specified {@code XMLStreamReader}. + * The supplied stream reader must be in {@code XMLStreamConstants.START_DOCUMENT} or * {@code XMLStreamConstants.START_ELEMENT} state. - * * @param streamReader the {@code XMLStreamReader} to read from * @throws IllegalStateException if the reader is not at the start of a document or element */ @@ -63,10 +63,9 @@ class StaxSource extends SAXSource { } /** - * Constructs a new instance of the {@code StaxSource} with the specified {@code XMLEventReader}. The - * supplied event reader must be in {@code XMLStreamConstants.START_DOCUMENT} or + * Construct a new instance of the {@code StaxSource} with the specified {@code XMLEventReader}. + * The supplied event reader must be in {@code XMLStreamConstants.START_DOCUMENT} or * {@code XMLStreamConstants.START_ELEMENT} state. - * * @param eventReader the {@code XMLEventReader} to read from * @throws IllegalStateException if the reader is not at the start of a document or element */ @@ -75,31 +74,30 @@ class StaxSource extends SAXSource { this.eventReader = eventReader; } + /** - * Returns the {@code XMLEventReader} used by this {@code StaxSource}. If this {@code StaxSource} was - * created with an {@code XMLStreamReader}, the result will be {@code null}. - * + * Return the {@code XMLEventReader} used by this {@code StaxSource}. If this {@code StaxSource} + * was created with an {@code XMLStreamReader}, the result will be {@code null}. * @return the StAX event reader used by this source * @see StaxSource#StaxSource(javax.xml.stream.XMLEventReader) */ XMLEventReader getXMLEventReader() { - return eventReader; + return this.eventReader; } /** - * Returns the {@code XMLStreamReader} used by this {@code StaxSource}. If this {@code StaxSource} was - * created with an {@code XMLEventReader}, the result will be {@code null}. - * + * Return the {@code XMLStreamReader} used by this {@code StaxSource}. If this {@code StaxSource} + * was created with an {@code XMLEventReader}, the result will be {@code null}. * @return the StAX event reader used by this source * @see StaxSource#StaxSource(javax.xml.stream.XMLEventReader) */ XMLStreamReader getXMLStreamReader() { - return streamReader; + return this.streamReader; } + /** - * Throws a {@code UnsupportedOperationException}. - * + * Throws an {@code UnsupportedOperationException}. * @throws UnsupportedOperationException always */ @Override @@ -108,12 +106,12 @@ public void setInputSource(InputSource inputSource) { } /** - * Throws a {@code UnsupportedOperationException}. - * + * Throws an {@code UnsupportedOperationException}. * @throws UnsupportedOperationException always */ @Override public void setXMLReader(XMLReader reader) { throw new UnsupportedOperationException("setXMLReader is not supported"); } + } diff --git a/spring-core/src/main/java/org/springframework/util/xml/StaxUtils.java b/spring-core/src/main/java/org/springframework/util/xml/StaxUtils.java index 576f39ebcf56..efafa259d43c 100644 --- a/spring-core/src/main/java/org/springframework/util/xml/StaxUtils.java +++ b/spring-core/src/main/java/org/springframework/util/xml/StaxUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,14 +45,15 @@ */ public abstract class StaxUtils { + // JAXP 1.4 is only available on JDK 1.6+ private static boolean jaxp14Available = ClassUtils.isPresent("javax.xml.transform.stax.StAXSource", StaxUtils.class.getClassLoader()); + // Stax Source /** * Create a custom, non-JAXP 1.4 StAX {@link Source} for the given {@link XMLStreamReader}. - * * @param streamReader the StAX stream reader * @return a source wrapping the {@code streamReader} */ @@ -62,9 +63,8 @@ public static Source createCustomStaxSource(XMLStreamReader streamReader) { /** * Create a StAX {@link Source} for the given {@link XMLStreamReader}. - * - *

    If JAXP 1.4 is available, this method returns a {@link StAXSource}; otherwise it returns a - * custom StAX Source. + *

    If JAXP 1.4 is available, this method returns a {@link StAXSource}; + * otherwise it returns a custom StAX Source. * @param streamReader the StAX stream reader * @return a source wrapping the {@code streamReader} * @see #createCustomStaxSource(XMLStreamReader) @@ -80,7 +80,6 @@ public static Source createStaxSource(XMLStreamReader streamReader) { /** * Create a custom, non-JAXP 1.4 StAX {@link Source} for the given {@link XMLEventReader}. - * * @param eventReader the StAX event reader * @return a source wrapping the {@code eventReader} */ @@ -90,9 +89,8 @@ public static Source createCustomStaxSource(XMLEventReader eventReader) { /** * Create a StAX {@link Source} for the given {@link XMLEventReader}. - * - *

    If JAXP 1.4 is available, this method returns a {@link StAXSource}; otherwise it returns a - * custom StAX Source. + *

    If JAXP 1.4 is available, this method returns a {@link StAXSource}; + * otherwise it returns a custom StAX Source. * @param eventReader the StAX event reader * @return a source wrapping the {@code eventReader} * @throws XMLStreamException in case of StAX errors @@ -116,11 +114,11 @@ public static boolean isStaxSource(Source source) { return (source instanceof StaxSource || (jaxp14Available && Jaxp14StaxHandler.isStaxSource(source))); } + // Stax Result /** * Create a custom, non-JAXP 1.4 StAX {@link Result} for the given {@link XMLStreamWriter}. - * * @param streamWriter the StAX stream writer * @return a source wrapping the {@code streamWriter} */ @@ -130,9 +128,8 @@ public static Result createCustomStaxResult(XMLStreamWriter streamWriter) { /** * Create a StAX {@link Result} for the given {@link XMLStreamWriter}. - * - *

    If JAXP 1.4 is available, this method returns a {@link StAXResult}; otherwise it returns a - * custom StAX Result. + *

    If JAXP 1.4 is available, this method returns a {@link StAXResult}; + * otherwise it returns a custom StAX Result. * @param streamWriter the StAX stream writer * @return a result wrapping the {@code streamWriter} * @see #createCustomStaxResult(XMLStreamWriter) @@ -148,7 +145,6 @@ public static Result createStaxResult(XMLStreamWriter streamWriter) { /** * Create a custom, non-JAXP 1.4 StAX {@link Result} for the given {@link XMLEventWriter}. - * * @param eventWriter the StAX event writer * @return a source wrapping the {@code eventWriter} */ @@ -158,7 +154,6 @@ public static Result createCustomStaxResult(XMLEventWriter eventWriter) { /** * Create a StAX {@link Result} for the given {@link XMLEventWriter}. - * *

    If JAXP 1.4 is available, this method returns a {@link StAXResult}; otherwise it returns a * custom StAX Result. * @param eventWriter the StAX event writer @@ -177,8 +172,8 @@ public static Result createStaxResult(XMLEventWriter eventWriter) throws XMLStre /** * Indicate whether the given {@link javax.xml.transform.Result} is a StAX Result. - * @return {@code true} if {@code result} is a custom Stax Result or JAXP - * 1.4 {@link StAXResult}; {@code false} otherwise. + * @return {@code true} if {@code result} is a custom Stax Result or JAXP 1.4 + * {@link StAXResult}; {@code false} otherwise. */ public static boolean isStaxResult(Result result) { return (result instanceof StaxResult || (jaxp14Available && Jaxp14StaxHandler.isStaxResult(result))); @@ -327,6 +322,7 @@ public static XMLStreamWriter createEventStreamWriter(XMLEventWriter eventWriter return new XMLEventStreamWriter(eventWriter, eventFactory); } + /** * Inner class to avoid a static JAXP 1.4 dependency. */ @@ -349,11 +345,11 @@ private static Result createStaxResult(XMLEventWriter eventWriter) { } private static boolean isStaxSource(Source source) { - return source instanceof StAXSource; + return (source instanceof StAXSource); } private static boolean isStaxResult(Result result) { - return result instanceof StAXResult; + return (result instanceof StAXResult); } private static XMLStreamReader getXMLStreamReader(Source source) { diff --git a/spring-core/src/test/java/org/springframework/core/convert/support/DefaultConversionTests.java b/spring-core/src/test/java/org/springframework/core/convert/support/DefaultConversionTests.java index 7b2ff0490a11..ba42cddf9daa 100644 --- a/spring-core/src/test/java/org/springframework/core/convert/support/DefaultConversionTests.java +++ b/spring-core/src/test/java/org/springframework/core/convert/support/DefaultConversionTests.java @@ -17,6 +17,7 @@ package org.springframework.core.convert.support; import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.*; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -212,6 +213,11 @@ public void testStringToEnum() throws Exception { assertEquals(Foo.BAR, conversionService.convert("BAR", Foo.class)); } + @Test + public void testStringToEnumWithSubclss() throws Exception { + assertEquals(SubFoo.BAZ, conversionService.convert("BAZ", SubFoo.BAR.getClass())); + } + @Test public void testStringToEnumEmptyString() { assertEquals(null, conversionService.convert("", Foo.class)); @@ -226,6 +232,24 @@ public static enum Foo { BAR, BAZ; } + public static enum SubFoo { + + BAR { + @Override + String s() { + return "x"; + } + }, + BAZ { + @Override + String s() { + return "y"; + } + }; + + abstract String s(); + } + @Test public void testStringToLocale() { assertEquals(Locale.ENGLISH, conversionService.convert("en", Locale.class)); diff --git a/spring-core/src/test/java/org/springframework/tests/TestGroup.java b/spring-core/src/test/java/org/springframework/tests/TestGroup.java index dcc376eea494..662f0dab532f 100644 --- a/spring-core/src/test/java/org/springframework/tests/TestGroup.java +++ b/spring-core/src/test/java/org/springframework/tests/TestGroup.java @@ -21,6 +21,10 @@ import java.util.HashSet; import java.util.Set; +import org.springframework.util.StringUtils; + +import static java.lang.String.*; + /** * A test group used to limit when certain tests are run. * @@ -44,7 +48,13 @@ public enum TestGroup { * {@code StopWatch}, etc. should be considered a candidate as their successful * execution is likely to be based on events occurring within a given time window. */ - PERFORMANCE; + PERFORMANCE, + + /** + * Tests requiring the presence of jmxremote_optional.jar in jre/lib/ext in order to + * avoid "Unsupported protocol: jmxmp" errors. + */ + JMXMP; /** @@ -64,8 +74,10 @@ public static Set parse(String value) { try { groups.add(valueOf(group.trim().toUpperCase())); } catch (IllegalArgumentException e) { - throw new IllegalArgumentException("Unable to find test group '" + group.trim() - + "' when parsing '" + value + "'"); + throw new IllegalArgumentException(format( + "Unable to find test group '%s' when parsing testGroups value: '%s'. " + + "Available groups include: [%s]", group.trim(), value, + StringUtils.arrayToCommaDelimitedString(TestGroup.values()))); } } return groups; diff --git a/spring-core/src/test/java/org/springframework/tests/TestGroupTests.java b/spring-core/src/test/java/org/springframework/tests/TestGroupTests.java index 2b4860fd7284..7f9fe924c4b4 100644 --- a/spring-core/src/test/java/org/springframework/tests/TestGroupTests.java +++ b/spring-core/src/test/java/org/springframework/tests/TestGroupTests.java @@ -62,7 +62,9 @@ public void parseInMixedCase() throws Exception { @Test public void parseMissing() throws Exception { thrown.expect(IllegalArgumentException.class); - thrown.expectMessage("Unable to find test group 'missing' when parsing 'performance, missing'"); + thrown.expectMessage("Unable to find test group 'missing' when parsing " + + "testGroups value: 'performance, missing'. Available groups include: " + + "[LONG_RUNNING,PERFORMANCE,JMXMP]"); TestGroup.parse("performance, missing"); } diff --git a/spring-core/src/test/java/org/springframework/util/AntPathMatcherTests.java b/spring-core/src/test/java/org/springframework/util/AntPathMatcherTests.java index 0a34e1c470ce..05a98f405d4c 100644 --- a/spring-core/src/test/java/org/springframework/util/AntPathMatcherTests.java +++ b/spring-core/src/test/java/org/springframework/util/AntPathMatcherTests.java @@ -412,6 +412,7 @@ public void combine() { assertEquals("/*.html", pathMatcher.combine("/*.*", "/*.html")); assertEquals("/{foo}/bar", pathMatcher.combine("/{foo}", "/bar")); // SPR-8858 assertEquals("/user/user", pathMatcher.combine("/user", "/user")); // SPR-7970 + assertEquals("/{foo:.*[^0-9].*}/edit/", pathMatcher.combine("/{foo:.*[^0-9].*}", "/edit/")); // SPR-10062 } @Test diff --git a/spring-core/src/test/java/org/springframework/util/StreamUtilsTests.java b/spring-core/src/test/java/org/springframework/util/StreamUtilsTests.java new file mode 100644 index 000000000000..fe10d2359673 --- /dev/null +++ b/spring-core/src/test/java/org/springframework/util/StreamUtilsTests.java @@ -0,0 +1,129 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.util; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.util.Random; +import java.util.UUID; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InOrder; + +/** + * Tests for {@link StreamUtils}. + * + * @author Phillip Webb + */ +public class StreamUtilsTests { + + private byte[] bytes = new byte[StreamUtils.BUFFER_SIZE + 10]; + + private String string = ""; + + @Before + public void setup() { + new Random().nextBytes(bytes); + while (string.length() < StreamUtils.BUFFER_SIZE + 10) { + string += UUID.randomUUID().toString(); + } + } + + @Test + public void copyToByteArray() throws Exception { + InputStream inputStream = spy(new ByteArrayInputStream(bytes)); + byte[] actual = StreamUtils.copyToByteArray(inputStream); + assertThat(actual, equalTo(bytes)); + verify(inputStream, never()).close(); + } + + @Test + public void copyToString() throws Exception { + Charset charset = Charset.defaultCharset(); + InputStream inputStream = spy(new ByteArrayInputStream(string.getBytes(charset))); + String actual = StreamUtils.copyToString(inputStream, charset); + assertThat(actual, equalTo(string)); + verify(inputStream, never()).close(); + } + + @Test + public void copyBytes() throws Exception { + ByteArrayOutputStream out = spy(new ByteArrayOutputStream()); + StreamUtils.copy(bytes, out); + assertThat(out.toByteArray(), equalTo(bytes)); + verify(out, never()).close(); + } + + @Test + public void copyString() throws Exception { + Charset charset = Charset.defaultCharset(); + ByteArrayOutputStream out = spy(new ByteArrayOutputStream()); + StreamUtils.copy(string, charset, out); + assertThat(out.toByteArray(), equalTo(string.getBytes(charset))); + verify(out, never()).close(); + } + + @Test + public void copyStream() throws Exception { + ByteArrayOutputStream out = spy(new ByteArrayOutputStream()); + StreamUtils.copy(new ByteArrayInputStream(bytes), out); + assertThat(out.toByteArray(), equalTo(bytes)); + verify(out, never()).close(); + } + + @Test + public void nonClosingInputStream() throws Exception { + InputStream source = mock(InputStream.class); + InputStream nonClosing = StreamUtils.nonClosing(source); + nonClosing.read(); + nonClosing.read(bytes); + nonClosing.read(bytes, 1, 2); + nonClosing.close(); + InOrder ordered = inOrder(source); + ordered.verify(source).read(); + ordered.verify(source).read(bytes, 0, bytes.length); + ordered.verify(source).read(bytes, 1, 2); + ordered.verify(source, never()).close(); + } + + @Test + public void nonClosingOutputStream() throws Exception { + OutputStream source = mock(OutputStream.class); + OutputStream nonClosing = StreamUtils.nonClosing(source); + nonClosing.write(1); + nonClosing.write(bytes); + nonClosing.write(bytes, 1, 2); + nonClosing.close(); + InOrder ordered = inOrder(source); + ordered.verify(source).write(1); + ordered.verify(source).write(bytes, 0, bytes.length); + ordered.verify(source).write(bytes, 1, 2); + ordered.verify(source, never()).close(); + } +} diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java b/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java index d9ae1a2efb13..3f89bed5121c 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java @@ -109,7 +109,8 @@ public enum SpelMessage { OPERAND_NOT_DECREMENTABLE(Kind.ERROR,1067,"the expression component ''{0}'' does not support decrement"), // NOT_ASSIGNABLE(Kind.ERROR,1068,"the expression component ''{0}'' is not assignable"), // MISSING_CHARACTER(Kind.ERROR,1069,"missing expected character ''{0}''"), - LEFT_OPERAND_PROBLEM(Kind.ERROR,1070, "Problem parsing left operand"); + LEFT_OPERAND_PROBLEM(Kind.ERROR,1070, "Problem parsing left operand"), + MISSING_SELECTION_EXPRESSION(Kind.ERROR, 1071, "A required selection expression has not been specified"); private Kind kind; private int code; diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Selection.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Selection.java index 7489c6ba6969..710e97592163 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Selection.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Selection.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ import org.springframework.expression.spel.ExpressionState; import org.springframework.expression.spel.SpelEvaluationException; import org.springframework.expression.spel.SpelMessage; +import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; @@ -54,7 +55,9 @@ public class Selection extends SpelNodeImpl { private final boolean nullSafe; public Selection(boolean nullSafe, int variant,int pos,SpelNodeImpl expression) { - super(pos,expression); + super(pos, expression != null ? new SpelNodeImpl[] { expression } + : new SpelNodeImpl[] {}); + Assert.notNull(expression, "Expression must not be null"); this.nullSafe = nullSafe; this.variant = variant; } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java b/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java index c243b5d7f4b4..7915a3c01572 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java @@ -557,6 +557,9 @@ private boolean maybeEatSelection(boolean nullSafeNavigation) { } nextToken(); SpelNodeImpl expr = eatExpression(); + if(expr == null) { + raiseInternalException(toPos(t), SpelMessage.MISSING_SELECTION_EXPRESSION); + } eatToken(TokenKind.RSQUARE); if (t.kind==TokenKind.SELECT_FIRST) { constructedNodes.push(new Selection(nullSafeNavigation,Selection.FIRST,toPos(t),expr)); diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java index c086771cbed8..ce6ad5123be5 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,13 +19,16 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; +import org.springframework.core.BridgeMethodResolver; import org.springframework.core.MethodParameter; import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.AccessException; @@ -37,7 +40,6 @@ import org.springframework.expression.TypeConverter; import org.springframework.expression.spel.SpelEvaluationException; import org.springframework.expression.spel.SpelMessage; -import org.springframework.util.CollectionUtils; /** * Reflection-based {@link MethodResolver} used by default in @@ -92,25 +94,16 @@ public MethodExecutor resolve(EvaluationContext context, Object targetObject, St try { TypeConverter typeConverter = context.getTypeConverter(); Class type = (targetObject instanceof Class ? (Class) targetObject : targetObject.getClass()); - Method[] methods = getMethods(type, targetObject); + List methods = new ArrayList(Arrays.asList(getMethods(type, targetObject))); // If a filter is registered for this type, call it MethodFilter filter = (this.filters != null ? this.filters.get(type) : null); if (filter != null) { - List methodsForFiltering = new ArrayList(); - for (Method method: methods) { - methodsForFiltering.add(method); - } - List methodsFiltered = filter.filter(methodsForFiltering); - if (CollectionUtils.isEmpty(methodsFiltered)) { - methods = NO_METHODS; - } - else { - methods = methodsFiltered.toArray(new Method[methodsFiltered.size()]); - } + methods = filter.filter(methods); } - Arrays.sort(methods, new Comparator() { + // Sort methods into a sensible order + Collections.sort(methods, new Comparator() { public int compare(Method m1, Method m2) { int m1pl = m1.getParameterTypes().length; int m2pl = m2.getParameterTypes().length; @@ -118,6 +111,14 @@ public int compare(Method m1, Method m2) { } }); + // Resolve any bridge methods + for (int i = 0; i < methods.size(); i++) { + methods.set(i, BridgeMethodResolver.findBridgedMethod(methods.get(i))); + } + + // Remove duplicate methods (possible due to resolved bridge methods) + methods = new ArrayList(new LinkedHashSet(methods)); + Method closeMatch = null; int closeMatchDistance = Integer.MAX_VALUE; int[] argsToConvert = null; @@ -125,9 +126,6 @@ public int compare(Method m1, Method m2) { boolean multipleOptions = false; for (Method method : methods) { - if (method.isBridge()) { - continue; - } if (method.getName().equals(name)) { Class[] paramTypes = method.getParameterTypes(); List paramDescriptors = new ArrayList(paramTypes.length); diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java index fdf04c7ba868..e6e28eedd3c4 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java @@ -27,6 +27,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -1736,6 +1737,22 @@ public void SPR_10125() throws Exception { assertThat(fromClass, is("interfaceValue")); } + @Test + public void SPR_10210() throws Exception { + StandardEvaluationContext context = new StandardEvaluationContext(); + context.setVariable("bridgeExample", new org.springframework.expression.spel.spr10210.D()); + Expression parseExpression = parser.parseExpression("#bridgeExample.bridgeMethod()"); + parseExpression.getValue(context); + } + + @Test + public void SPR_10328() throws Exception { + thrown.expect(SpelParseException.class); + thrown.expectMessage("EL1071E:(pos 2): A required selection expression has not been specified"); + Expression exp = parser.parseExpression("$[]"); + exp.getValue(Arrays.asList("foo", "bar", "baz")); + } + public static class BooleanHolder { private Boolean simpleProperty = true; @@ -1796,4 +1813,5 @@ public static class StaticFinalImpl1 extends AbstractStaticFinal implements Stat public static class StaticFinalImpl2 extends AbstractStaticFinal { } + } diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/spr10210/A.java b/spring-expression/src/test/java/org/springframework/expression/spel/spr10210/A.java new file mode 100644 index 000000000000..3098b293c743 --- /dev/null +++ b/spring-expression/src/test/java/org/springframework/expression/spel/spr10210/A.java @@ -0,0 +1,27 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.expression.spel.spr10210; + +import org.springframework.expression.spel.spr10210.comp.B; +import org.springframework.expression.spel.spr10210.infra.C; + +abstract class A extends B { + + public void bridgeMethod() { + } + +} diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/spr10210/D.java b/spring-expression/src/test/java/org/springframework/expression/spel/spr10210/D.java new file mode 100644 index 000000000000..e4776d70e66e --- /dev/null +++ b/spring-expression/src/test/java/org/springframework/expression/spel/spr10210/D.java @@ -0,0 +1,20 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.expression.spel.spr10210; + +public class D extends A { +} diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/spr10210/comp/B.java b/spring-expression/src/test/java/org/springframework/expression/spel/spr10210/comp/B.java new file mode 100644 index 000000000000..3250836265ce --- /dev/null +++ b/spring-expression/src/test/java/org/springframework/expression/spel/spr10210/comp/B.java @@ -0,0 +1,24 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.expression.spel.spr10210.comp; + +import java.io.Serializable; + +import org.springframework.expression.spel.spr10210.infra.C; + +public class B implements Serializable { +} diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/spr10210/infra/C.java b/spring-expression/src/test/java/org/springframework/expression/spel/spr10210/infra/C.java new file mode 100644 index 000000000000..2f0a32e53bee --- /dev/null +++ b/spring-expression/src/test/java/org/springframework/expression/spel/spr10210/infra/C.java @@ -0,0 +1,20 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.expression.spel.spr10210.infra; + +public interface C { +} diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcOperations.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcOperations.java index 8f5a9964cd39..6db102fc64fb 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcOperations.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcOperations.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -133,7 +133,8 @@ public interface JdbcOperations { * object via a RowMapper. *

    Uses a JDBC Statement, not a PreparedStatement. If you want to * execute a static query with a PreparedStatement, use the overloaded - * {@code queryForObject} method with {@code null} as argument array. + * {@link #queryForObject(String, RowMapper, Object...)} method with + * {@code null} as argument array. * @param sql SQL query to execute * @param rowMapper object that will map one object per row * @return the single mapped object @@ -148,7 +149,8 @@ public interface JdbcOperations { * Execute a query for a result object, given static SQL. *

    Uses a JDBC Statement, not a PreparedStatement. If you want to * execute a static query with a PreparedStatement, use the overloaded - * {@code queryForObject} method with {@code null} as argument array. + * {@link #queryForObject(String, Class, Object...)} method with + * {@code null} as argument array. *

    This method is useful for running static SQL with a known outcome. * The query is expected to be a single row/single column query; the returned * result will be directly mapped to the corresponding object type. @@ -166,7 +168,8 @@ public interface JdbcOperations { * Execute a query for a result Map, given static SQL. *

    Uses a JDBC Statement, not a PreparedStatement. If you want to * execute a static query with a PreparedStatement, use the overloaded - * {@code queryForMap} method with {@code null} as argument array. + * {@link #queryForMap(String, Object...)} method with {@code null} + * as argument array. *

    The query is expected to be a single row query; the result row will be * mapped to a Map (one entry for each column, using the column name as the key). * @param sql SQL query to execute @@ -194,7 +197,9 @@ public interface JdbcOperations { * exactly one row, or does not return exactly one column in that row * @throws DataAccessException if there is any problem executing the query * @see #queryForLong(String, Object[]) + * @deprecated in favor of {@link #queryForObject(String, Class)} */ + @Deprecated long queryForLong(String sql) throws DataAccessException; /** @@ -211,7 +216,9 @@ public interface JdbcOperations { * exactly one row, or does not return exactly one column in that row * @throws DataAccessException if there is any problem executing the query * @see #queryForInt(String, Object[]) + * @deprecated in favor of {@link #queryForObject(String, Class)} */ + @Deprecated int queryForInt(String sql) throws DataAccessException; /** @@ -712,7 +719,9 @@ T queryForObject(String sql, Object[] args, int[] argTypes, Class require * @throws DataAccessException if the query fails * @see #queryForLong(String) * @see java.sql.Types + * @deprecated in favor of {@link #queryForObject(String, Object[], int[], Class)} )} */ + @Deprecated long queryForLong(String sql, Object[] args, int[] argTypes) throws DataAccessException; /** @@ -730,7 +739,9 @@ T queryForObject(String sql, Object[] args, int[] argTypes, Class require * exactly one row, or does not return exactly one column in that row * @throws DataAccessException if the query fails * @see #queryForLong(String) + * @deprecated in favor of {@link #queryForObject(String, Class, Object[])} )} */ + @Deprecated long queryForLong(String sql, Object... args) throws DataAccessException; /** @@ -748,7 +759,9 @@ T queryForObject(String sql, Object[] args, int[] argTypes, Class require * @throws DataAccessException if the query fails * @see #queryForInt(String) * @see java.sql.Types + * @deprecated in favor of {@link #queryForObject(String, Object[], int[], Class)} )} */ + @Deprecated int queryForInt(String sql, Object[] args, int[] argTypes) throws DataAccessException; /** @@ -766,7 +779,9 @@ T queryForObject(String sql, Object[] args, int[] argTypes, Class require * exactly one row, or does not return exactly one column in that row * @throws DataAccessException if the query fails * @see #queryForInt(String) + * @deprecated in favor of {@link #queryForObject(String, Class, Object[])} )} */ + @Deprecated int queryForInt(String sql, Object... args) throws DataAccessException; /** diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java index 3f0085cebf41..910d20687229 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -477,11 +477,13 @@ public T queryForObject(String sql, Class requiredType) throws DataAccess return queryForObject(sql, getSingleColumnRowMapper(requiredType)); } + @Deprecated public long queryForLong(String sql) throws DataAccessException { Number number = queryForObject(sql, Long.class); return (number != null ? number.longValue() : 0); } + @Deprecated public int queryForInt(String sql) throws DataAccessException { Number number = queryForObject(sql, Integer.class); return (number != null ? number.intValue() : 0); @@ -757,21 +759,25 @@ public Map queryForMap(String sql, Object... args) throws DataAc return queryForObject(sql, args, getColumnMapRowMapper()); } + @Deprecated public long queryForLong(String sql, Object[] args, int[] argTypes) throws DataAccessException { Number number = queryForObject(sql, args, argTypes, Long.class); return (number != null ? number.longValue() : 0); } + @Deprecated public long queryForLong(String sql, Object... args) throws DataAccessException { Number number = queryForObject(sql, args, Long.class); return (number != null ? number.longValue() : 0); } + @Deprecated public int queryForInt(String sql, Object[] args, int[] argTypes) throws DataAccessException { Number number = queryForObject(sql, args, argTypes, Integer.class); return (number != null ? number.intValue() : 0); } + @Deprecated public int queryForInt(String sql, Object... args) throws DataAccessException { Number number = queryForObject(sql, args, Integer.class); return (number != null ? number.intValue() : 0); diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/CallMetaDataProvider.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/CallMetaDataProvider.java index a3da020588fd..29accf8ffa2d 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/CallMetaDataProvider.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/CallMetaDataProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -56,33 +56,33 @@ void initializeWithProcedureColumnMetaData( /** * Provide any modification of the procedure name passed in to match the meta data currently used. - * This could include alterig the case. + * This could include altering the case. */ String procedureNameToUse(String procedureName); /** * Provide any modification of the catalog name passed in to match the meta data currently used. - * This could include alterig the case. + * This could include altering the case. */ String catalogNameToUse(String catalogName); /** * Provide any modification of the schema name passed in to match the meta data currently used. - * This could include alterig the case. + * This could include altering the case. */ String schemaNameToUse(String schemaName); /** * Provide any modification of the catalog name passed in to match the meta data currently used. - * The reyurned value will be used for meta data lookups. This could include alterig the case used - * or providing a base catalog if mone provided. + * The returned value will be used for meta data lookups. This could include altering the case used + * or providing a base catalog if none is provided. */ String metaDataCatalogNameToUse(String catalogName) ; /** * Provide any modification of the schema name passed in to match the meta data currently used. - * The reyurned value will be used for meta data lookups. This could include alterig the case used - * or providing a base schema if mone provided. + * The returned value will be used for meta data lookups. This could include altering the case used + * or providing a base schema if none is provided. */ String metaDataSchemaNameToUse(String schemaName) ; diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/TableMetaDataProvider.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/TableMetaDataProvider.java index 0dff1678c4c6..c7365bed5bce 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/TableMetaDataProvider.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/TableMetaDataProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -76,8 +76,8 @@ void initializeWithTableColumnMetaData(DatabaseMetaData databaseMetaData, String /** * Provide any modification of the catalog name passed in to match the meta data currently used. - * The reyurned value will be used for meta data lookups. This could include alterig the case used or - * providing a base catalog if mone provided. + * The returned value will be used for meta data lookups. This could include altering the case used or + * providing a base catalog if none is provided. * * @param catalogName * @return catalog name to use @@ -86,8 +86,8 @@ void initializeWithTableColumnMetaData(DatabaseMetaData databaseMetaData, String /** * Provide any modification of the schema name passed in to match the meta data currently used. - * The reyurned value will be used for meta data lookups. This could include alterig the case used or - * providing a base schema if mone provided. + * The returned value will be used for meta data lookups. This could include altering the case used or + * providing a base schema if none is provided. * * @param schemaName * @return schema name to use diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/EmptySqlParameterSource.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/EmptySqlParameterSource.java new file mode 100644 index 000000000000..a0b525f0dcd8 --- /dev/null +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/EmptySqlParameterSource.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.jdbc.core.namedparam; + +/** + * A simple empty implementation of the {@link SqlParameterSource} interface. + * + * @author Juergen Hoeller + * @since 3.2.2 + */ +public class EmptySqlParameterSource implements SqlParameterSource { + + /** + * A shared instance of {@link EmptySqlParameterSource}. + */ + public static final EmptySqlParameterSource INSTANCE = new EmptySqlParameterSource(); + + + public boolean hasValue(String paramName) { + return false; + } + + public Object getValue(String paramName) throws IllegalArgumentException { + throw new IllegalArgumentException("This SqlParameterSource is empty"); + } + + public int getSqlType(String paramName) { + return TYPE_UNKNOWN; + } + + public String getTypeName(String paramName) { + return null; + } + +} diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcOperations.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcOperations.java index a7f5c9f7c9cc..91e20f3fcd9b 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcOperations.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcOperations.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -88,6 +88,21 @@ T execute(String sql, SqlParameterSource paramSource, PreparedStatementCallb T execute(String sql, Map paramMap, PreparedStatementCallback action) throws DataAccessException; + /** + * Execute a JDBC data access operation, implemented as callback action + * working on a JDBC PreparedStatement. This allows for implementing arbitrary + * data access operations on a single Statement, within Spring's managed + * JDBC environment: that is, participating in Spring-managed transactions + * and converting JDBC SQLExceptions into Spring's DataAccessException hierarchy. + *

    The callback action can return a result object, for example a + * domain object or a collection of domain objects. + * @param sql SQL to execute + * @param action callback object that specifies the action + * @return a result object returned by the action, or {@code null} + * @throws DataAccessException if there is any problem + */ + T execute(String sql, PreparedStatementCallback action) throws DataAccessException; + /** * Query given SQL to create a prepared statement from SQL and a list * of arguments to bind to the query, reading the ResultSet with a @@ -115,6 +130,19 @@ T query(String sql, SqlParameterSource paramSource, ResultSetExtractor rs T query(String sql, Map paramMap, ResultSetExtractor rse) throws DataAccessException; + /** + * Query given SQL to create a prepared statement from SQL, + * reading the ResultSet with a ResultSetExtractor. + *

    Note: In contrast to the JdbcOperations method with the same signature, + * this query variant always uses a PreparedStatement. It is effectively + * equivalent to a query call with an empty parameter Map. + * @param sql SQL query to execute + * @param rse object that will extract results + * @return an arbitrary result object, as returned by the ResultSetExtractor + * @throws org.springframework.dao.DataAccessException if the query fails + */ + T query(String sql, ResultSetExtractor rse) throws DataAccessException; + /** * Query given SQL to create a prepared statement from SQL and a list of * arguments to bind to the query, reading the ResultSet on a per-row basis @@ -139,6 +167,18 @@ void query(String sql, SqlParameterSource paramSource, RowCallbackHandler rch) */ void query(String sql, Map paramMap, RowCallbackHandler rch) throws DataAccessException; + /** + * Query given SQL to create a prepared statement from SQL, + * reading the ResultSet on a per-row basis with a RowCallbackHandler. + *

    Note: In contrast to the JdbcOperations method with the same signature, + * this query variant always uses a PreparedStatement. It is effectively + * equivalent to a query call with an empty parameter Map. + * @param sql SQL query to execute + * @param rch object that will extract results, one row at a time + * @throws org.springframework.dao.DataAccessException if the query fails + */ + void query(String sql, RowCallbackHandler rch) throws DataAccessException; + /** * Query given SQL to create a prepared statement from SQL and a list * of arguments to bind to the query, mapping each row to a Java object @@ -166,6 +206,19 @@ List query(String sql, SqlParameterSource paramSource, RowMapper rowMa List query(String sql, Map paramMap, RowMapper rowMapper) throws DataAccessException; + /** + * Query given SQL to create a prepared statement from SQL, + * mapping each row to a Java object via a RowMapper. + *

    Note: In contrast to the JdbcOperations method with the same signature, + * this query variant always uses a PreparedStatement. It is effectively + * equivalent to a query call with an empty parameter Map. + * @param sql SQL query to execute + * @param rowMapper object that will map one object per row + * @return the result List, containing mapped objects + * @throws org.springframework.dao.DataAccessException if the query fails + */ + List query(String sql, RowMapper rowMapper) throws DataAccessException; + /** * Query given SQL to create a prepared statement from SQL and a list * of arguments to bind to the query, mapping a single result row to a @@ -245,8 +298,7 @@ T queryForObject(String sql, Map paramMap, Class requiredType) * @param paramSource container of arguments to bind to the query * @return the result Map (one entry for each column, using the column name as the key) * @throws org.springframework.dao.IncorrectResultSizeDataAccessException - * if the query does not return exactly one row, or does not return exactly - * one column in that row + * if the query does not return exactly one row * @throws org.springframework.dao.DataAccessException if the query fails * @see org.springframework.jdbc.core.JdbcTemplate#queryForMap(String) * @see org.springframework.jdbc.core.ColumnMapRowMapper @@ -266,8 +318,7 @@ T queryForObject(String sql, Map paramMap, Class requiredType) * (leaving it to the PreparedStatement to guess the corresponding SQL type) * @return the result Map (one entry for each column, using the column name as the key) * @throws org.springframework.dao.IncorrectResultSizeDataAccessException - * if the query does not return exactly one row, or does not return exactly - * one column in that row + * if the query does not return exactly one row * @throws org.springframework.dao.DataAccessException if the query fails * @see org.springframework.jdbc.core.JdbcTemplate#queryForMap(String) * @see org.springframework.jdbc.core.ColumnMapRowMapper @@ -287,7 +338,9 @@ T queryForObject(String sql, Map paramMap, Class requiredType) * one column in that row * @throws org.springframework.dao.DataAccessException if the query fails * @see org.springframework.jdbc.core.JdbcTemplate#queryForLong(String) + * @deprecated in favor of {@link #queryForObject(String, SqlParameterSource, Class)} */ + @Deprecated long queryForLong(String sql, SqlParameterSource paramSource) throws DataAccessException; /** @@ -304,7 +357,9 @@ T queryForObject(String sql, Map paramMap, Class requiredType) * one column in that row * @throws org.springframework.dao.DataAccessException if the query fails * @see org.springframework.jdbc.core.JdbcTemplate#queryForLong(String) + * @deprecated in favor of {@link #queryForObject(String, Map, Class)} */ + @Deprecated long queryForLong(String sql, Map paramMap) throws DataAccessException; /** @@ -319,7 +374,9 @@ T queryForObject(String sql, Map paramMap, Class requiredType) * exactly one row, or does not return exactly one column in that row * @throws org.springframework.dao.DataAccessException if the query fails * @see org.springframework.jdbc.core.JdbcTemplate#queryForInt(String) + * @deprecated in favor of {@link #queryForObject(String, SqlParameterSource, Class)} */ + @Deprecated int queryForInt(String sql, SqlParameterSource paramSource) throws DataAccessException; /** @@ -335,7 +392,9 @@ T queryForObject(String sql, Map paramMap, Class requiredType) * exactly one row, or does not return exactly one column in that row * @throws org.springframework.dao.DataAccessException if the query fails * @see org.springframework.jdbc.core.JdbcTemplate#queryForInt(String) + * @deprecated in favor of {@link #queryForObject(String, Map, Class)} */ + @Deprecated int queryForInt(String sql, Map paramMap) throws DataAccessException; /** @@ -378,8 +437,8 @@ List queryForList(String sql, Map paramMap, Class elementTy * list of arguments to bind to the query, expecting a result list. *

    The results will be mapped to a List (one entry for each row) of * Maps (one entry for each column, using the column name as the key). - * Thus Each element in the list will be of the form returned by this interface's - * queryForMap() methods. + * Each element in the list will be of the form returned by this interface's + * {@code queryForMap} methods. * @param sql SQL query to execute * @param paramSource container of arguments to bind to the query * @return a List that contains a Map per row @@ -394,7 +453,7 @@ List queryForList(String sql, Map paramMap, Class elementTy *

    The results will be mapped to a List (one entry for each row) of * Maps (one entry for each column, using the column name as the key). * Each element in the list will be of the form returned by this interface's - * queryForMap() methods. + * {@code queryForMap} methods. * @param sql SQL query to execute * @param paramMap map of parameters to bind to the query * (leaving it to the PreparedStatement to guess the corresponding SQL type) @@ -499,7 +558,7 @@ int update(String sql, SqlParameterSource paramSource, KeyHolder generatedKeyHol * @param batchValues the array of Maps containing the batch of arguments for the query * @return an array containing the numbers of rows affected by each update in the batch */ - public int[] batchUpdate(String sql, Map[] batchValues); + int[] batchUpdate(String sql, Map[] batchValues); /** * Execute a batch using the supplied SQL statement with the batch of supplied arguments. @@ -507,6 +566,6 @@ int update(String sql, SqlParameterSource paramSource, KeyHolder generatedKeyHol * @param batchArgs the array of {@link SqlParameterSource} containing the batch of arguments for the query * @return an array containing the numbers of rows affected by each update in the batch */ - public int[] batchUpdate(String sql, SqlParameterSource[] batchArgs); + int[] batchUpdate(String sql, SqlParameterSource[] batchArgs); } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.java index 1abfdea3c218..d8258cefc957 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -137,6 +137,10 @@ public T execute(String sql, Map paramMap, PreparedStatementCallb return execute(sql, new MapSqlParameterSource(paramMap), action); } + public T execute(String sql, PreparedStatementCallback action) throws DataAccessException { + return execute(sql, EmptySqlParameterSource.INSTANCE, action); + } + public T query(String sql, SqlParameterSource paramSource, ResultSetExtractor rse) throws DataAccessException { @@ -149,6 +153,10 @@ public T query(String sql, Map paramMap, ResultSetExtractor rs return query(sql, new MapSqlParameterSource(paramMap), rse); } + public T query(String sql, ResultSetExtractor rse) throws DataAccessException { + return query(sql, EmptySqlParameterSource.INSTANCE, rse); + } + public void query(String sql, SqlParameterSource paramSource, RowCallbackHandler rch) throws DataAccessException { @@ -161,6 +169,10 @@ public void query(String sql, Map paramMap, RowCallbackHandler rch) query(sql, new MapSqlParameterSource(paramMap), rch); } + public void query(String sql, RowCallbackHandler rch) throws DataAccessException { + query(sql, EmptySqlParameterSource.INSTANCE, rch); + } + public List query(String sql, SqlParameterSource paramSource, RowMapper rowMapper) throws DataAccessException { @@ -173,6 +185,10 @@ public List query(String sql, Map paramMap, RowMapper rowMa return query(sql, new MapSqlParameterSource(paramMap), rowMapper); } + public List query(String sql, RowMapper rowMapper) throws DataAccessException { + return query(sql, EmptySqlParameterSource.INSTANCE, rowMapper); + } + public T queryForObject(String sql, SqlParameterSource paramSource, RowMapper rowMapper) throws DataAccessException { @@ -206,20 +222,24 @@ public Map queryForMap(String sql, Map paramMap) thro return queryForObject(sql, paramMap, new ColumnMapRowMapper()); } + @Deprecated public long queryForLong(String sql, SqlParameterSource paramSource) throws DataAccessException { Number number = queryForObject(sql, paramSource, Long.class); return (number != null ? number.longValue() : 0); } + @Deprecated public long queryForLong(String sql, Map paramMap) throws DataAccessException { return queryForLong(sql, new MapSqlParameterSource(paramMap)); } + @Deprecated public int queryForInt(String sql, SqlParameterSource paramSource) throws DataAccessException { Number number = queryForObject(sql, paramSource, Integer.class); return (number != null ? number.intValue() : 0); } + @Deprecated public int queryForInt(String sql, Map paramMap) throws DataAccessException { return queryForInt(sql, new MapSqlParameterSource(paramMap)); } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcCall.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcCall.java index 7c80a14d0fec..12e1dbd85ef0 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcCall.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcCall.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -97,7 +97,7 @@ protected AbstractJdbcCall(JdbcTemplate jdbcTemplate) { /** - * Get the configured {@link JdbcTemplate} + * Get the configured {@link JdbcTemplate}. */ public JdbcTemplate getJdbcTemplate() { return this.jdbcTemplate; @@ -110,7 +110,6 @@ protected CallableStatementCreatorFactory getCallableStatementFactory() { return this.callableStatementFactory; } - /** * Set the name of the stored procedure. */ @@ -294,7 +293,7 @@ protected void compileInternal() { this.callMetaDataContext.createReturnResultSetParameter(entry.getKey(), entry.getValue()); this.declaredParameters.add(resultSetParameter); } - callMetaDataContext.processParameters(this.declaredParameters); + this.callMetaDataContext.processParameters(this.declaredParameters); this.callString = this.callMetaDataContext.createCallString(); if (logger.isDebugEnabled()) { diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcInsert.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcInsert.java index 98f0e5991ebb..cb66d803c70f 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcInsert.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcInsert.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -74,9 +74,9 @@ public abstract class AbstractJdbcInsert { private final List declaredColumns = new ArrayList(); /** - * Has this operation been compiled? Compilation means at - * least checking that a DataSource or JdbcTemplate has been provided, - * but subclasses may also implement their own custom validation. + * Has this operation been compiled? Compilation means at least checking + * that a DataSource or JdbcTemplate has been provided, but subclasses + * may also implement their own custom validation. */ private boolean compiled = false; @@ -108,9 +108,16 @@ protected AbstractJdbcInsert(JdbcTemplate jdbcTemplate) { //------------------------------------------------------------------------- - // Methods dealing with configuaration properties + // Methods dealing with configuration properties //------------------------------------------------------------------------- + /** + * Get the {@link JdbcTemplate} that is configured to be used. + */ + public JdbcTemplate getJdbcTemplate() { + return this.jdbcTemplate; + } + /** * Set the name of the table for this insert */ @@ -230,41 +237,31 @@ public int[] getInsertTypes() { return this.insertTypes; } - /** - * Get the {@link JdbcTemplate} that is configured to be used - */ - protected JdbcTemplate getJdbcTemplate() { - return this.jdbcTemplate; - } - //------------------------------------------------------------------------- // Methods handling compilation issues //------------------------------------------------------------------------- /** - * Compile this JdbcInsert using provided parameters and meta data plus other settings. This - * finalizes the configuration for this object and subsequent attempts to compile are ignored. - * This will be implicitly called the first time an un-compiled insert is executed. - * @throws org.springframework.dao.InvalidDataAccessApiUsageException if the object hasn't - * been correctly initialized, for example if no DataSource has been provided + * Compile this JdbcInsert using provided parameters and meta data plus other settings. + * This finalizes the configuration for this object and subsequent attempts to compile are + * ignored. This will be implicitly called the first time an un-compiled insert is executed. + * @throws InvalidDataAccessApiUsageException if the object hasn't been correctly initialized, + * for example if no DataSource has been provided */ public synchronized final void compile() throws InvalidDataAccessApiUsageException { if (!isCompiled()) { if (getTableName() == null) { throw new InvalidDataAccessApiUsageException("Table name is required"); } - try { this.jdbcTemplate.afterPropertiesSet(); } catch (IllegalArgumentException ex) { throw new InvalidDataAccessApiUsageException(ex.getMessage()); } - compileInternal(); this.compiled = true; - if (logger.isDebugEnabled()) { logger.debug("JdbcInsert for table [" + getTableName() + "] compiled"); } @@ -272,21 +269,17 @@ public synchronized final void compile() throws InvalidDataAccessApiUsageExcepti } /** - * Method to perform the actual compilation. Subclasses can override this template method to perform - * their own compilation. Invoked after this base class's compilation is complete. + * Method to perform the actual compilation. Subclasses can override this template method + * to perform their own compilation. Invoked after this base class's compilation is complete. */ protected void compileInternal() { - - tableMetaDataContext.processMetaData(getJdbcTemplate().getDataSource(), getColumnNames(), getGeneratedKeyNames()); - - insertString = tableMetaDataContext.createInsertString(getGeneratedKeyNames()); - - insertTypes = tableMetaDataContext.createInsertTypes(); - + this.tableMetaDataContext.processMetaData( + getJdbcTemplate().getDataSource(), getColumnNames(), getGeneratedKeyNames()); + this.insertString = this.tableMetaDataContext.createInsertString(getGeneratedKeyNames()); + this.insertTypes = this.tableMetaDataContext.createInsertTypes(); if (logger.isDebugEnabled()) { - logger.debug("Compiled JdbcInsert. Insert string is [" + getInsertString() + "]"); + logger.debug("Compiled insert object: insert string is [" + getInsertString() + "]"); } - onCompileInternal(); } @@ -318,12 +311,13 @@ protected void checkCompiled() { } /** - * Method to check whether we are allowd to make any configuration changes at this time. If the class has been - * compiled, then no further changes to the configuration are allowed. + * Method to check whether we are allowd to make any configuration changes at this time. + * If the class has been compiled, then no further changes to the configuration are allowed. */ protected void checkIfConfigurationModificationIsAllowed() { if (isCompiled()) { - throw new InvalidDataAccessApiUsageException("Configuration can't be altered once the class has been compiled or used."); + throw new InvalidDataAccessApiUsageException( + "Configuration can't be altered once the class has been compiled or used"); } } @@ -334,7 +328,6 @@ protected void checkIfConfigurationModificationIsAllowed() { /** * Method that provides execution of the insert using the passed in Map of parameters - * * @param args Map with parameter names and values to be used in insert * @return number of rows affected */ @@ -346,7 +339,6 @@ protected int doExecute(Map args) { /** * Method that provides execution of the insert using the passed in {@link SqlParameterSource} - * * @param parameterSource parameter names and values to be used in insert * @return number of rows affected */ @@ -357,14 +349,13 @@ protected int doExecute(SqlParameterSource parameterSource) { } /** - * Method to execute the insert + * Method to execute the insert. */ private int executeInsertInternal(List values) { if (logger.isDebugEnabled()) { logger.debug("The following parameters are used for insert " + getInsertString() + " with: " + values); } - int updateCount = jdbcTemplate.update(getInsertString(), values.toArray(), getInsertTypes()); - return updateCount; + return getJdbcTemplate().update(getInsertString(), values.toArray(), getInsertTypes()); } /** @@ -428,8 +419,8 @@ private Number executeInsertAndReturnKeyInternal(final List values) { return kh.getKey(); } else { - throw new DataIntegrityViolationException("Unable to retrieve the generated key for the insert: " + - getInsertString()); + throw new DataIntegrityViolationException( + "Unable to retrieve the generated key for the insert: " + getInsertString()); } } @@ -442,7 +433,7 @@ private KeyHolder executeInsertAndReturnKeyHolderInternal(final List val } final KeyHolder keyHolder = new GeneratedKeyHolder(); if (this.tableMetaDataContext.isGetGeneratedKeysSupported()) { - jdbcTemplate.update( + getJdbcTemplate().update( new PreparedStatementCreator() { public PreparedStatement createPreparedStatement(Connection con) throws SQLException { PreparedStatement ps = prepareStatementForGeneratedKeys(con); @@ -467,22 +458,20 @@ public PreparedStatement createPreparedStatement(Connection con) throws SQLExcep getGeneratedKeyNames().length + " columns specified: " + Arrays.asList(getGeneratedKeyNames())); } // This is a hack to be able to get the generated key from a database that doesn't support - // get generated keys feature. HSQL is one, PostgreSQL is another. Postgres uses a RETURNING + // get generated keys feature. HSQL is one, PostgreSQL is another. Postgres uses a RETURNING // clause while HSQL uses a second query that has to be executed with the same connection. - final String keyQuery = tableMetaDataContext.getSimulationQueryForGetGeneratedKey( - tableMetaDataContext.getTableName(), - getGeneratedKeyNames()[0]); + final String keyQuery = this.tableMetaDataContext.getSimulationQueryForGetGeneratedKey( + this.tableMetaDataContext.getTableName(), getGeneratedKeyNames()[0]); Assert.notNull(keyQuery, "Query for simulating get generated keys can't be null"); if (keyQuery.toUpperCase().startsWith("RETURNING")) { - Long key = jdbcTemplate.queryForLong( - getInsertString() + " " + keyQuery, - values.toArray(new Object[values.size()])); - HashMap keys = new HashMap(1); + Long key = getJdbcTemplate().queryForObject(getInsertString() + " " + keyQuery, + values.toArray(new Object[values.size()]), Long.class); + Map keys = new HashMap(1); keys.put(getGeneratedKeyNames()[0], key); keyHolder.getKeyList().add(keys); } else { - jdbcTemplate.execute(new ConnectionCallback() { + getJdbcTemplate().execute(new ConnectionCallback() { public Object doInConnection(Connection con) throws SQLException, DataAccessException { // Do the insert PreparedStatement ps = null; @@ -490,13 +479,14 @@ public Object doInConnection(Connection con) throws SQLException, DataAccessExce ps = con.prepareStatement(getInsertString()); setParameterValues(ps, values, getInsertTypes()); ps.executeUpdate(); - } finally { + } + finally { JdbcUtils.closeStatement(ps); } //Get the key Statement keyStmt = null; ResultSet rs = null; - HashMap keys = new HashMap(1); + Map keys = new HashMap(1); try { keyStmt = con.createStatement(); rs = keyStmt.executeQuery(keyQuery); @@ -505,7 +495,8 @@ public Object doInConnection(Connection con) throws SQLException, DataAccessExce keys.put(getGeneratedKeyNames()[0], key); keyHolder.getKeyList().add(keys); } - } finally { + } + finally { JdbcUtils.closeResultSet(rs); JdbcUtils.closeStatement(keyStmt); } @@ -547,14 +538,14 @@ private PreparedStatement prepareStatementForGeneratedKeys(Connection con) throw } /** - * Method that provides execution of a batch insert using the passed in Maps of parameters - * + * Method that provides execution of a batch insert using the passed in Maps of parameters. * @param batch array of Maps with parameter names and values to be used in batch insert * @return array of number of rows affected */ + @SuppressWarnings("unchecked") protected int[] doExecuteBatch(Map[] batch) { checkCompiled(); - List[] batchValues = new ArrayList[batch.length]; + List[] batchValues = new ArrayList[batch.length]; int i = 0; for (Map args : batch) { List values = matchInParameterValuesWithInsertColumns(args); @@ -565,13 +556,13 @@ protected int[] doExecuteBatch(Map[] batch) { /** * Method that provides execution of a batch insert using the passed in array of {@link SqlParameterSource} - * * @param batch array of SqlParameterSource with parameter names and values to be used in insert * @return array of number of rows affected */ + @SuppressWarnings("unchecked") protected int[] doExecuteBatch(SqlParameterSource[] batch) { checkCompiled(); - List[] batchValues = new ArrayList[batch.length]; + List[] batchValues = new ArrayList[batch.length]; int i = 0; for (SqlParameterSource parameterSource : batch) { List values = matchInParameterValuesWithInsertColumns(parameterSource); @@ -581,27 +572,22 @@ protected int[] doExecuteBatch(SqlParameterSource[] batch) { } /** - * Method to execute the batch insert + * Method to execute the batch insert. */ - //TODO synchronize parameter setters with the SimpleJdbcTemplate private int[] executeBatchInternal(final List[] batchValues) { if (logger.isDebugEnabled()) { logger.debug("Executing statement " + getInsertString() + " with batch of size: " + batchValues.length); } - int[] updateCounts = jdbcTemplate.batchUpdate( - getInsertString(), + return getJdbcTemplate().batchUpdate(getInsertString(), new BatchPreparedStatementSetter() { - public void setValues(PreparedStatement ps, int i) throws SQLException { List values = batchValues[i]; setParameterValues(ps, values, getInsertTypes()); } - public int getBatchSize() { return batchValues.length; } }); - return updateCounts; } /** @@ -611,6 +597,7 @@ public int getBatchSize() { */ private void setParameterValues(PreparedStatement preparedStatement, List values, int[] columnTypes) throws SQLException { + int colIndex = 0; for (Object value : values) { colIndex++; @@ -624,25 +611,23 @@ private void setParameterValues(PreparedStatement preparedStatement, List matchInParameterValuesWithInsertColumns(SqlParameterSource parameterSource) { - return tableMetaDataContext.matchInParameterValuesWithInsertColumns(parameterSource); + return this.tableMetaDataContext.matchInParameterValuesWithInsertColumns(parameterSource); } /** - * Match the provided in parameter values with regitered parameters and parameters defined via metedata - * processing. - * + * Match the provided in parameter values with regitered parameters and parameters defined + * via metadata processing. * @param args the parameter values provided in a Map * @return Map with parameter names and values */ protected List matchInParameterValuesWithInsertColumns(Map args) { - return tableMetaDataContext.matchInParameterValuesWithInsertColumns(args); + return this.tableMetaDataContext.matchInParameterValuesWithInsertColumns(args); } } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcCall.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcCall.java index d687dbc2f6ab..6ace2d863e8e 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcCall.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcCall.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -128,7 +128,7 @@ public SimpleJdbcCall returningResultSet(String parameterName, RowMapper rowMapp /** * @deprecated in favor of {@link #returningResultSet(String, org.springframework.jdbc.core.RowMapper)} */ - @Deprecated + @Deprecated public SimpleJdbcCall returningResultSet(String parameterName, ParameterizedRowMapper rowMapper) { addDeclaredRowMapper(parameterName, rowMapper); return this; diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcInsert.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcInsert.java index 7d29df830184..98b89e9f1f27 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcInsert.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcInsert.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,9 +33,9 @@ * name of the table and a Map containing the column names and the column values. * *

    The meta data processing is based on the DatabaseMetaData provided by the - * JDBC driver. As long as the JBDC driver can provide the names of the columns + * JDBC driver. As long as the JBDC driver can provide the names of the columns * for a specified table than we can rely on this auto-detection feature. If that - * is not the case then the column names must be specified explicitly. + * is not the case, then the column names must be specified explicitly. * *

    The actual insert is being handled using Spring's * {@link org.springframework.jdbc.core.JdbcTemplate}. diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/support/AbstractLobCreatingPreparedStatementCallback.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/support/AbstractLobCreatingPreparedStatementCallback.java index 7ef0b3258325..c106ad57f413 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/support/AbstractLobCreatingPreparedStatementCallback.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/support/AbstractLobCreatingPreparedStatementCallback.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,15 +23,16 @@ import org.springframework.jdbc.core.PreparedStatementCallback; import org.springframework.jdbc.support.lob.LobCreator; import org.springframework.jdbc.support.lob.LobHandler; +import org.springframework.util.Assert; /** - * Abstract PreparedStatementCallback implementation that manages a LobCreator. + * Abstract {@link PreparedStatementCallback} implementation that manages a {@link LobCreator}. * Typically used as inner class, with access to surrounding method arguments. * *

    Delegates to the {@code setValues} template method for setting values * on the PreparedStatement, using a given LobCreator for BLOB/CLOB arguments. * - *

    A usage example with JdbcTemplate: + *

    A usage example with {@link org.springframework.jdbc.core.JdbcTemplate}: * *

    JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);  // reusable object
      * LobHandler lobHandler = new DefaultLobHandler();  // reusable object
    @@ -62,6 +63,7 @@ public abstract class AbstractLobCreatingPreparedStatementCallback implements Pr
     	 * @param lobHandler the LobHandler to create LobCreators with
     	 */
     	public AbstractLobCreatingPreparedStatementCallback(LobHandler lobHandler) {
    +		Assert.notNull(lobHandler, "LobHandler must not be null");
     		this.lobHandler = lobHandler;
     	}
     
    diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/LazyConnectionDataSourceProxy.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/LazyConnectionDataSourceProxy.java
    index f6ddc15b349f..5495aa68ce7c 100644
    --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/LazyConnectionDataSourceProxy.java
    +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/LazyConnectionDataSourceProxy.java
    @@ -1,5 +1,5 @@
     /*
    - * Copyright 2002-2010 the original author or authors.
    + * Copyright 2002-2013 the original author or authors.
      *
      * Licensed under the Apache License, Version 2.0 (the "License");
      * you may not use this file except in compliance with the License.
    @@ -66,18 +66,17 @@
      * You will get the same effect with non-transactional reads, but lazy fetching
      * of JDBC Connections allows you to still perform reads in transactions.
      *
    - * 

    NOTE: This DataSource proxy needs to return wrapped Connections to - * handle lazy fetching of an actual JDBC Connection. Therefore, the returned - * Connections cannot be cast to a native JDBC Connection type like OracleConnection, - * or to a connection pool implementation type. Use a corresponding - * NativeJdbcExtractor to retrieve the native JDBC Connection. + *

    NOTE: This DataSource proxy needs to return wrapped Connections + * (which implement the {@link ConnectionProxy} interface) in order to handle + * lazy fetching of an actual JDBC Connection. Therefore, the returned Connections + * cannot be cast to a native JDBC Connection type such as OracleConnection or + * to a connection pool implementation type. Use a corresponding + * {@link org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor} + * or JDBC 4's {@link Connection#unwrap} to retrieve the native JDBC Connection. * * @author Juergen Hoeller * @since 1.1.4 - * @see ConnectionProxy * @see DataSourceTransactionManager - * @see org.springframework.orm.hibernate3.HibernateTransactionManager - * @see org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor */ public class LazyConnectionDataSourceProxy extends DelegatingDataSource { @@ -407,7 +406,13 @@ private Connection getTargetConnection(Method operation) throws SQLException { // Apply kept transaction settings, if any. if (this.readOnly) { - this.target.setReadOnly(this.readOnly); + try { + this.target.setReadOnly(this.readOnly); + } + catch (Exception ex) { + // "read-only not supported" -> ignore, it's just a hint anyway + logger.debug("Could not set JDBC Connection read-only", ex); + } } if (this.transactionIsolation != null && !this.transactionIsolation.equals(defaultTransactionIsolation())) { diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/TransactionAwareDataSourceProxy.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/TransactionAwareDataSourceProxy.java index 1321c8166273..3bde77765d11 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/TransactionAwareDataSourceProxy.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/TransactionAwareDataSourceProxy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -64,10 +64,10 @@ *

    NOTE: This DataSource proxy needs to return wrapped Connections * (which implement the {@link ConnectionProxy} interface) in order to handle * close calls properly. Therefore, the returned Connections cannot be cast - * to a native JDBC Connection type like OracleConnection or to a connection + * to a native JDBC Connection type such as OracleConnection or to a connection * pool implementation type. Use a corresponding * {@link org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor} - * to retrieve the native JDBC Connection. + * or JDBC 4's {@link Connection#unwrap} to retrieve the native JDBC Connection. * * @author Juergen Hoeller * @since 1.1 diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/AbstractLobHandler.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/AbstractLobHandler.java index 737a58245927..4da40d61faaf 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/AbstractLobHandler.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/AbstractLobHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2005 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ import java.sql.SQLException; /** - * Abstract base class for LobHandler implementations. + * Abstract base class for {@link LobHandler} implementations. * *

    Implements all accessor methods for column names through a column lookup * and delegating to the corresponding accessor that takes a column index. diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/DefaultLobHandler.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/DefaultLobHandler.java index 6964172e962f..e570be24f03c 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/DefaultLobHandler.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/DefaultLobHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,18 +32,20 @@ import org.apache.commons.logging.LogFactory; /** - * Default implementation of the {@link LobHandler} interface. Invokes - * the direct accessor methods that {@code java.sql.ResultSet} + * Default implementation of the {@link LobHandler} interface. + * Invokes the direct accessor methods that {@code java.sql.ResultSet} * and {@code java.sql.PreparedStatement} offer. * *

    This LobHandler should work for any JDBC driver that is JDBC compliant * in terms of the spec's suggestions regarding simple BLOB and CLOB handling. - * This does not apply to Oracle 9i, and only to a limited degree to Oracle 10g! - * As a consequence, use {@link OracleLobHandler} for accessing Oracle BLOBs/CLOBs. + * This does not apply to Oracle 9i's drivers at all; as of Oracle 10g, + * it does work but may still come with LOB size limitations. Consider using + * recent Oracle drivers even when working against an older database server. + * See the {@link LobHandler} javadoc for the full set of recommendations. * *

    Some JDBC drivers require values with a BLOB/CLOB target column to be - * explicitly set through the JDBC {@code setBlob} / {@code setClob} - * API: for example, PostgreSQL's driver. Switch the {@link #setWrapAsLob "wrapAsLob"} + * explicitly set through the JDBC {@code setBlob} / {@code setClob} API: + * for example, PostgreSQL's driver. Switch the {@link #setWrapAsLob "wrapAsLob"} * property to "true" when operating against such a driver. * *

    On JDBC 4.0, this LobHandler also supports streaming the BLOB/CLOB content @@ -51,11 +53,15 @@ * argument directly. Consider switching the {@link #setStreamAsLob "streamAsLob"} * property to "true" when operating against a fully compliant JDBC 4.0 driver. * - *

    See the {@link LobHandler} javadoc for a summary of recommendations. + *

    Finally, primarily as a direct equivalent to {@link OracleLobHandler}, + * this LobHandler also supports the creation of temporary BLOB/CLOB objects. + * Consider switching the {@link #setCreateTemporaryLob "createTemporaryLob"} + * property to "true" when "streamAsLob" happens to run into LOB size limitations. + * + *

    See the {@link LobHandler} interface javadoc for a summary of recommendations. * * @author Juergen Hoeller * @since 04.12.2003 - * @see #setStreamAsLob * @see java.sql.ResultSet#getBytes * @see java.sql.ResultSet#getBinaryStream * @see java.sql.ResultSet#getString @@ -75,15 +81,18 @@ public class DefaultLobHandler extends AbstractLobHandler { private boolean streamAsLob = false; + private boolean createTemporaryLob = false; + /** * Specify whether to submit a byte array / String to the JDBC driver * wrapped in a JDBC Blob / Clob object, using the JDBC {@code setBlob} / * {@code setClob} method with a Blob / Clob argument. *

    Default is "false", using the common JDBC 2.0 {@code setBinaryStream} - * / {@code setCharacterStream} method for setting the content. - * Switch this to "true" for explicit Blob / Clob wrapping against - * JDBC drivers that are known to require such wrapping (e.g. PostgreSQL's). + * / {@code setCharacterStream} method for setting the content. Switch this + * to "true" for explicit Blob / Clob wrapping against JDBC drivers that + * are known to require such wrapping (e.g. PostgreSQL's for access to OID + * columns, whereas BYTEA columns need to be accessed the standard way). *

    This setting affects byte array / String arguments as well as stream * arguments, unless {@link #setStreamAsLob "streamAsLob"} overrides this * handling to use JDBC 4.0's new explicit streaming support (if available). @@ -100,7 +109,7 @@ public void setWrapAsLob(boolean wrapAsLob) { * {@code setClob} method with a stream argument. *

    Default is "false", using the common JDBC 2.0 {@code setBinaryStream} * / {@code setCharacterStream} method for setting the content. - * Switch this to "true" for explicit JDBC 4.0 usage, provided that your + * Switch this to "true" for explicit JDBC 4.0 streaming, provided that your * JDBC driver actually supports those JDBC 4.0 operations (e.g. Derby's). *

    This setting affects stream arguments as well as byte array / String * arguments, requiring JDBC 4.0 support. For supporting LOB content against @@ -112,6 +121,23 @@ public void setStreamAsLob(boolean streamAsLob) { this.streamAsLob = streamAsLob; } + /** + * Specify whether to copy a byte array / String into a temporary JDBC + * Blob / Clob object created through the JDBC 4.0 {@code createBlob} / + * {@code createClob} methods. + *

    Default is "false", using the common JDBC 2.0 {@code setBinaryStream} + * / {@code setCharacterStream} method for setting the content. Switch this + * to "true" for explicit Blob / Clob creation using JDBC 4.0. + *

    This setting affects stream arguments as well as byte array / String + * arguments, requiring JDBC 4.0 support. For supporting LOB content against + * JDBC 3.0, check out the {@link #setWrapAsLob "wrapAsLob"} setting. + * @see java.sql.Connection#createBlob() + * @see java.sql.Connection#createClob() + */ + public void setCreateTemporaryLob(boolean createTemporaryLob) { + this.createTemporaryLob = createTemporaryLob; + } + public byte[] getBlobAsBytes(ResultSet rs, int columnIndex) throws SQLException { logger.debug("Returning BLOB as bytes"); @@ -169,12 +195,12 @@ public Reader getClobAsCharacterStream(ResultSet rs, int columnIndex) throws SQL } public LobCreator getLobCreator() { - return new DefaultLobCreator(); + return (this.createTemporaryLob ? new TemporaryLobCreator() : new DefaultLobCreator()); } /** - * Default LobCreator implementation as inner class. + * Default LobCreator implementation as an inner class. * Can be subclassed in DefaultLobHandler extensions. */ protected class DefaultLobCreator implements LobCreator { @@ -268,15 +294,10 @@ public void setClobAsAsciiStream( PreparedStatement ps, int paramIndex, InputStream asciiStream, int contentLength) throws SQLException { - if (streamAsLob || wrapAsLob) { + if (streamAsLob) { if (asciiStream != null) { try { - if (streamAsLob) { - ps.setClob(paramIndex, new InputStreamReader(asciiStream, "US-ASCII"), contentLength); - } - else { - ps.setClob(paramIndex, new PassThroughClob(asciiStream, contentLength)); - } + ps.setClob(paramIndex, new InputStreamReader(asciiStream, "US-ASCII"), contentLength); } catch (UnsupportedEncodingException ex) { throw new SQLException("US-ASCII encoding not supported: " + ex); @@ -286,6 +307,14 @@ public void setClobAsAsciiStream( ps.setClob(paramIndex, (Clob) null); } } + else if (wrapAsLob) { + if (asciiStream != null) { + ps.setClob(paramIndex, new PassThroughClob(asciiStream, contentLength)); + } + else { + ps.setClob(paramIndex, (Clob) null); + } + } else { ps.setAsciiStream(paramIndex, asciiStream, contentLength); } @@ -325,7 +354,7 @@ else if (wrapAsLob) { } public void close() { - // nothing to do here + // nothing to do when not creating temporary LOBs } } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/LobHandler.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/LobHandler.java index 251a63a0e8d9..07e1847e4f56 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/LobHandler.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/LobHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ /** * Abstraction for handling large binary fields and large text fields in * specific databases, no matter if represented as simple types or Large OBjects. - * Its main purpose is to isolate Oracle's peculiar handling of LOBs in + * Its main purpose is to isolate Oracle 9i's peculiar handling of LOBs in * {@link OracleLobHandler}; most other databases should be able to work * with the provided {@link DefaultLobHandler}. * @@ -45,8 +45,10 @@ * proprietary BLOB/CLOB API, and additionally doesn't accept large streams for * PreparedStatement's corresponding setter methods. Therefore, you need to use * {@link OracleLobHandler} there, which uses Oracle's BLOB/CLOB API for both types - * of access. The Oracle 10g JDBC driver should basically work with - * {@link DefaultLobHandler} as well, with some limitations in terms of LOB sizes. + * of access. The Oracle 10g+ JDBC driver will work with {@link DefaultLobHandler} + * as well, with some limitations in terms of LOB sizes depending on DBMS setup; + * as of Oracle 11g (or actually, using the 11g driver even against older databases), + * there should be no need to use {@link OracleLobHandler} at all anymore. * *

    Of course, you need to declare different field types for each database. * In Oracle, any binary content needs to go into a BLOB, and all character content @@ -57,12 +59,20 @@ * *

    Summarizing the recommended options (for actual LOB fields): *

      - *
    • JDBC 4.0 driver: {@link DefaultLobHandler} with {@code streamAsLob=true}. - *
    • PostgreSQL: {@link DefaultLobHandler} with {@code wrapAsLob=true}. - *
    • Oracle 9i/10g: {@link OracleLobHandler} with a connection-pool-specific + *
    • JDBC 4.0 driver (including Oracle 11g driver): Use {@link DefaultLobHandler}, + * potentially with {@code streamAsLob=true} if your database driver requires that + * hint when populating a LOB field. Fall back to {@code createTemporaryLob=true} + * if you happen to run into LOB size limitations with your (Oracle) database setup. + *
    • Oracle 10g driver: Use {@link DefaultLobHandler} with standard setup. + * On Oracle 10.1, set the "SetBigStringTryClob" connection property; as of Oracle 10.2, + * DefaultLobHandler should work with standard setup out of the box. Alternatively, + * consider using the proprietary {@link OracleLobHandler} (see below). + *
    • Oracle 9i driver: Use {@link OracleLobHandler} with a connection-pool-specific * {@link OracleLobHandler#setNativeJdbcExtractor NativeJdbcExtractor}. + *
    • PostgreSQL: Configure {@link DefaultLobHandler} with {@code wrapAsLob=true}, + * and use that LobHandler to access OID columns (but not BYTEA) in your database tables. *
    • For all other database drivers (and for non-LOB fields that might potentially - * turn into LOBs on some databases): a plain {@link DefaultLobHandler}. + * turn into LOBs on some databases): Simply use a plain {@link DefaultLobHandler}. *
    * * @author Juergen Hoeller diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/OracleLobHandler.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/OracleLobHandler.java index 5399cf9c9103..2b239b3504d8 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/OracleLobHandler.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/OracleLobHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,14 +49,23 @@ * Note that this LobHandler requires Oracle JDBC driver 9i or higher! * *

    While most databases are able to work with {@link DefaultLobHandler}, - * Oracle just accepts Blob/Clob instances created via its own proprietary - * BLOB/CLOB API, and additionally doesn't accept large streams for - * PreparedStatement's corresponding setter methods. Therefore, you need - * to use a strategy like this LobHandler implementation. + * Oracle 9i (or more specifically, the Oracle 9i JDBC driver) just accepts + * Blob/Clob instances created via its own proprietary BLOB/CLOB API, + * and additionally doesn't accept large streams for PreparedStatement's + * corresponding setter methods. Therefore, you need to use a strategy like + * this LobHandler implementation, or upgrade to the Oracle 10g/11g driver + * (which still supports access to Oracle 9i databases). + * + *

    NOTE: As of Oracle 10.2, {@link DefaultLobHandler} should work equally + * well out of the box. On Oracle 11g, JDBC 4.0 based options such as + * {@link DefaultLobHandler#setStreamAsLob} and {@link DefaultLobHandler#setCreateTemporaryLob} + * are available as well, rendering this proprietary OracleLobHandler obsolete. + * Also, consider upgrading to a new driver even when accessing an older database. + * See the {@link LobHandler} interface javadoc for a summary of recommendations. * *

    Needs to work on a native JDBC Connection, to be able to cast it to * {@code oracle.jdbc.OracleConnection}. If you pass in Connections from a - * connection pool (the usual case in a J2EE environment), you need to set an + * connection pool (the usual case in a Java EE environment), you need to set an * appropriate {@link org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor} * to allow for automatic retrieval of the underlying native JDBC Connection. * LobHandler and NativeJdbcExtractor are separate concerns, therefore they @@ -72,8 +81,15 @@ * @author Juergen Hoeller * @author Thomas Risberg * @since 04.12.2003 + * @see DefaultLobHandler * @see #setNativeJdbcExtractor + * @deprecated in favor of {@link DefaultLobHandler} for the Oracle 10g driver and + * higher. Consider using the 10g/11g driver even against an Oracle 9i database! + * {@link DefaultLobHandler#setCreateTemporaryLob} is the direct equivalent of this + * OracleLobHandler's implementation strategy, just using standard JDBC 4.0 API. + * That said, in most cases, regular DefaultLobHandler setup will work fine as well. */ +@Deprecated public class OracleLobHandler extends AbstractLobHandler { private static final String BLOB_CLASS_NAME = "oracle.sql.BLOB"; @@ -143,7 +159,7 @@ public void setCache(boolean cache) { } /** - * Set whether to agressively release any resources used by the LOB. If set to {@code true} + * Set whether to aggressively release any resources used by the LOB. If set to {@code true} * then you can only read the LOB values once. Any subsequent reads will fail since the resources * have been closed. *

    Setting this property to {@code true} can be useful when your queries generates large @@ -283,7 +299,7 @@ protected void initializeResourcesBeforeRead(Connection con, Object lob) { ((BLOB) lob).open(BLOB.MODE_READONLY); */ Method open = lob.getClass().getMethod("open", int.class); - open.invoke(lob, modeReadOnlyConstants.get(lob.getClass())); + open.invoke(lob, this.modeReadOnlyConstants.get(lob.getClass())); } } catch (InvocationTargetException ex) { @@ -366,7 +382,7 @@ protected void releaseResourcesAfterRead(Connection con, Object lob) { */ protected class OracleLobCreator implements LobCreator { - private final List createdLobs = new LinkedList(); + private final List temporaryLobs = new LinkedList(); public void setBlobAsBytes(PreparedStatement ps, int paramIndex, final byte[] content) throws SQLException { @@ -495,7 +511,7 @@ protected Object createLob(PreparedStatement ps, boolean clob, LobCallback callb Object lob = prepareLob(con, clob ? clobClass : blobClass); callback.populateLob(lob); lob.getClass().getMethod("close", (Class[]) null).invoke(lob, (Object[]) null); - this.createdLobs.add(lob); + this.temporaryLobs.add(lob); if (logger.isDebugEnabled()) { logger.debug("Created new Oracle " + (clob ? "CLOB" : "BLOB")); } @@ -556,7 +572,7 @@ protected Object prepareLob(Connection con, Class lobClass) throws Exception { */ public void close() { try { - for (Iterator it = this.createdLobs.iterator(); it.hasNext();) { + for (Iterator it = this.temporaryLobs.iterator(); it.hasNext();) { /* BLOB blob = (BLOB) it.next(); blob.freeTemporary(); diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/TemporaryLobCreator.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/TemporaryLobCreator.java new file mode 100644 index 000000000000..f8c52660cb7d --- /dev/null +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/TemporaryLobCreator.java @@ -0,0 +1,167 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.jdbc.support.lob; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.dao.DataAccessResourceFailureException; +import org.springframework.util.FileCopyUtils; + +/** + * {@link LobCreator} implementation based on temporary LOBs, + * using JDBC 4.0's {@link java.sql.Connection#createBlob()} / + * {@link java.sql.Connection#createClob()} mechanism. + * + *

    Used by DefaultLobHandler's {@link DefaultLobHandler#setCreateTemporaryLob} mode. + * Can also be used directly to reuse the tracking and freeing of temporary LOBs. + * + * @author Juergen Hoeller + * @since 3.2.2 + * @see DefaultLobHandler#setCreateTemporaryLob + * @see java.sql.Connection#createBlob() + * @see java.sql.Connection#createClob() + */ +public class TemporaryLobCreator implements LobCreator { + + protected static final Log logger = LogFactory.getLog(TemporaryLobCreator.class); + + private final Set temporaryBlobs = new LinkedHashSet(1); + + private final Set temporaryClobs = new LinkedHashSet(1); + + + public void setBlobAsBytes(PreparedStatement ps, int paramIndex, byte[] content) + throws SQLException { + + Blob blob = ps.getConnection().createBlob(); + blob.setBytes(1, content); + + this.temporaryBlobs.add(blob); + ps.setBlob(paramIndex, blob); + + if (logger.isDebugEnabled()) { + logger.debug(content != null ? "Copied bytes into temporary BLOB with length " + content.length : + "Set BLOB to null"); + } + } + + public void setBlobAsBinaryStream( + PreparedStatement ps, int paramIndex, InputStream binaryStream, int contentLength) + throws SQLException { + + Blob blob = ps.getConnection().createBlob(); + try { + FileCopyUtils.copy(binaryStream, blob.setBinaryStream(1)); + } + catch (IOException ex) { + throw new DataAccessResourceFailureException("Could not copy into LOB stream", ex); + } + + this.temporaryBlobs.add(blob); + ps.setBlob(paramIndex, blob); + + if (logger.isDebugEnabled()) { + logger.debug(binaryStream != null ? + "Copied binary stream into temporary BLOB with length " + contentLength : + "Set BLOB to null"); + } + } + + public void setClobAsString(PreparedStatement ps, int paramIndex, String content) + throws SQLException { + + Clob clob = ps.getConnection().createClob(); + clob.setString(1, content); + + this.temporaryClobs.add(clob); + ps.setClob(paramIndex, clob); + + if (logger.isDebugEnabled()) { + logger.debug(content != null ? "Copied string into temporary CLOB with length " + content.length() : + "Set CLOB to null"); + } + } + + public void setClobAsAsciiStream( + PreparedStatement ps, int paramIndex, InputStream asciiStream, int contentLength) + throws SQLException { + + Clob clob = ps.getConnection().createClob(); + try { + FileCopyUtils.copy(asciiStream, clob.setAsciiStream(1)); + } + catch (IOException ex) { + throw new DataAccessResourceFailureException("Could not copy into LOB stream", ex); + } + + this.temporaryClobs.add(clob); + ps.setClob(paramIndex, clob); + + if (logger.isDebugEnabled()) { + logger.debug(asciiStream != null ? + "Copied ASCII stream into temporary CLOB with length " + contentLength : + "Set CLOB to null"); + } + } + + public void setClobAsCharacterStream( + PreparedStatement ps, int paramIndex, Reader characterStream, int contentLength) + throws SQLException { + + Clob clob = ps.getConnection().createClob(); + try { + FileCopyUtils.copy(characterStream, clob.setCharacterStream(1)); + } + catch (IOException ex) { + throw new DataAccessResourceFailureException("Could not copy into LOB stream", ex); + } + + this.temporaryClobs.add(clob); + ps.setClob(paramIndex, clob); + + if (logger.isDebugEnabled()) { + logger.debug(characterStream != null ? + "Copied character stream into temporary CLOB with length " + contentLength : + "Set CLOB to null"); + } + } + + public void close() { + try { + for (Blob blob : this.temporaryBlobs) { + blob.free(); + } + for (Clob clob : this.temporaryClobs) { + clob.free(); + } + } + catch (SQLException ex) { + logger.error("Could not free LOB", ex); + } + } +} diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/nativejdbc/WebLogicNativeJdbcExtractor.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/nativejdbc/WebLogicNativeJdbcExtractor.java index e967966aee5c..09129320e5b9 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/nativejdbc/WebLogicNativeJdbcExtractor.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/nativejdbc/WebLogicNativeJdbcExtractor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ /** * Implementation of the {@link NativeJdbcExtractor} interface for WebLogic, - * supporting WebLogic Server 8.1 and higher. + * supporting WebLogic Server 9.0 and higher. * *

    Returns the underlying native Connection to application code instead * of WebLogic's wrapper implementation; unwraps the Connection for native diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/nativejdbc/WebSphereNativeJdbcExtractor.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/nativejdbc/WebSphereNativeJdbcExtractor.java index d40a575c7a87..b225ea19d229 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/nativejdbc/WebSphereNativeJdbcExtractor.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/nativejdbc/WebSphereNativeJdbcExtractor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ /** * Implementation of the {@link NativeJdbcExtractor} interface for WebSphere, - * supporting WebSphere Application Server 5.1 and higher. + * supporting WebSphere Application Server 6.1 and higher. * *

    Returns the underlying native Connection to application code instead * of WebSphere's wrapper implementation; unwraps the Connection for @@ -40,14 +40,14 @@ */ public class WebSphereNativeJdbcExtractor extends NativeJdbcExtractorAdapter { - private static final String JDBC_ADAPTER_CONNECTION_NAME_5 = "com.ibm.ws.rsadapter.jdbc.WSJdbcConnection"; + private static final String JDBC_ADAPTER_CONNECTION_NAME = "com.ibm.ws.rsadapter.jdbc.WSJdbcConnection"; - private static final String JDBC_ADAPTER_UTIL_NAME_5 = "com.ibm.ws.rsadapter.jdbc.WSJdbcUtil"; + private static final String JDBC_ADAPTER_UTIL_NAME = "com.ibm.ws.rsadapter.jdbc.WSJdbcUtil"; - private Class webSphere5ConnectionClass; + private Class webSphereConnectionClass; - private Method webSphere5NativeConnectionMethod; + private Method webSphereNativeConnectionMethod; /** @@ -56,10 +56,10 @@ public class WebSphereNativeJdbcExtractor extends NativeJdbcExtractorAdapter { */ public WebSphereNativeJdbcExtractor() { try { - this.webSphere5ConnectionClass = getClass().getClassLoader().loadClass(JDBC_ADAPTER_CONNECTION_NAME_5); - Class jdbcAdapterUtilClass = getClass().getClassLoader().loadClass(JDBC_ADAPTER_UTIL_NAME_5); - this.webSphere5NativeConnectionMethod = - jdbcAdapterUtilClass.getMethod("getNativeConnection", new Class[] {this.webSphere5ConnectionClass}); + this.webSphereConnectionClass = getClass().getClassLoader().loadClass(JDBC_ADAPTER_CONNECTION_NAME); + Class jdbcAdapterUtilClass = getClass().getClassLoader().loadClass(JDBC_ADAPTER_UTIL_NAME); + this.webSphereNativeConnectionMethod = + jdbcAdapterUtilClass.getMethod("getNativeConnection", new Class[] {this.webSphereConnectionClass}); } catch (Exception ex) { throw new IllegalStateException( @@ -97,9 +97,8 @@ public boolean isNativeConnectionNecessaryForNativeCallableStatements() { */ @Override protected Connection doGetNativeConnection(Connection con) throws SQLException { - if (this.webSphere5ConnectionClass.isAssignableFrom(con.getClass())) { - return (Connection) ReflectionUtils.invokeJdbcMethod( - this.webSphere5NativeConnectionMethod, null, new Object[] {con}); + if (this.webSphereConnectionClass.isAssignableFrom(con.getClass())) { + return (Connection) ReflectionUtils.invokeJdbcMethod(this.webSphereNativeConnectionMethod, null, con); } return con; } diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplateTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplateTests.java index 6564fe5ee7a9..c17c8bbc02d9 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplateTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplateTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,15 +16,6 @@ package org.springframework.jdbc.core.namedparam; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.mockito.BDDMockito.given; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.PreparedStatement; @@ -36,13 +27,13 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; - import javax.sql.DataSource; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; + import org.springframework.dao.DataAccessException; import org.springframework.jdbc.Customer; import org.springframework.jdbc.core.JdbcOperations; @@ -53,6 +44,14 @@ import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.SqlParameterValue; +import static org.junit.Assert.*; +import static org.mockito.BDDMockito.*; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + /** * @author Rick Evans * @author Juergen Hoeller @@ -64,6 +63,8 @@ public class NamedParameterJdbcTemplateTests { "select id, forename from custmr where id = :id and country = :country"; private static final String SELECT_NAMED_PARAMETERS_PARSED = "select id, forename from custmr where id = ? and country = ?"; + private static final String SELECT_NO_PARAMETERS = + "select id, forename from custmr"; private static final String UPDATE_NAMED_PARAMETERS = "update seat_status set booking_id = null where performance_id = :perfId and price_band_id = :priceId"; @@ -161,33 +162,23 @@ public Object doInPreparedStatement(PreparedStatement ps) verify(connection).close(); } - @Test public void testUpdate() throws SQLException { - given(preparedStatement.executeUpdate()).willReturn(1); - - params.put("perfId", 1); - params.put("priceId", 1); - int rowsAffected = namedParameterTemplate.update(UPDATE_NAMED_PARAMETERS, params); - - assertEquals(1, rowsAffected); - verify(connection).prepareStatement(UPDATE_NAMED_PARAMETERS_PARSED); - verify(preparedStatement).setObject(1, 1); - verify(preparedStatement).setObject(2, 1); - verify(preparedStatement).close(); - verify(connection).close(); - } - @Test - public void testUpdateWithTypedParameters() throws SQLException { + public void testExecuteNoParameters() throws SQLException { given(preparedStatement.executeUpdate()).willReturn(1); - params.put("perfId", new SqlParameterValue(Types.DECIMAL, 1)); - params.put("priceId", new SqlParameterValue(Types.INTEGER, 1)); - int rowsAffected = namedParameterTemplate.update(UPDATE_NAMED_PARAMETERS, params); + Object result = namedParameterTemplate.execute(SELECT_NO_PARAMETERS, + new PreparedStatementCallback() { + @Override + public Object doInPreparedStatement(PreparedStatement ps) + throws SQLException { + assertEquals(preparedStatement, ps); + ps.executeQuery(); + return "result"; + } + }); - assertEquals(1, rowsAffected); - verify(connection).prepareStatement(UPDATE_NAMED_PARAMETERS_PARSED); - verify(preparedStatement).setObject(1, 1, Types.DECIMAL); - verify(preparedStatement).setObject(2, 1, Types.INTEGER); + assertEquals("result", result); + verify(connection).prepareStatement(SELECT_NO_PARAMETERS); verify(preparedStatement).close(); verify(connection).close(); } @@ -222,6 +213,32 @@ public Customer extractData(ResultSet rs) throws SQLException, verify(connection).close(); } + @Test + public void testQueryWithResultSetExtractorNoParameters() throws SQLException { + given(resultSet.next()).willReturn(true); + given(resultSet.getInt("id")).willReturn(1); + given(resultSet.getString("forename")).willReturn("rod"); + + Customer cust = namedParameterTemplate.query(SELECT_NO_PARAMETERS, + new ResultSetExtractor() { + @Override + public Customer extractData(ResultSet rs) throws SQLException, + DataAccessException { + rs.next(); + Customer cust = new Customer(); + cust.setId(rs.getInt(COLUMN_NAMES[0])); + cust.setForename(rs.getString(COLUMN_NAMES[1])); + return cust; + } + }); + + assertTrue("Customer id was assigned correctly", cust.getId() == 1); + assertTrue("Customer forename was assigned correctly", cust.getForename().equals("rod")); + verify(connection).prepareStatement(SELECT_NO_PARAMETERS); + verify(preparedStatement).close(); + verify(connection).close(); + } + @Test public void testQueryWithRowCallbackHandler() throws SQLException { given(resultSet.next()).willReturn(true, false); @@ -251,6 +268,31 @@ public void processRow(ResultSet rs) throws SQLException { verify(connection).close(); } + @Test + public void testQueryWithRowCallbackHandlerNoParameters() throws SQLException { + given(resultSet.next()).willReturn(true, false); + given(resultSet.getInt("id")).willReturn(1); + given(resultSet.getString("forename")).willReturn("rod"); + + final List customers = new LinkedList(); + namedParameterTemplate.query(SELECT_NO_PARAMETERS, new RowCallbackHandler() { + @Override + public void processRow(ResultSet rs) throws SQLException { + Customer cust = new Customer(); + cust.setId(rs.getInt(COLUMN_NAMES[0])); + cust.setForename(rs.getString(COLUMN_NAMES[1])); + customers.add(cust); + } + }); + + assertEquals(1, customers.size()); + assertTrue("Customer id was assigned correctly", customers.get(0).getId() == 1); + assertTrue("Customer forename was assigned correctly", customers.get(0).getForename().equals("rod")); + verify(connection).prepareStatement(SELECT_NO_PARAMETERS); + verify(preparedStatement).close(); + verify(connection).close(); + } + @Test public void testQueryWithRowMapper() throws SQLException { given(resultSet.next()).willReturn(true, false); @@ -279,6 +321,30 @@ public Customer mapRow(ResultSet rs, int rownum) throws SQLException { verify(connection).close(); } + @Test + public void testQueryWithRowMapperNoParameters() throws SQLException { + given(resultSet.next()).willReturn(true, false); + given(resultSet.getInt("id")).willReturn(1); + given(resultSet.getString("forename")).willReturn("rod"); + + List customers = namedParameterTemplate.query(SELECT_NO_PARAMETERS, + new RowMapper() { + @Override + public Customer mapRow(ResultSet rs, int rownum) throws SQLException { + Customer cust = new Customer(); + cust.setId(rs.getInt(COLUMN_NAMES[0])); + cust.setForename(rs.getString(COLUMN_NAMES[1])); + return cust; + } + }); + assertEquals(1, customers.size()); + assertTrue("Customer id was assigned correctly", customers.get(0).getId() == 1); + assertTrue("Customer forename was assigned correctly", customers.get(0).getForename().equals("rod")); + verify(connection).prepareStatement(SELECT_NO_PARAMETERS); + verify(preparedStatement).close(); + verify(connection).close(); + } + @Test public void testQueryForObjectWithRowMapper() throws SQLException { given(resultSet.next()).willReturn(true, false); @@ -307,8 +373,39 @@ public Customer mapRow(ResultSet rs, int rownum) throws SQLException { } @Test - public void testBatchUpdateWithPlainMap() throws Exception { + public void testUpdate() throws SQLException { + given(preparedStatement.executeUpdate()).willReturn(1); + + params.put("perfId", 1); + params.put("priceId", 1); + int rowsAffected = namedParameterTemplate.update(UPDATE_NAMED_PARAMETERS, params); + + assertEquals(1, rowsAffected); + verify(connection).prepareStatement(UPDATE_NAMED_PARAMETERS_PARSED); + verify(preparedStatement).setObject(1, 1); + verify(preparedStatement).setObject(2, 1); + verify(preparedStatement).close(); + verify(connection).close(); + } + + @Test + public void testUpdateWithTypedParameters() throws SQLException { + given(preparedStatement.executeUpdate()).willReturn(1); + + params.put("perfId", new SqlParameterValue(Types.DECIMAL, 1)); + params.put("priceId", new SqlParameterValue(Types.INTEGER, 1)); + int rowsAffected = namedParameterTemplate.update(UPDATE_NAMED_PARAMETERS, params); + + assertEquals(1, rowsAffected); + verify(connection).prepareStatement(UPDATE_NAMED_PARAMETERS_PARSED); + verify(preparedStatement).setObject(1, 1, Types.DECIMAL); + verify(preparedStatement).setObject(2, 1, Types.INTEGER); + verify(preparedStatement).close(); + verify(connection).close(); + } + @Test + public void testBatchUpdateWithPlainMap() throws Exception { @SuppressWarnings("unchecked") final Map[] ids = new Map[2]; ids[0] = Collections.singletonMap("id", 100); @@ -335,7 +432,7 @@ public void testBatchUpdateWithPlainMap() throws Exception { @Test public void testBatchUpdateWithSqlParameterSource() throws Exception { - final SqlParameterSource[] ids = new SqlParameterSource[2]; + SqlParameterSource[] ids = new SqlParameterSource[2]; ids[0] = new MapSqlParameterSource("id", 100); ids[1] = new MapSqlParameterSource("id", 200); final int[] rowsAffected = new int[] { 1, 2 }; @@ -360,8 +457,7 @@ public void testBatchUpdateWithSqlParameterSource() throws Exception { @Test public void testBatchUpdateWithSqlParameterSourcePlusTypeInfo() throws Exception { - - final SqlParameterSource[] ids = new SqlParameterSource[2]; + SqlParameterSource[] ids = new SqlParameterSource[2]; ids[0] = new MapSqlParameterSource().addValue("id", 100, Types.NUMERIC); ids[1] = new MapSqlParameterSource().addValue("id", 200, Types.NUMERIC); final int[] rowsAffected = new int[] { 1, 2 }; diff --git a/spring-jms/src/main/java/org/springframework/jms/listener/endpoint/JmsMessageEndpointFactory.java b/spring-jms/src/main/java/org/springframework/jms/listener/endpoint/JmsMessageEndpointFactory.java index 392d02b1fa70..954dd4982ee9 100644 --- a/spring-jms/src/main/java/org/springframework/jms/listener/endpoint/JmsMessageEndpointFactory.java +++ b/spring-jms/src/main/java/org/springframework/jms/listener/endpoint/JmsMessageEndpointFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -109,11 +109,11 @@ protected ClassLoader getEndpointClassLoader() { /** - * Internal exception thrown when a ResourceExeption has been encountered + * Internal exception thrown when a ResourceException has been encountered * during the endpoint invocation. *

    Will only be used if the ResourceAdapter does not invoke the * endpoint's {@code beforeDelivery} and {@code afterDelivery} - * directly, leavng it up to the concrete endpoint to apply those - + * directly, leaving it up to the concrete endpoint to apply those - * and to handle any ResourceExceptions thrown from them. */ @SuppressWarnings("serial") diff --git a/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/HibernateTransactionManager.java b/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/HibernateTransactionManager.java index 6d3e7ccf2564..5871bb8a87e9 100644 --- a/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/HibernateTransactionManager.java +++ b/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/HibernateTransactionManager.java @@ -22,12 +22,16 @@ import org.hibernate.ConnectionReleaseMode; import org.hibernate.FlushMode; import org.hibernate.HibernateException; +import org.hibernate.Interceptor; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.transaction.spi.TransactionContext; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.InitializingBean; import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessResourceFailureException; @@ -100,7 +104,7 @@ */ @SuppressWarnings("serial") public class HibernateTransactionManager extends AbstractPlatformTransactionManager - implements ResourceTransactionManager, InitializingBean { + implements ResourceTransactionManager, BeanFactoryAware, InitializingBean { private SessionFactory sessionFactory; @@ -112,6 +116,14 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana private boolean hibernateManagedSession = false; + private Object entityInterceptor; + + /** + * Just needed for entityInterceptorBeanName. + * @see #setEntityInterceptorBeanName + */ + private BeanFactory beanFactory; + /** * Create a new HibernateTransactionManager instance. @@ -229,7 +241,7 @@ public void setPrepareConnection(boolean prepareConnection) { * to always return a proper Session when called for a Spring-managed transaction; * transaction begin will fail if the {@code getCurrentSession()} call fails. *

    This mode will typically be used in combination with a custom Hibernate - * {@link org.hibernate.context.CurrentSessionContext} implementation that stores + * {@link org.hibernate.context.spi.CurrentSessionContext} implementation that stores * Sessions in a place other than Spring's TransactionSynchronizationManager. * It may also be used in combination with Spring's Open-Session-in-View support * (using Spring's default {@link SpringSessionContext}), in which case it subtly @@ -242,10 +254,81 @@ public void setHibernateManagedSession(boolean hibernateManagedSession) { this.hibernateManagedSession = hibernateManagedSession; } + /** + * Set the bean name of a Hibernate entity interceptor that allows to inspect + * and change property values before writing to and reading from the database. + * Will get applied to any new Session created by this transaction manager. + *

    Requires the bean factory to be known, to be able to resolve the bean + * name to an interceptor instance on session creation. Typically used for + * prototype interceptors, i.e. a new interceptor instance per session. + *

    Can also be used for shared interceptor instances, but it is recommended + * to set the interceptor reference directly in such a scenario. + * @param entityInterceptorBeanName the name of the entity interceptor in + * the bean factory + * @see #setBeanFactory + * @see #setEntityInterceptor + */ + public void setEntityInterceptorBeanName(String entityInterceptorBeanName) { + this.entityInterceptor = entityInterceptorBeanName; + } + + /** + * Set a Hibernate entity interceptor that allows to inspect and change + * property values before writing to and reading from the database. + * Will get applied to any new Session created by this transaction manager. + *

    Such an interceptor can either be set at the SessionFactory level, + * i.e. on LocalSessionFactoryBean, or at the Session level, i.e. on + * HibernateTemplate, HibernateInterceptor, and HibernateTransactionManager. + * It's preferable to set it on LocalSessionFactoryBean or HibernateTransactionManager + * to avoid repeated configuration and guarantee consistent behavior in transactions. + * @see LocalSessionFactoryBean#setEntityInterceptor + */ + public void setEntityInterceptor(Interceptor entityInterceptor) { + this.entityInterceptor = entityInterceptor; + } + + /** + * Return the current Hibernate entity interceptor, or {@code null} if none. + * Resolves an entity interceptor bean name via the bean factory, + * if necessary. + * @throws IllegalStateException if bean name specified but no bean factory set + * @throws BeansException if bean name resolution via the bean factory failed + * @see #setEntityInterceptor + * @see #setEntityInterceptorBeanName + * @see #setBeanFactory + */ + public Interceptor getEntityInterceptor() throws IllegalStateException, BeansException { + if (this.entityInterceptor instanceof Interceptor) { + return (Interceptor) entityInterceptor; + } + else if (this.entityInterceptor instanceof String) { + if (this.beanFactory == null) { + throw new IllegalStateException("Cannot get entity interceptor via bean name if no bean factory set"); + } + String beanName = (String) this.entityInterceptor; + return this.beanFactory.getBean(beanName, Interceptor.class); + } + else { + return null; + } + } + + /** + * The bean factory just needs to be known for resolving entity interceptor + * bean names. It does not need to be set for any other mode of operation. + * @see #setEntityInterceptorBeanName + */ + public void setBeanFactory(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + public void afterPropertiesSet() { if (getSessionFactory() == null) { throw new IllegalArgumentException("Property 'sessionFactory' is required"); } + if (this.entityInterceptor instanceof String && this.beanFactory == null) { + throw new IllegalArgumentException("Property 'beanFactory' is required for 'entityInterceptorBeanName'"); + } // Check for SessionFactory's DataSource. if (this.autodetectDataSource && getDataSource() == null) { @@ -325,7 +408,10 @@ protected void doBegin(Object transaction, TransactionDefinition definition) { try { if (txObject.getSessionHolder() == null || txObject.getSessionHolder().isSynchronizedWithTransaction()) { - Session newSession = getSessionFactory().openSession(); + Interceptor entityInterceptor = getEntityInterceptor(); + Session newSession = (entityInterceptor != null ? + getSessionFactory().withOptions().interceptor(entityInterceptor).openSession() : + getSessionFactory().openSession()); if (logger.isDebugEnabled()) { logger.debug("Opened new Session [" + newSession + "] for Hibernate transaction"); } diff --git a/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/LocalSessionFactoryBuilder.java b/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/LocalSessionFactoryBuilder.java index e125abf2b71b..b0aac2d2bbe6 100644 --- a/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/LocalSessionFactoryBuilder.java +++ b/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/LocalSessionFactoryBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -68,6 +68,8 @@ public class LocalSessionFactoryBuilder extends Configuration { private static final String RESOURCE_PATTERN = "/**/*.class"; + private static final String PACKAGE_INFO_SUFFIX = ".package-info"; + private static final TypeFilter[] ENTITY_TYPE_FILTERS = new TypeFilter[] { new AnnotationTypeFilter(Entity.class, false), new AnnotationTypeFilter(Embeddable.class, false), @@ -194,8 +196,11 @@ public LocalSessionFactoryBuilder scanPackages(String... packagesToScan) throws if (resource.isReadable()) { MetadataReader reader = readerFactory.getMetadataReader(resource); String className = reader.getClassMetadata().getClassName(); - if (matchesFilter(reader, readerFactory)) { - addAnnotatedClasses(this.resourcePatternResolver.getClassLoader().loadClass(className)); + if (matchesEntityTypeFilter(reader, readerFactory)) { + addAnnotatedClass(this.resourcePatternResolver.getClassLoader().loadClass(className)); + } + else if (className.endsWith(PACKAGE_INFO_SUFFIX)) { + addPackage(className.substring(0, className.length() - PACKAGE_INFO_SUFFIX.length())); } } } @@ -214,7 +219,7 @@ public LocalSessionFactoryBuilder scanPackages(String... packagesToScan) throws * Check whether any of the configured entity type filters matches * the current class descriptor contained in the metadata reader. */ - private boolean matchesFilter(MetadataReader reader, MetadataReaderFactory readerFactory) throws IOException { + private boolean matchesEntityTypeFilter(MetadataReader reader, MetadataReaderFactory readerFactory) throws IOException { for (TypeFilter filter : ENTITY_TYPE_FILTERS) { if (filter.match(reader, readerFactory)) { return true; diff --git a/spring-orm-hibernate4/src/test/java/org/springframework/orm/hibernate4/HibernateTransactionManagerTests.java b/spring-orm-hibernate4/src/test/java/org/springframework/orm/hibernate4/HibernateTransactionManagerTests.java index 26bdd97e2922..f235fc196e0d 100644 --- a/spring-orm-hibernate4/src/test/java/org/springframework/orm/hibernate4/HibernateTransactionManagerTests.java +++ b/spring-orm-hibernate4/src/test/java/org/springframework/orm/hibernate4/HibernateTransactionManagerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,17 +25,21 @@ import java.util.Properties; import javax.sql.DataSource; -import junit.framework.TestCase; -import org.easymock.MockControl; import org.hibernate.FlushMode; +import org.hibernate.Interceptor; import org.hibernate.Query; import org.hibernate.Session; +import org.hibernate.SessionBuilder; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.dialect.HSQLDialect; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.exception.ConstraintViolationException; +import org.junit.After; +import org.junit.Test; +import org.mockito.InOrder; +import org.springframework.beans.factory.BeanFactory; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.jdbc.datasource.ConnectionHolder; import org.springframework.jdbc.datasource.DriverManagerDataSource; @@ -50,63 +54,42 @@ import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.transaction.support.TransactionTemplate; +import static org.junit.Assert.*; +import static org.mockito.BDDMockito.*; + /** * @author Juergen Hoeller * @since 3.2 */ -public class HibernateTransactionManagerTests extends TestCase { +public class HibernateTransactionManagerTests { + + @After + public void tearDown() { + assertTrue(TransactionSynchronizationManager.getResourceMap().isEmpty()); + assertFalse(TransactionSynchronizationManager.isSynchronizationActive()); + assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly()); + assertFalse(TransactionSynchronizationManager.isActualTransactionActive()); + } + @Test public void testTransactionCommit() throws Exception { - MockControl dsControl = MockControl.createControl(DataSource.class); - final DataSource ds = (DataSource) dsControl.getMock(); - MockControl conControl = MockControl.createControl(Connection.class); - Connection con = (Connection) conControl.getMock(); - MockControl sfControl = MockControl.createControl(SessionFactory.class); - final SessionFactory sf = (SessionFactory) sfControl.getMock(); - MockControl sessionControl = MockControl.createControl(ImplementingSession.class); - final ImplementingSession session = (ImplementingSession) sessionControl.getMock(); - MockControl txControl = MockControl.createControl(Transaction.class); - Transaction tx = (Transaction) txControl.getMock(); - MockControl queryControl = MockControl.createControl(Query.class); - Query query = (Query) queryControl.getMock(); + final DataSource ds = mock(DataSource.class); + Connection con = mock(Connection.class); + final SessionFactory sf = mock(SessionFactory.class); + final ImplementingSession session = mock(ImplementingSession.class); + Transaction tx = mock(Transaction.class); + Query query = mock(Query.class); final List list = new ArrayList(); list.add("test"); - con.getTransactionIsolation(); - conControl.setReturnValue(Connection.TRANSACTION_READ_COMMITTED); - con.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); - conControl.setVoidCallable(1); - con.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); - conControl.setVoidCallable(1); - con.isReadOnly(); - conControl.setReturnValue(false, 1); - sf.openSession(); - sfControl.setReturnValue(session, 1); - session.getTransaction(); - sessionControl.setReturnValue(tx, 1); - tx.setTimeout(10); - txControl.setVoidCallable(1); - tx.begin(); - txControl.setVoidCallable(1); - session.connection(); - sessionControl.setReturnValue(con, 3); - session.createQuery("some query string"); - sessionControl.setReturnValue(query, 1); - query.list(); - queryControl.setReturnValue(list, 1); - tx.commit(); - txControl.setVoidCallable(1); - session.isConnected(); - sessionControl.setReturnValue(true, 1); - session.close(); - sessionControl.setReturnValue(null, 1); - - dsControl.replay(); - conControl.replay(); - sfControl.replay(); - sessionControl.replay(); - txControl.replay(); - queryControl.replay(); + given(con.getTransactionIsolation()).willReturn(Connection.TRANSACTION_READ_COMMITTED); + given(sf.openSession()).willReturn(session); + given(session.getTransaction()).willReturn(tx); + given(session.connection()).willReturn(con); + given(session.isOpen()).willReturn(true); + given(session.createQuery("some query string")).willReturn(query); + given(query.list()).willReturn(list); + given(session.isConnected()).willReturn(true); LocalSessionFactoryBean lsfb = new LocalSessionFactoryBean() { @Override @@ -143,42 +126,27 @@ public Object doInTransaction(TransactionStatus status) { assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sfProxy)); assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds)); assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive()); - dsControl.verify(); - conControl.verify(); - sfControl.verify(); - sessionControl.verify(); - txControl.verify(); - queryControl.verify(); + + verify(con).setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); + verify(con).setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); + verify(tx).setTimeout(10); + verify(tx).begin(); + verify(tx).commit(); + verify(session).close(); } + @Test public void testTransactionRollback() throws Exception { - MockControl conControl = MockControl.createControl(Connection.class); - Connection con = (Connection) conControl.getMock(); - MockControl sfControl = MockControl.createControl(SessionFactory.class); - final SessionFactory sf = (SessionFactory) sfControl.getMock(); - MockControl sessionControl = MockControl.createControl(ImplementingSession.class); - ImplementingSession session = (ImplementingSession) sessionControl.getMock(); - MockControl txControl = MockControl.createControl(Transaction.class); - Transaction tx = (Transaction) txControl.getMock(); - - sf.openSession(); - sfControl.setReturnValue(session, 1); - session.beginTransaction(); - sessionControl.setReturnValue(tx, 1); - session.close(); - sessionControl.setReturnValue(null, 1); - tx.rollback(); - txControl.setVoidCallable(1); - session.isConnected(); - sessionControl.setReturnValue(true, 1); - session.connection(); - sessionControl.setReturnValue(con, 2); - con.isReadOnly(); - conControl.setReturnValue(false, 1); - - sfControl.replay(); - sessionControl.replay(); - txControl.replay(); + Connection con = mock(Connection.class); + final SessionFactory sf = mock(SessionFactory.class); + ImplementingSession session = mock(ImplementingSession.class); + Transaction tx = mock(Transaction.class); + + given(sf.openSession()).willReturn(session); + given(session.beginTransaction()).willReturn(tx); + given(session.isOpen()).willReturn(true); + given(session.isConnected()).willReturn(true); + given(session.connection()).willReturn(con); PlatformTransactionManager tm = new HibernateTransactionManager(sf); TransactionTemplate tt = new TransactionTemplate(tm); @@ -201,40 +169,23 @@ public Object doInTransaction(TransactionStatus status) { assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf)); assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive()); - sfControl.verify(); - sessionControl.verify(); - txControl.verify(); + verify(session).close(); + verify(tx).rollback(); } + @Test public void testTransactionRollbackOnly() throws Exception { - MockControl conControl = MockControl.createControl(Connection.class); - Connection con = (Connection) conControl.getMock(); - MockControl sfControl = MockControl.createControl(SessionFactory.class); - final SessionFactory sf = (SessionFactory) sfControl.getMock(); - MockControl sessionControl = MockControl.createControl(ImplementingSession.class); - ImplementingSession session = (ImplementingSession) sessionControl.getMock(); - MockControl txControl = MockControl.createControl(Transaction.class); - Transaction tx = (Transaction) txControl.getMock(); - - sf.openSession(); - sfControl.setReturnValue(session, 1); - session.beginTransaction(); - sessionControl.setReturnValue(tx, 1); - session.flush(); - sessionControl.setVoidCallable(1); - session.close(); - sessionControl.setReturnValue(null, 1); - tx.rollback(); - txControl.setVoidCallable(1); - session.isConnected(); - sessionControl.setReturnValue(true, 1); - session.connection(); - sessionControl.setReturnValue(con, 2); - con.isReadOnly(); - conControl.setReturnValue(false, 1); - sfControl.replay(); - sessionControl.replay(); - txControl.replay(); + Connection con = mock(Connection.class); + final SessionFactory sf = mock(SessionFactory.class); + ImplementingSession session = mock(ImplementingSession.class); + Transaction tx = mock(Transaction.class); + + given(sf.openSession()).willReturn(session); + given(session.beginTransaction()).willReturn(tx); + given(session.isOpen()).willReturn(true); + given(session.getFlushMode()).willReturn(FlushMode.AUTO); + given(session.isConnected()).willReturn(true); + given(session.connection()).willReturn(con); PlatformTransactionManager tm = new HibernateTransactionManager(sf); TransactionTemplate tt = new TransactionTemplate(tm); @@ -252,40 +203,24 @@ public Object doInTransaction(TransactionStatus status) { }); assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf)); - sfControl.verify(); - sessionControl.verify(); - txControl.verify(); + verify(session).flush(); + verify(session).close(); + verify(tx).rollback(); } + @Test public void testParticipatingTransactionWithCommit() throws Exception { - MockControl conControl = MockControl.createControl(Connection.class); - Connection con = (Connection) conControl.getMock(); - MockControl sfControl = MockControl.createControl(SessionFactory.class); - final SessionFactory sf = (SessionFactory) sfControl.getMock(); - MockControl sessionControl = MockControl.createControl(ImplementingSession.class); - final ImplementingSession session = (ImplementingSession) sessionControl.getMock(); - MockControl txControl = MockControl.createControl(Transaction.class); - Transaction tx = (Transaction) txControl.getMock(); - - sf.openSession(); - sfControl.setReturnValue(session, 1); - session.beginTransaction(); - sessionControl.setReturnValue(tx, 1); - session.flush(); - sessionControl.setVoidCallable(1); - session.close(); - sessionControl.setReturnValue(null, 1); - tx.commit(); - txControl.setVoidCallable(1); - session.isConnected(); - sessionControl.setReturnValue(true, 1); - session.connection(); - sessionControl.setReturnValue(con, 2); - con.isReadOnly(); - conControl.setReturnValue(false, 1); - sfControl.replay(); - sessionControl.replay(); - txControl.replay(); + Connection con = mock(Connection.class); + final SessionFactory sf = mock(SessionFactory.class); + final ImplementingSession session = mock(ImplementingSession.class); + Transaction tx = mock(Transaction.class); + + given(sf.openSession()).willReturn(session); + given(session.beginTransaction()).willReturn(tx); + given(session.isOpen()).willReturn(true); + given(session.getFlushMode()).willReturn(FlushMode.AUTO); + given(session.isConnected()).willReturn(true); + given(session.connection()).willReturn(con); LocalSessionFactoryBean lsfb = new LocalSessionFactoryBean() { @Override @@ -316,38 +251,24 @@ public Object doInTransaction(TransactionStatus status) { }); assertTrue("Correct result list", result == l); - sfControl.verify(); - sessionControl.verify(); - txControl.verify(); + verify(session).flush(); + verify(session).close(); + verify(tx).commit(); } + @Test public void testParticipatingTransactionWithRollback() throws Exception { - MockControl conControl = MockControl.createControl(Connection.class); - Connection con = (Connection) conControl.getMock(); - MockControl sfControl = MockControl.createControl(SessionFactory.class); - final SessionFactory sf = (SessionFactory) sfControl.getMock(); - MockControl sessionControl = MockControl.createControl(ImplementingSession.class); - ImplementingSession session = (ImplementingSession) sessionControl.getMock(); - MockControl txControl = MockControl.createControl(Transaction.class); - Transaction tx = (Transaction) txControl.getMock(); - - sf.openSession(); - sfControl.setReturnValue(session, 1); - session.beginTransaction(); - sessionControl.setReturnValue(tx, 1); - session.close(); - sessionControl.setReturnValue(null, 1); - tx.rollback(); - txControl.setVoidCallable(1); - session.isConnected(); - sessionControl.setReturnValue(true, 1); - session.connection(); - sessionControl.setReturnValue(con, 2); - con.isReadOnly(); - conControl.setReturnValue(false, 1); - sfControl.replay(); - sessionControl.replay(); - txControl.replay(); + Connection con = mock(Connection.class); + final SessionFactory sf = mock(SessionFactory.class); + ImplementingSession session = mock(ImplementingSession.class); + Transaction tx = mock(Transaction.class); + + given(sf.openSession()).willReturn(session); + given(session.beginTransaction()).willReturn(tx); + given(session.isOpen()).willReturn(true); + given(session.getFlushMode()).willReturn(FlushMode.AUTO); + given(session.isConnected()).willReturn(true); + given(session.connection()).willReturn(con); PlatformTransactionManager tm = new HibernateTransactionManager(sf); final TransactionTemplate tt = new TransactionTemplate(tm); @@ -369,41 +290,27 @@ public Object doInTransaction(TransactionStatus status) { // expected } - sfControl.verify(); - sessionControl.verify(); - txControl.verify(); + verify(session).close(); + verify(tx).rollback(); } + @Test public void testParticipatingTransactionWithRollbackOnly() throws Exception { - MockControl conControl = MockControl.createControl(Connection.class); - Connection con = (Connection) conControl.getMock(); - MockControl sfControl = MockControl.createControl(SessionFactory.class); - final SessionFactory sf = (SessionFactory) sfControl.getMock(); - MockControl sessionControl = MockControl.createControl(ImplementingSession.class); - ImplementingSession session = (ImplementingSession) sessionControl.getMock(); - MockControl txControl = MockControl.createControl(Transaction.class); - Transaction tx = (Transaction) txControl.getMock(); - - sf.openSession(); - sfControl.setReturnValue(session, 1); - session.beginTransaction(); - sessionControl.setReturnValue(tx, 1); - session.close(); - sessionControl.setReturnValue(null, 1); - tx.rollback(); - txControl.setVoidCallable(1); - session.isConnected(); - sessionControl.setReturnValue(true, 1); - session.connection(); - sessionControl.setReturnValue(con, 2); - con.isReadOnly(); - conControl.setReturnValue(false, 1); - sfControl.replay(); - sessionControl.replay(); - txControl.replay(); + Connection con = mock(Connection.class); + final SessionFactory sf = mock(SessionFactory.class); + ImplementingSession session = mock(ImplementingSession.class); + Transaction tx = mock(Transaction.class); + + given(sf.openSession()).willReturn(session); + given(session.beginTransaction()).willReturn(tx); + given(session.isOpen()).willReturn(true); + given(session.isConnected()).willReturn(true); + given(session.connection()).willReturn(con); PlatformTransactionManager tm = new HibernateTransactionManager(sf); final TransactionTemplate tt = new TransactionTemplate(tm); + final List l = new ArrayList(); + l.add("test"); try { tt.execute(new TransactionCallback() { @@ -424,55 +331,28 @@ public Object doInTransaction(TransactionStatus status) { // expected } - sfControl.verify(); - sessionControl.verify(); - txControl.verify(); + verify(session).close(); + verify(tx).rollback(); } - public void testParticipatingTransactionWithWithRequiresNew() throws Exception { - MockControl sfControl = MockControl.createControl(SessionFactory.class); - final SessionFactory sf = (SessionFactory) sfControl.getMock(); - MockControl session1Control = MockControl.createControl(ImplementingSession.class); - ImplementingSession session1 = (ImplementingSession) session1Control.getMock(); - MockControl session2Control = MockControl.createControl(ImplementingSession.class); - ImplementingSession session2 = (ImplementingSession) session2Control.getMock(); - MockControl conControl = MockControl.createControl(Connection.class); - Connection con = (Connection) conControl.getMock(); - MockControl txControl = MockControl.createControl(Transaction.class); - Transaction tx = (Transaction) txControl.getMock(); - - sf.openSession(); - sfControl.setReturnValue(session1, 1); - sf.openSession(); - sfControl.setReturnValue(session2, 1); - session1.beginTransaction(); - session1Control.setReturnValue(tx, 1); - session2.beginTransaction(); - session2Control.setReturnValue(tx, 1); - session2.flush(); - session2Control.setVoidCallable(1); - session1.close(); - session1Control.setReturnValue(null, 1); - session2.close(); - session2Control.setReturnValue(null, 1); - tx.commit(); - txControl.setVoidCallable(2); - session1.isConnected(); - session1Control.setReturnValue(true, 1); - session1.connection(); - session1Control.setReturnValue(con, 2); - session2.isConnected(); - session2Control.setReturnValue(true, 1); - session2.connection(); - session2Control.setReturnValue(con, 2); - con.isReadOnly(); - conControl.setReturnValue(false, 2); - - sfControl.replay(); - session1Control.replay(); - session2Control.replay(); - conControl.replay(); - txControl.replay(); + @Test + public void testParticipatingTransactionWithRequiresNew() throws Exception { + final SessionFactory sf = mock(SessionFactory.class); + ImplementingSession session1 = mock(ImplementingSession.class); + ImplementingSession session2 = mock(ImplementingSession.class); + Connection con = mock(Connection.class); + Transaction tx = mock(Transaction.class); + + given(sf.openSession()).willReturn(session1, session2); + given(session1.beginTransaction()).willReturn(tx); + given(session1.isOpen()).willReturn(true); + given(session2.beginTransaction()).willReturn(tx); + given(session2.isOpen()).willReturn(true); + given(session2.getFlushMode()).willReturn(FlushMode.AUTO); + given(session1.isConnected()).willReturn(true); + given(session1.connection()).willReturn(con); + given(session2.isConnected()).willReturn(true); + given(session2.connection()).willReturn(con); PlatformTransactionManager tm = new HibernateTransactionManager(sf); final TransactionTemplate tt = new TransactionTemplate(tm); @@ -506,42 +386,26 @@ public Object doInTransaction(TransactionStatus status) { }); assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf)); - sfControl.verify(); - session1Control.verify(); - session2Control.verify(); - conControl.verify(); - txControl.verify(); + verify(session2).flush(); + verify(session1).close(); + verify(session2).close(); + verify(tx, times(2)).commit(); } - public void testParticipatingTransactionWithWithNotSupported() throws Exception { - MockControl sfControl = MockControl.createControl(SessionFactory.class); - final SessionFactory sf = (SessionFactory) sfControl.getMock(); - MockControl sessionControl = MockControl.createControl(ImplementingSession.class); - ImplementingSession session = (ImplementingSession) sessionControl.getMock(); - MockControl conControl = MockControl.createControl(Connection.class); - Connection con = (Connection) conControl.getMock(); - MockControl txControl = MockControl.createControl(Transaction.class); - Transaction tx = (Transaction) txControl.getMock(); - - sf.openSession(); - sfControl.setReturnValue(session, 1); - session.beginTransaction(); - sessionControl.setReturnValue(tx, 1); - session.close(); - sessionControl.setReturnValue(null, 1); - tx.commit(); - txControl.setVoidCallable(1); - session.isConnected(); - sessionControl.setReturnValue(true, 1); - session.connection(); - sessionControl.setReturnValue(con, 2); - con.isReadOnly(); - conControl.setReturnValue(false, 1); - - sfControl.replay(); - sessionControl.replay(); - conControl.replay(); - txControl.replay(); + @Test + public void testParticipatingTransactionWithNotSupported() throws Exception { + final SessionFactory sf = mock(SessionFactory.class); + ImplementingSession session = mock(ImplementingSession.class); + Connection con = mock(Connection.class); + Transaction tx = mock(Transaction.class); + + given(sf.openSession()).willReturn(session); + given(session.getSessionFactory()).willReturn(sf); + given(session.beginTransaction()).willReturn(tx); + given(session.isOpen()).willReturn(true); + given(session.getFlushMode()).willReturn(FlushMode.AUTO); + given(session.isConnected()).willReturn(true); + given(session.connection()).willReturn(con); HibernateTransactionManager tm = new HibernateTransactionManager(sf); final TransactionTemplate tt = new TransactionTemplate(tm); @@ -574,25 +438,18 @@ public Object doInTransaction(TransactionStatus status) { }); assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf)); - sfControl.verify(); - sessionControl.verify(); - txControl.verify(); + verify(session).close(); + verify(tx).commit(); } + @Test public void testTransactionWithPropagationSupports() throws Exception { - MockControl sfControl = MockControl.createControl(SessionFactory.class); - final SessionFactory sf = (SessionFactory) sfControl.getMock(); - MockControl sessionControl = MockControl.createControl(ImplementingSession.class); - final ImplementingSession session = (ImplementingSession) sessionControl.getMock(); - - sf.openSession(); - sfControl.setReturnValue(session, 1); - session.flush(); - sessionControl.setVoidCallable(1); - session.close(); - sessionControl.setReturnValue(null, 1); - sfControl.replay(); - sessionControl.replay(); + final SessionFactory sf = mock(SessionFactory.class); + final Session session = mock(Session.class); + + given(sf.openSession()).willReturn(session); + given(session.getSessionFactory()).willReturn(sf); + given(session.getFlushMode()).willReturn(FlushMode.MANUAL); LocalSessionFactoryBean lsfb = new LocalSessionFactoryBean() { @Override @@ -623,47 +480,27 @@ public Object doInTransaction(TransactionStatus status) { }); assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sfProxy)); - sfControl.verify(); - sessionControl.verify(); + InOrder ordered = inOrder(session); + ordered.verify(session).flush(); + ordered.verify(session).close(); } + @Test public void testTransactionWithPropagationSupportsAndInnerTransaction() throws Exception { - MockControl sfControl = MockControl.createControl(SessionFactory.class); - final SessionFactory sf = (SessionFactory) sfControl.getMock(); - MockControl session1Control = MockControl.createControl(ImplementingSession.class); - final ImplementingSession session1 = (ImplementingSession) session1Control.getMock(); - MockControl session2Control = MockControl.createControl(ImplementingSession.class); - final ImplementingSession session2 = (ImplementingSession) session2Control.getMock(); - MockControl conControl = MockControl.createControl(Connection.class); - Connection con = (Connection) conControl.getMock(); - MockControl txControl = MockControl.createControl(Transaction.class); - Transaction tx = (Transaction) txControl.getMock(); - - sf.openSession(); - sfControl.setReturnValue(session1, 1); - session1.flush(); - session1Control.setVoidCallable(1); - session1.close(); - session1Control.setReturnValue(null, 1); - - sf.openSession(); - sfControl.setReturnValue(session2, 1); - session2.beginTransaction(); - session2Control.setReturnValue(tx, 1); - session2.connection(); - session2Control.setReturnValue(con, 2); - session2.flush(); - session2Control.setVoidCallable(1); - tx.commit(); - txControl.setVoidCallable(1); - session2.isConnected(); - session2Control.setReturnValue(true, 1); - session2.close(); - session2Control.setReturnValue(null, 1); - sfControl.replay(); - session1Control.replay(); - session2Control.replay(); - txControl.replay(); + final SessionFactory sf = mock(SessionFactory.class); + final ImplementingSession session1 = mock(ImplementingSession.class); + final ImplementingSession session2 = mock(ImplementingSession.class); + Connection con = mock(Connection.class); + Transaction tx = mock(Transaction.class); + + given(sf.openSession()).willReturn(session1, session2); + given(session1.getSessionFactory()).willReturn(sf); + given(session1.getFlushMode()).willReturn(FlushMode.AUTO); + given(session2.beginTransaction()).willReturn(tx); + given(session2.connection()).willReturn(con); + given(session2.getFlushMode()).willReturn(FlushMode.AUTO); + given(session2.isOpen()).willReturn(true); + given(session2.isConnected()).willReturn(true); LocalSessionFactoryBean lsfb = new LocalSessionFactoryBean() { @Override @@ -710,56 +547,117 @@ public Object doInTransaction(TransactionStatus status) { }); assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf)); - sfControl.verify(); - session1Control.verify(); - session2Control.verify(); - txControl.verify(); + verify(session1).flush(); + verify(session1).close(); + verify(session2).flush(); + verify(session2).close(); + verify(tx).commit(); + } + + @Test + public void testTransactionCommitWithEntityInterceptor() throws Exception { + Interceptor entityInterceptor = mock(Interceptor.class); + Connection con = mock(Connection.class); + final SessionFactory sf = mock(SessionFactory.class); + ImplementingSession session = mock(ImplementingSession.class); + SessionBuilder options = mock(SessionBuilder.class); + Transaction tx = mock(Transaction.class); + + given(sf.withOptions()).willReturn(options); + given(options.interceptor(entityInterceptor)).willReturn(options); + given(options.openSession()).willReturn(session); + given(session.beginTransaction()).willReturn(tx); + given(session.isOpen()).willReturn(true); + given(session.isConnected()).willReturn(true); + given(session.connection()).willReturn(con); + + HibernateTransactionManager tm = new HibernateTransactionManager(sf); + tm.setEntityInterceptor(entityInterceptor); + TransactionTemplate tt = new TransactionTemplate(tm); + tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf)); + assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive()); + + Object result = tt.execute(new TransactionCallbackWithoutResult() { + @Override + public void doInTransactionWithoutResult(TransactionStatus status) { + assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf)); + } + }); + + assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf)); + assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive()); + + verify(session).close(); + verify(tx).commit(); } + @Test + public void testTransactionCommitWithEntityInterceptorBeanName() throws Exception { + Interceptor entityInterceptor = mock(Interceptor.class); + Interceptor entityInterceptor2 = mock(Interceptor.class); + Connection con = mock(Connection.class); + final SessionFactory sf = mock(SessionFactory.class); + ImplementingSession session = mock(ImplementingSession.class); + SessionBuilder options = mock(SessionBuilder.class); + Transaction tx = mock(Transaction.class); + + given(sf.withOptions()).willReturn(options); + given(options.interceptor(entityInterceptor)).willReturn(options); + given(options.interceptor(entityInterceptor2)).willReturn(options); + given(options.openSession()).willReturn(session); + given(session.beginTransaction()).willReturn(tx); + given(session.isOpen()).willReturn(true); + given(session.isConnected()).willReturn(true); + given(session.connection()).willReturn(con); + + BeanFactory beanFactory = mock(BeanFactory.class); + given(beanFactory.getBean("entityInterceptor", Interceptor.class)).willReturn( + entityInterceptor, entityInterceptor2); + + HibernateTransactionManager tm = new HibernateTransactionManager(sf); + tm.setEntityInterceptorBeanName("entityInterceptor"); + tm.setBeanFactory(beanFactory); + + TransactionTemplate tt = new TransactionTemplate(tm); + tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf)); + assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive()); + + for (int i = 0; i < 2; i++) { + tt.execute(new TransactionCallbackWithoutResult() { + @Override + public void doInTransactionWithoutResult(TransactionStatus status) { + assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf)); + } + }); + } + + assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf)); + assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive()); + + verify(session, times(2)).close(); + verify(tx, times(2)).commit(); + } + + @Test public void testTransactionCommitWithReadOnly() throws Exception { - MockControl conControl = MockControl.createControl(Connection.class); - Connection con = (Connection) conControl.getMock(); - MockControl sfControl = MockControl.createControl(SessionFactory.class); - final SessionFactory sf = (SessionFactory) sfControl.getMock(); - MockControl sessionControl = MockControl.createControl(ImplementingSession.class); - ImplementingSession session = (ImplementingSession) sessionControl.getMock(); - MockControl txControl = MockControl.createControl(Transaction.class); - Transaction tx = (Transaction) txControl.getMock(); - MockControl queryControl = MockControl.createControl(Query.class); - Query query = (Query) queryControl.getMock(); + Connection con = mock(Connection.class); + final SessionFactory sf = mock(SessionFactory.class); + ImplementingSession session = mock(ImplementingSession.class); + Transaction tx = mock(Transaction.class); + Query query = mock(Query.class); final List list = new ArrayList(); list.add("test"); - sf.openSession(); - sfControl.setReturnValue(session, 1); - session.beginTransaction(); - sessionControl.setReturnValue(tx, 1); - session.setFlushMode(FlushMode.MANUAL); - sessionControl.setVoidCallable(1); - session.connection(); - sessionControl.setReturnValue(con, 2); - con.setReadOnly(true); - conControl.setVoidCallable(1); - session.createQuery("some query string"); - sessionControl.setReturnValue(query, 1); - query.list(); - queryControl.setReturnValue(list, 1); - tx.commit(); - txControl.setVoidCallable(1); - session.isConnected(); - sessionControl.setReturnValue(true, 1); - con.isReadOnly(); - conControl.setReturnValue(true, 1); - con.setReadOnly(false); - conControl.setVoidCallable(1); - session.close(); - sessionControl.setReturnValue(null, 1); - - conControl.replay(); - sfControl.replay(); - sessionControl.replay(); - txControl.replay(); - queryControl.replay(); + given(sf.openSession()).willReturn(session); + given(session.beginTransaction()).willReturn(tx); + given(session.connection()).willReturn(con); + given(session.isOpen()).willReturn(true); + given(session.createQuery("some query string")).willReturn(query); + given(query.list()).willReturn(list); + given(session.isConnected()).willReturn(true); + given(con.isReadOnly()).willReturn(true); HibernateTransactionManager tm = new HibernateTransactionManager(sf); TransactionTemplate tt = new TransactionTemplate(tm); @@ -782,48 +680,30 @@ public Object doInTransaction(TransactionStatus status) { assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf)); assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive()); - conControl.verify(); - sfControl.verify(); - sessionControl.verify(); - txControl.verify(); - queryControl.verify(); + verify(session).setFlushMode(FlushMode.MANUAL); + verify(con).setReadOnly(true); + verify(tx).commit(); + verify(con).setReadOnly(false); + verify(session).close(); } + @Test public void testTransactionCommitWithFlushFailure() throws Exception { - MockControl conControl = MockControl.createControl(Connection.class); - Connection con = (Connection) conControl.getMock(); - MockControl sfControl = MockControl.createControl(SessionFactory.class); - final SessionFactory sf = (SessionFactory) sfControl.getMock(); - MockControl sessionControl = MockControl.createControl(ImplementingSession.class); - ImplementingSession session = (ImplementingSession) sessionControl.getMock(); - MockControl txControl = MockControl.createControl(Transaction.class); - Transaction tx = (Transaction) txControl.getMock(); - - sf.openSession(); - sfControl.setReturnValue(session, 1); - session.beginTransaction(); - sessionControl.setReturnValue(tx, 1); - tx.commit(); + Connection con = mock(Connection.class); + final SessionFactory sf = mock(SessionFactory.class); + ImplementingSession session = mock(ImplementingSession.class); + Transaction tx = mock(Transaction.class); + + given(sf.openSession()).willReturn(session); + given(session.beginTransaction()).willReturn(tx); + given(session.isOpen()).willReturn(true); SQLException sqlEx = new SQLException("argh", "27"); Exception rootCause = null; ConstraintViolationException jdbcEx = new ConstraintViolationException("mymsg", sqlEx, null); - txControl.setThrowable(jdbcEx, 1); rootCause = jdbcEx; - session.close(); - sessionControl.setReturnValue(null, 1); - tx.rollback(); - txControl.setVoidCallable(1); - session.isConnected(); - sessionControl.setReturnValue(true, 1); - session.connection(); - sessionControl.setReturnValue(con, 2); - con.isReadOnly(); - conControl.setReturnValue(false, 1); - - sfControl.replay(); - sessionControl.replay(); - txControl.replay(); - conControl.replay(); + willThrow(jdbcEx).given(tx).commit(); + given(session.isConnected()).willReturn(true); + given(session.connection()).willReturn(con); HibernateTransactionManager tm = new HibernateTransactionManager(sf); TransactionTemplate tt = new TransactionTemplate(tm); @@ -850,50 +730,25 @@ public Object doInTransaction(TransactionStatus status) { assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf)); assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive()); - sfControl.verify(); - sessionControl.verify(); - txControl.verify(); - conControl.verify(); + + verify(session).close(); + verify(tx).rollback(); } + @Test public void testTransactionCommitWithPreBound() throws Exception { - MockControl dsControl = MockControl.createControl(DataSource.class); - final DataSource ds = (DataSource) dsControl.getMock(); - MockControl conControl = MockControl.createControl(Connection.class); - Connection con = (Connection) conControl.getMock(); - MockControl sfControl = MockControl.createControl(SessionFactory.class); - final SessionFactory sf = (SessionFactory) sfControl.getMock(); - MockControl sessionControl = MockControl.createControl(ImplementingSession.class); - final ImplementingSession session = (ImplementingSession) sessionControl.getMock(); - MockControl txControl = MockControl.createControl(Transaction.class); - Transaction tx = (Transaction) txControl.getMock(); - - session.getFlushMode(); - sessionControl.setReturnValue(FlushMode.AUTO, 2); - session.beginTransaction(); - sessionControl.setReturnValue(tx, 1); - session.connection(); - sessionControl.setReturnValue(con, 3); - con.getTransactionIsolation(); - conControl.setReturnValue(Connection.TRANSACTION_READ_COMMITTED); - con.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); - conControl.setVoidCallable(1); - con.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); - conControl.setVoidCallable(1); - tx.commit(); - txControl.setVoidCallable(1); - session.isConnected(); - sessionControl.setReturnValue(true, 1); - con.isReadOnly(); - conControl.setReturnValue(false, 1); - session.disconnect(); - sessionControl.setReturnValue(null, 1); - - dsControl.replay(); - conControl.replay(); - sfControl.replay(); - sessionControl.replay(); - txControl.replay(); + final DataSource ds = mock(DataSource.class); + Connection con = mock(Connection.class); + final SessionFactory sf = mock(SessionFactory.class); + final ImplementingSession session = mock(ImplementingSession.class); + Transaction tx = mock(Transaction.class); + + given(session.beginTransaction()).willReturn(tx); + given(session.isOpen()).willReturn(true); + given(session.getFlushMode()).willReturn(FlushMode.MANUAL); + given(session.connection()).willReturn(con); + given(con.getTransactionIsolation()).willReturn(Connection.TRANSACTION_READ_COMMITTED); + given(session.isConnected()).willReturn(true); HibernateTransactionManager tm = new HibernateTransactionManager(); tm.setSessionFactory(sf); @@ -928,55 +783,29 @@ public Object doInTransaction(TransactionStatus status) { assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds)); assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive()); - dsControl.verify(); - conControl.verify(); - sfControl.verify(); - sessionControl.verify(); - txControl.verify(); + InOrder ordered = inOrder(session, con); + ordered.verify(con).setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); + ordered.verify(session).setFlushMode(FlushMode.AUTO); + ordered.verify(con).setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); + ordered.verify(session).setFlushMode(FlushMode.MANUAL); + verify(tx).commit(); + verify(session).disconnect(); } + @Test public void testTransactionRollbackWithPreBound() throws Exception { - MockControl dsControl = MockControl.createControl(DataSource.class); - final DataSource ds = (DataSource) dsControl.getMock(); - MockControl conControl = MockControl.createControl(Connection.class); - Connection con = (Connection) conControl.getMock(); - MockControl sfControl = MockControl.createControl(SessionFactory.class); - final SessionFactory sf = (SessionFactory) sfControl.getMock(); - MockControl sessionControl = MockControl.createControl(ImplementingSession.class); - final ImplementingSession session = (ImplementingSession) sessionControl.getMock(); - MockControl tx1Control = MockControl.createControl(Transaction.class); - final Transaction tx1 = (Transaction) tx1Control.getMock(); - MockControl tx2Control = MockControl.createControl(Transaction.class); - final Transaction tx2 = (Transaction) tx2Control.getMock(); - - session.getFlushMode(); - sessionControl.setReturnValue(FlushMode.AUTO, 4); - session.beginTransaction(); - sessionControl.setReturnValue(tx1, 1); - tx1.rollback(); - tx1Control.setVoidCallable(1); - session.clear(); - sessionControl.setVoidCallable(1); - session.beginTransaction(); - sessionControl.setReturnValue(tx2, 1); - tx2.commit(); - tx2Control.setVoidCallable(1); - - session.isConnected(); - sessionControl.setReturnValue(true, 2); - session.connection(); - sessionControl.setReturnValue(con, 6); - con.isReadOnly(); - conControl.setReturnValue(false, 2); - session.disconnect(); - sessionControl.setReturnValue(null, 2); - - dsControl.replay(); - conControl.replay(); - sfControl.replay(); - sessionControl.replay(); - tx1Control.replay(); - tx2Control.replay(); + final DataSource ds = mock(DataSource.class); + Connection con = mock(Connection.class); + final SessionFactory sf = mock(SessionFactory.class); + final ImplementingSession session = mock(ImplementingSession.class); + final Transaction tx1 = mock(Transaction.class); + final Transaction tx2 = mock(Transaction.class); + + given(session.beginTransaction()).willReturn(tx1, tx2); + given(session.isOpen()).willReturn(true); + given(session.getFlushMode()).willReturn(FlushMode.MANUAL); + given(session.isConnected()).willReturn(true); + given(session.connection()).willReturn(con); HibernateTransactionManager tm = new HibernateTransactionManager(); tm.setSessionFactory(sf); @@ -1034,49 +863,27 @@ public void doInTransactionWithoutResult(TransactionStatus status) { assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds)); assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive()); - dsControl.verify(); - conControl.verify(); - sfControl.verify(); - sessionControl.verify(); - tx1Control.verify(); - tx2Control.verify(); + verify(tx1).rollback(); + verify(tx2).commit(); + InOrder ordered = inOrder(session); + ordered.verify(session).clear(); + ordered.verify(session).setFlushMode(FlushMode.AUTO); + ordered.verify(session).setFlushMode(FlushMode.MANUAL); + ordered.verify(session).disconnect(); } + @Test public void testTransactionRollbackWithHibernateManagedSession() throws Exception { - MockControl sfControl = MockControl.createControl(SessionFactory.class); - final SessionFactory sf = (SessionFactory) sfControl.getMock(); - MockControl sessionControl = MockControl.createControl(ImplementingSession.class); - final ImplementingSession session = (ImplementingSession) sessionControl.getMock(); - MockControl tx1Control = MockControl.createControl(Transaction.class); - final Transaction tx1 = (Transaction) tx1Control.getMock(); - MockControl tx2Control = MockControl.createControl(Transaction.class); - final Transaction tx2 = (Transaction) tx2Control.getMock(); - - sf.getCurrentSession(); - sfControl.setReturnValue(session, 2); - session.getFlushMode(); - sessionControl.setReturnValue(FlushMode.AUTO, 4); - session.getTransaction(); - sessionControl.setReturnValue(tx1, 1); - session.beginTransaction(); - sessionControl.setReturnValue(tx1, 1); - tx1.isActive(); - tx1Control.setReturnValue(false, 1); - tx1.rollback(); - tx1Control.setVoidCallable(1); - session.getTransaction(); - sessionControl.setReturnValue(tx2, 1); - session.beginTransaction(); - sessionControl.setReturnValue(tx2, 1); - tx2.isActive(); - tx2Control.setReturnValue(false, 1); - tx2.commit(); - tx2Control.setVoidCallable(1); - - sfControl.replay(); - sessionControl.replay(); - tx1Control.replay(); - tx2Control.replay(); + final SessionFactory sf = mock(SessionFactory.class); + final Session session = mock(Session.class); + final Transaction tx1 = mock(Transaction.class); + final Transaction tx2 = mock(Transaction.class); + + given(sf.getCurrentSession()).willReturn(session); + given(session.isOpen()).willReturn(true); + given(session.getTransaction()).willReturn(tx1, tx2); + given(session.beginTransaction()).willReturn(tx1, tx2); + given(session.getFlushMode()).willReturn(FlushMode.MANUAL); HibernateTransactionManager tm = new HibernateTransactionManager(); tm.setSessionFactory(sf); @@ -1118,16 +925,19 @@ public void doInTransactionWithoutResult(TransactionStatus status) { } }); - sfControl.verify(); - sessionControl.verify(); - tx1Control.verify(); - tx2Control.verify(); + verify(tx1).rollback(); + verify(tx2).commit(); + InOrder ordered = inOrder(session); + ordered.verify(session).setFlushMode(FlushMode.AUTO); + ordered.verify(session).setFlushMode(FlushMode.MANUAL); } + @Test public void testExistingTransactionWithPropagationNestedAndRollback() throws Exception { doTestExistingTransactionWithPropagationNestedAndRollback(false); } + @Test public void testExistingTransactionWithManualSavepointAndRollback() throws Exception { doTestExistingTransactionWithPropagationNestedAndRollback(true); } @@ -1135,60 +945,27 @@ public void testExistingTransactionWithManualSavepointAndRollback() throws Excep private void doTestExistingTransactionWithPropagationNestedAndRollback(final boolean manualSavepoint) throws Exception { - MockControl dsControl = MockControl.createControl(DataSource.class); - final DataSource ds = (DataSource) dsControl.getMock(); - MockControl conControl = MockControl.createControl(Connection.class); - Connection con = (Connection) conControl.getMock(); - MockControl mdControl = MockControl.createControl(DatabaseMetaData.class); - DatabaseMetaData md = (DatabaseMetaData) mdControl.getMock(); - MockControl spControl = MockControl.createControl(Savepoint.class); - Savepoint sp = (Savepoint) spControl.getMock(); - MockControl sfControl = MockControl.createControl(SessionFactory.class); - final SessionFactory sf = (SessionFactory) sfControl.getMock(); - MockControl sessionControl = MockControl.createControl(ImplementingSession.class); - ImplementingSession session = (ImplementingSession) sessionControl.getMock(); - MockControl txControl = MockControl.createControl(Transaction.class); - Transaction tx = (Transaction) txControl.getMock(); - MockControl queryControl = MockControl.createControl(Query.class); - Query query = (Query) queryControl.getMock(); + final DataSource ds = mock(DataSource.class); + Connection con = mock(Connection.class); + DatabaseMetaData md = mock(DatabaseMetaData.class); + Savepoint sp = mock(Savepoint.class); + final SessionFactory sf = mock(SessionFactory.class); + ImplementingSession session = mock(ImplementingSession.class); + Transaction tx = mock(Transaction.class); + Query query = mock(Query.class); final List list = new ArrayList(); list.add("test"); - con.isReadOnly(); - conControl.setReturnValue(false, 1); - sf.openSession(); - sfControl.setReturnValue(session, 1); - session.beginTransaction(); - sessionControl.setReturnValue(tx, 1); - session.connection(); - sessionControl.setReturnValue(con, 3); - md.supportsSavepoints(); - mdControl.setReturnValue(true, 1); - con.getMetaData(); - conControl.setReturnValue(md, 1); - con.setSavepoint(ConnectionHolder.SAVEPOINT_NAME_PREFIX + 1); - conControl.setReturnValue(sp, 1); - con.rollback(sp); - conControl.setVoidCallable(1); - session.createQuery("some query string"); - sessionControl.setReturnValue(query, 1); - query.list(); - queryControl.setReturnValue(list, 1); - session.isConnected(); - sessionControl.setReturnValue(true, 1); - session.close(); - sessionControl.setReturnValue(null, 1); - tx.commit(); - txControl.setVoidCallable(1); - - dsControl.replay(); - conControl.replay(); - mdControl.replay(); - spControl.replay(); - sfControl.replay(); - sessionControl.replay(); - txControl.replay(); - queryControl.replay(); + given(sf.openSession()).willReturn(session); + given(session.beginTransaction()).willReturn(tx); + given(session.connection()).willReturn(con); + given(session.isOpen()).willReturn(true); + given(md.supportsSavepoints()).willReturn(true); + given(con.getMetaData()).willReturn(md); + given(con.setSavepoint(ConnectionHolder.SAVEPOINT_NAME_PREFIX + 1)).willReturn(sp); + given(session.createQuery("some query string")).willReturn(query); + given(query.list()).willReturn(list); + given(session.isConnected()).willReturn(true); HibernateTransactionManager tm = new HibernateTransactionManager(); tm.setNestedTransactionAllowed(true); @@ -1228,16 +1005,14 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf)); assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds)); assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive()); - dsControl.verify(); - conControl.verify(); - mdControl.verify(); - spControl.verify(); - sfControl.verify(); - sessionControl.verify(); - txControl.verify(); - queryControl.verify(); + + verify(con).setSavepoint(ConnectionHolder.SAVEPOINT_NAME_PREFIX + 1); + verify(con).rollback(sp); + verify(session).close(); + verify(tx).commit(); } + @Test public void testTransactionCommitWithNonExistingDatabase() throws Exception { final DriverManagerDataSource ds = new DriverManagerDataSource(); LocalSessionFactoryBean lsfb = new LocalSessionFactoryBean(); @@ -1279,6 +1054,7 @@ public Object doInTransaction(TransactionStatus status) { assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive()); } + @Test public void testTransactionCommitWithPreBoundSessionAndNonExistingDatabase() throws Exception { final DriverManagerDataSource ds = new DriverManagerDataSource(); LocalSessionFactoryBean lsfb = new LocalSessionFactoryBean(); @@ -1328,6 +1104,7 @@ public Object doInTransaction(TransactionStatus status) { assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive()); } + @Test public void testTransactionCommitWithNonExistingDatabaseAndLazyConnection() throws Exception { DriverManagerDataSource dsTarget = new DriverManagerDataSource(); final LazyConnectionDataSourceProxy ds = new LazyConnectionDataSourceProxy(); @@ -1370,28 +1147,14 @@ public Object doInTransaction(TransactionStatus status) { assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive()); } + @Test public void testTransactionFlush() throws Exception { - MockControl sfControl = MockControl.createControl(SessionFactory.class); - final SessionFactory sf = (SessionFactory) sfControl.getMock(); - MockControl sessionControl = MockControl.createControl(ImplementingSession.class); - final ImplementingSession session = (ImplementingSession) sessionControl.getMock(); - MockControl txControl = MockControl.createControl(Transaction.class); - Transaction tx = (Transaction) txControl.getMock(); - - sf.openSession(); - sfControl.setReturnValue(session, 1); - session.beginTransaction(); - sessionControl.setReturnValue(tx, 1); - session.flush(); - sessionControl.setVoidCallable(1); - tx.commit(); - txControl.setVoidCallable(1); - session.close(); - sessionControl.setReturnValue(null, 1); - - sfControl.replay(); - sessionControl.replay(); - txControl.replay(); + final SessionFactory sf = mock(SessionFactory.class); + final Session session = mock(Session.class); + Transaction tx = mock(Transaction.class); + + given(sf.openSession()).willReturn(session); + given(session.beginTransaction()).willReturn(tx); HibernateTransactionManager tm = new HibernateTransactionManager(sf); tm.setPrepareConnection(false); @@ -1411,17 +1174,10 @@ public void doInTransactionWithoutResult(TransactionStatus status) { assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf)); assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive()); - sfControl.verify(); - sessionControl.verify(); - txControl.verify(); - } - @Override - protected void tearDown() { - assertTrue(TransactionSynchronizationManager.getResourceMap().isEmpty()); - assertFalse(TransactionSynchronizationManager.isSynchronizationActive()); - assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly()); - assertFalse(TransactionSynchronizationManager.isActualTransactionActive()); + verify(session).flush(); + verify(tx).commit(); + verify(session).close(); } diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateTransactionManager.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateTransactionManager.java index 896a91379fb8..abd06c801ac2 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateTransactionManager.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/HibernateTransactionManager.java @@ -887,6 +887,7 @@ public boolean isRollbackOnly() { (hasConnectionHolder() && getConnectionHolder().isRollbackOnly()); } + @Override public void flush() { try { this.sessionHolder.getSession().flush(); diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/annotation/AnnotationSessionFactoryBean.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/annotation/AnnotationSessionFactoryBean.java index d944ef497dc4..72f5ee42ea7d 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/annotation/AnnotationSessionFactoryBean.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/annotation/AnnotationSessionFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -79,6 +79,8 @@ public class AnnotationSessionFactoryBean extends LocalSessionFactoryBean implem private static final String RESOURCE_PATTERN = "/**/*.class"; + private static final String PACKAGE_INFO_SUFFIX = ".package-info"; + private Class[] annotatedClasses; @@ -101,7 +103,7 @@ public AnnotationSessionFactoryBean() { @Override - public void setConfigurationClass(Class configurationClass) { + public void setConfigurationClass(Class configurationClass) { if (configurationClass == null || !AnnotationConfiguration.class.isAssignableFrom(configurationClass)) { throw new IllegalArgumentException( "AnnotationSessionFactoryBean only supports AnnotationConfiguration or subclasses"); @@ -191,9 +193,12 @@ protected void scanPackages(AnnotationConfiguration config) { if (resource.isReadable()) { MetadataReader reader = readerFactory.getMetadataReader(resource); String className = reader.getClassMetadata().getClassName(); - if (matchesFilter(reader, readerFactory)) { + if (matchesEntityTypeFilter(reader, readerFactory)) { config.addAnnotatedClass(this.resourcePatternResolver.getClassLoader().loadClass(className)); } + else if (className.endsWith(PACKAGE_INFO_SUFFIX)) { + config.addPackage(className.substring(0, className.length() - PACKAGE_INFO_SUFFIX.length())); + } } } } @@ -211,7 +216,7 @@ protected void scanPackages(AnnotationConfiguration config) { * Check whether any of the configured entity type filters matches * the current class descriptor contained in the metadata reader. */ - private boolean matchesFilter(MetadataReader reader, MetadataReaderFactory readerFactory) throws IOException { + private boolean matchesEntityTypeFilter(MetadataReader reader, MetadataReaderFactory readerFactory) throws IOException { if (this.entityTypeFilters != null) { for (TypeFilter filter : this.entityTypeFilters) { if (filter.match(reader, readerFactory)) { diff --git a/spring-orm/src/main/java/org/springframework/orm/jdo/DefaultJdoDialect.java b/spring-orm/src/main/java/org/springframework/orm/jdo/DefaultJdoDialect.java index 38d0f7263a4c..9d058158b91d 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jdo/DefaultJdoDialect.java +++ b/spring-orm/src/main/java/org/springframework/orm/jdo/DefaultJdoDialect.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import java.lang.reflect.Method; import java.sql.Connection; import java.sql.SQLException; +import javax.jdo.Constants; import javax.jdo.JDOException; import javax.jdo.PersistenceManager; import javax.jdo.Query; @@ -32,7 +33,6 @@ import org.springframework.jdbc.datasource.ConnectionHandle; import org.springframework.jdbc.support.JdbcUtils; import org.springframework.jdbc.support.SQLExceptionTranslator; -import org.springframework.transaction.InvalidIsolationLevelException; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionException; import org.springframework.util.ClassUtils; @@ -40,13 +40,13 @@ /** * Default implementation of the {@link JdoDialect} interface. - * Updated to build on JDO 2.0 or higher, as of Spring 2.5. + * Requires JDO 2.0; explicitly supports JDO API features up until 3.0. * Used as default dialect by {@link JdoAccessor} and {@link JdoTransactionManager}. * *

    Simply begins a standard JDO transaction in {@code beginTransaction}. * Returns a handle for a JDO2 DataStoreConnection on {@code getJdbcConnection}. * Calls the corresponding JDO2 PersistenceManager operation on {@code flush} - * Ignores a given query timeout in {@code applyQueryTimeout}. + * Translates {@code applyQueryTimeout} to JDO 3.0's {@code setTimeoutMillis}. * Uses a Spring SQLExceptionTranslator for exception translation, if applicable. * *

    Note that, even with JDO2, vendor-specific subclasses are still necessary @@ -122,24 +122,49 @@ public SQLExceptionTranslator getJdbcExceptionTranslator() { //------------------------------------------------------------------------- /** - * This implementation invokes the standard JDO {@code Transaction.begin} - * method. Throws an InvalidIsolationLevelException if a non-default isolation - * level is set. + * This implementation invokes the standard JDO {@link Transaction#begin()} + * method and also {@link Transaction#setIsolationLevel(String)} if necessary. * @see javax.jdo.Transaction#begin * @see org.springframework.transaction.InvalidIsolationLevelException */ public Object beginTransaction(Transaction transaction, TransactionDefinition definition) throws JDOException, SQLException, TransactionException { - if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) { - throw new InvalidIsolationLevelException( - "Standard JDO does not support custom isolation levels: " + - "use a special JdoDialect implementation for your JDO provider"); + String jdoIsolationLevel = getJdoIsolationLevel(definition); + if (jdoIsolationLevel != null) { + transaction.setIsolationLevel(jdoIsolationLevel); } transaction.begin(); return null; } + /** + * Determine the JDO isolation level String to use for the given + * Spring transaction definition. + * @param definition the Spring transaction definition + * @return the corresponding JDO isolation level String, or {@code null} + * to indicate that no isolation level should be set explicitly + * @see Transaction#setIsolationLevel(String) + * @see Constants#TX_SERIALIZABLE + * @see Constants#TX_REPEATABLE_READ + * @see Constants#TX_READ_COMMITTED + * @see Constants#TX_READ_UNCOMMITTED + */ + protected String getJdoIsolationLevel(TransactionDefinition definition) { + switch (definition.getIsolationLevel()) { + case TransactionDefinition.ISOLATION_SERIALIZABLE: + return Constants.TX_SERIALIZABLE; + case TransactionDefinition.ISOLATION_REPEATABLE_READ: + return Constants.TX_REPEATABLE_READ; + case TransactionDefinition.ISOLATION_READ_COMMITTED: + return Constants.TX_READ_COMMITTED; + case TransactionDefinition.ISOLATION_READ_UNCOMMITTED: + return Constants.TX_READ_UNCOMMITTED; + default: + return null; + } + } + /** * This implementation does nothing, as the default beginTransaction implementation * does not require any cleanup. diff --git a/spring-orm/src/test/java/org/springframework/orm/hibernate3/HibernateTransactionManagerTests.java b/spring-orm/src/test/java/org/springframework/orm/hibernate3/HibernateTransactionManagerTests.java index 534beea65664..d4d12ba036ba 100644 --- a/spring-orm/src/test/java/org/springframework/orm/hibernate3/HibernateTransactionManagerTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/hibernate3/HibernateTransactionManagerTests.java @@ -16,18 +16,6 @@ package org.springframework.orm.hibernate3; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.willThrow; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.SQLException; @@ -35,7 +23,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Properties; - import javax.sql.DataSource; import org.hibernate.FlushMode; @@ -53,6 +40,7 @@ import org.junit.After; import org.junit.Test; import org.mockito.InOrder; + import org.springframework.beans.factory.BeanFactory; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.jdbc.datasource.ConnectionHolder; @@ -69,6 +57,9 @@ import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.transaction.support.TransactionTemplate; +import static org.junit.Assert.*; +import static org.mockito.BDDMockito.*; + /** * @author Juergen Hoeller * @author Phillip Webb @@ -450,7 +441,7 @@ public Object doInHibernate(org.hibernate.Session session) { } @Test - public void testParticipatingTransactionWithWithRequiresNew() throws Exception { + public void testParticipatingTransactionWithRequiresNew() throws Exception { final SessionFactory sf = mock(SessionFactory.class); Session session1 = mock(Session.class); Session session2 = mock(Session.class); @@ -512,7 +503,7 @@ public Object doInHibernate(org.hibernate.Session session) { } @Test - public void testParticipatingTransactionWithWithNotSupported() throws Exception { + public void testParticipatingTransactionWithNotSupported() throws Exception { final SessionFactory sf = mock(SessionFactory.class); Session session = mock(Session.class); Connection con = mock(Connection.class); @@ -1399,4 +1390,5 @@ public void doInTransactionWithoutResult(TransactionStatus status) { verify(tx).commit(); verify(session).close(); } + } diff --git a/spring-orm/src/test/java/org/springframework/orm/jdo/JdoTransactionManagerTests.java b/spring-orm/src/test/java/org/springframework/orm/jdo/JdoTransactionManagerTests.java index 538ce2572bad..46003776b05a 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jdo/JdoTransactionManagerTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jdo/JdoTransactionManagerTests.java @@ -16,17 +16,6 @@ package org.springframework.orm.jdo; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.willThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - import java.lang.reflect.Proxy; import java.sql.Connection; import java.sql.DatabaseMetaData; @@ -34,7 +23,7 @@ import java.sql.Savepoint; import java.util.ArrayList; import java.util.List; - +import javax.jdo.Constants; import javax.jdo.JDOFatalDataStoreException; import javax.jdo.PersistenceManager; import javax.jdo.PersistenceManagerFactory; @@ -48,6 +37,7 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; + import org.springframework.jdbc.datasource.ConnectionHandle; import org.springframework.jdbc.datasource.ConnectionHolder; import org.springframework.jdbc.datasource.SimpleConnectionHandle; @@ -55,7 +45,6 @@ import org.springframework.orm.jdo.support.StandardPersistenceManagerProxyBean; import org.springframework.tests.sample.beans.TestBean; import org.springframework.tests.transaction.MockJtaTransaction; -import org.springframework.transaction.InvalidIsolationLevelException; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; @@ -65,6 +54,12 @@ import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.transaction.support.TransactionTemplate; +import static org.junit.Assert.*; +import static org.mockito.BDDMockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + /** * @author Juergen Hoeller * @author Phillip Webb @@ -620,24 +615,19 @@ public Object doInJdo(PersistenceManager pm) { } @Test - public void testInvalidIsolation() { + public void testIsolationLevel() { given(pmf.getPersistenceManager()).willReturn(pm); given(pm.currentTransaction()).willReturn(tx); PlatformTransactionManager tm = new JdoTransactionManager(pmf); TransactionTemplate tt = new TransactionTemplate(tm); tt.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE); - try { - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) { - } - }); - fail("Should have thrown InvalidIsolationLevelException"); - } - catch (InvalidIsolationLevelException ex) { - // expected - } + tt.execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus status) { + } + }); + verify(tx).setIsolationLevel(Constants.TX_SERIALIZABLE); verify(pm).close(); } diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/openjpa/OpenJpaEntityManagerFactoryWithAspectJWeavingIntegrationTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/openjpa/OpenJpaEntityManagerFactoryWithAspectJWeavingIntegrationTests.java index 20d9e5f029bc..a35fdac8cadb 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/openjpa/OpenJpaEntityManagerFactoryWithAspectJWeavingIntegrationTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/openjpa/OpenJpaEntityManagerFactoryWithAspectJWeavingIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,17 +19,13 @@ import org.junit.Ignore; /** - * Test that AspectJ weaving (in particular the currently shipped aspects) work with JPA (see SPR-3873 for more details). + * Test that AspectJ weaving (in particular the currently shipped aspects) work with JPA + * (see SPR-3873 for more details). * * @author Ramnivas Laddad + * @author Chris Beams */ -// TODO [SPR-10074] this test causes gradle to hang. -// When run independently e.g. `./gradlew :spring-orm:test -Dtest.single=OpenJpaEntity...` -// it works fine. When run together with all other tests e.g. `./gradlew :spring-orm:test` -// it hangs on the 'testCanSerializeProxies' test method. Note that this test DOES pass in -// Eclipse, even when the entire 'spring-orm' module is run. Run gradle with '-i' to -// get more details when reproducing the hanging test. -@Ignore("this test causes gradle to hang") +@Ignore("This test causes gradle to hang. See SPR-10333.") public class OpenJpaEntityManagerFactoryWithAspectJWeavingIntegrationTests extends OpenJpaEntityManagerFactoryIntegrationTests { @Override diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/toplink/TopLinkMultiEntityManagerFactoryIntegrationTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/toplink/TopLinkMultiEntityManagerFactoryIntegrationTests.java index 78e8f86af5bd..7b7431311435 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/toplink/TopLinkMultiEntityManagerFactoryIntegrationTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/toplink/TopLinkMultiEntityManagerFactoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,9 +26,9 @@ * Toplink-specific JPA tests with multiple EntityManagerFactory instances. * * @author Costin Leau + * @author Chris Beams */ -// TODO [SPR-10074] this test causes gradle to hang. See OJEMFWAJWIT. -@Ignore("this test causes gradle to hang. See OJEMFWAJWIT.") +@Ignore("This test causes gradle to hang. See SPR-10333.") public class TopLinkMultiEntityManagerFactoryIntegrationTests extends AbstractContainerEntityManagerFactoryIntegrationTests { diff --git a/spring-oxm/src/main/java/org/springframework/oxm/castor/CastorMarshaller.java b/spring-oxm/src/main/java/org/springframework/oxm/castor/CastorMarshaller.java index 0405e8272e74..131b782c876a 100644 --- a/spring-oxm/src/main/java/org/springframework/oxm/castor/CastorMarshaller.java +++ b/spring-oxm/src/main/java/org/springframework/oxm/castor/CastorMarshaller.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,16 +30,20 @@ import org.exolab.castor.mapping.Mapping; import org.exolab.castor.mapping.MappingException; +import org.exolab.castor.util.ObjectFactory; +import org.exolab.castor.xml.IDResolver; import org.exolab.castor.xml.MarshalException; import org.exolab.castor.xml.Marshaller; import org.exolab.castor.xml.ResolverException; import org.exolab.castor.xml.UnmarshalHandler; import org.exolab.castor.xml.Unmarshaller; import org.exolab.castor.xml.ValidationException; +import org.exolab.castor.xml.XMLClassDescriptorResolver; import org.exolab.castor.xml.XMLContext; import org.exolab.castor.xml.XMLException; import org.w3c.dom.Node; import org.xml.sax.ContentHandler; +import org.xml.sax.EntityResolver; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; @@ -56,30 +60,30 @@ import org.springframework.oxm.support.AbstractMarshaller; import org.springframework.oxm.support.SaxResourceUtils; import org.springframework.util.ObjectUtils; -import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; import org.springframework.util.xml.StaxUtils; /** - * Implementation of the {@code Marshaller} interface for Castor. By default, Castor does not require any further - * configuration, though setting target classes, target packages or providing a mapping file can be used to have more - * control over the behavior of Castor. + * Implementation of the {@code Marshaller} interface for Castor. By default, Castor does + * not require any further configuration, though setting target classes, target packages or + * providing a mapping file can be used to have more control over the behavior of Castor. * - *

    If a target class is specified using {@code setTargetClass}, the {@code CastorMarshaller} can only be - * used to unmarshal XML that represents that specific class. If you want to unmarshal multiple classes, you have to - * provide a mapping file using {@code setMappingLocations}. + *

    If a target class is specified using {@code setTargetClass}, the {@code CastorMarshaller} + * can only be used to unmarshal XML that represents that specific class. If you want to unmarshal + * multiple classes, you have to provide a mapping file using {@code setMappingLocations}. * - *

    Due to limitations of Castor's API, it is required to set the encoding used for writing to output streams. It - * defaults to {@code UTF-8}. + *

    Due to limitations of Castor's API, it is required to set the encoding used for + * writing to output streams. It defaults to {@code UTF-8}. * * @author Arjen Poutsma * @author Jakub Narloch + * @author Juergen Hoeller + * @since 3.0 * @see #setEncoding(String) * @see #setTargetClass(Class) * @see #setTargetPackages(String[]) * @see #setMappingLocation(Resource) * @see #setMappingLocations(Resource[]) - * @since 3.0 */ public class CastorMarshaller extends AbstractMarshaller implements InitializingBean, BeanClassLoaderAware { @@ -88,6 +92,7 @@ public class CastorMarshaller extends AbstractMarshaller implements Initializing */ public static final String DEFAULT_ENCODING = "UTF-8"; + private Resource[] mappingLocations; private String encoding = DEFAULT_ENCODING; @@ -98,47 +103,59 @@ public class CastorMarshaller extends AbstractMarshaller implements Initializing private boolean validating = false; - private boolean whitespacePreserve = false; - - private boolean ignoreExtraAttributes = true; - - private boolean ignoreExtraElements = false; - - private Map namespaceMappings; - - private XMLContext xmlContext; - private boolean suppressNamespaces = false; private boolean suppressXsiType = false; private boolean marshalAsDocument = true; - private String rootElement; - private boolean marshalExtendedType = true; + private String rootElement; + private String noNamespaceSchemaLocation; private String schemaLocation; private boolean useXSITypeAtRoot = false; - private Map processingInstructions; + private boolean whitespacePreserve = false; - private Map namespaceToPackageMapping; + private boolean ignoreExtraAttributes = true; - private ClassLoader classLoader; + private boolean ignoreExtraElements = false; - private Object root; + private Object rootObject; private boolean reuseObjects = false; private boolean clearCollections = false; + private Map castorProperties; + + private Map doctypes; + + private Map processingInstructions; + + private Map namespaceMappings; + + private Map namespaceToPackageMapping; + + private EntityResolver entityResolver; + + private XMLClassDescriptorResolver classDescriptorResolver; + + private IDResolver idResolver; + + private ObjectFactory objectFactory; + + private ClassLoader beanClassLoader; + + private XMLContext xmlContext; + + /** * Set the encoding to be used for stream access. - * * @see #DEFAULT_ENCODING */ public void setEncoding(String encoding) { @@ -146,7 +163,7 @@ public void setEncoding(String encoding) { } /** - * Set the locations of the Castor XML Mapping files. + * Set the locations of the Castor XML mapping files. */ public void setMappingLocation(Resource mappingLocation) { this.mappingLocations = new Resource[]{mappingLocation}; @@ -190,8 +207,8 @@ public void setTargetPackages(String[] targetPackages) { } /** - * Set whether this marshaller should validate in- and outgoing documents.

    Default is {@code false}. - * + * Set whether this marshaller should validate in- and outgoing documents. + *

    Default is {@code false}. * @see Marshaller#setValidation(boolean) */ public void setValidating(boolean validating) { @@ -199,152 +216,180 @@ public void setValidating(boolean validating) { } /** - * Set whether the Castor {@link Unmarshaller} should preserve "ignorable" whitespace.

    Default is - * {@code false}. - * - * @see org.exolab.castor.xml.Unmarshaller#setWhitespacePreserve(boolean) + * Sets whether this marshaller should output namespaces. + *

    The default is {@code false}, i.e. namespaces are written. + * @see org.exolab.castor.xml.Marshaller#setSuppressNamespaces(boolean) */ - public void setWhitespacePreserve(boolean whitespacePreserve) { - this.whitespacePreserve = whitespacePreserve; + public void setSuppressNamespaces(boolean suppressNamespaces) { + this.suppressNamespaces = suppressNamespaces; } /** - * Set whether the Castor {@link Unmarshaller} should ignore attributes that do not match a specific field.

    Default - * is {@code true}: extra attributes are ignored. - * - * @see org.exolab.castor.xml.Unmarshaller#setIgnoreExtraAttributes(boolean) + * Set whether this marshaller should output the {@code xsi:type} attribute. + *

    The default is {@code false}, i.e. the {@code xsi:type} is written. + * @see org.exolab.castor.xml.Marshaller#setSuppressXSIType(boolean) */ - public void setIgnoreExtraAttributes(boolean ignoreExtraAttributes) { - this.ignoreExtraAttributes = ignoreExtraAttributes; + public void setSuppressXsiType(boolean suppressXsiType) { + this.suppressXsiType = suppressXsiType; } /** - * Set whether the Castor {@link Unmarshaller} should ignore elements that do not match a specific field.

    Default - * is - * {@code false}, extra attributes are flagged as an error. - * - * @see org.exolab.castor.xml.Unmarshaller#setIgnoreExtraElements(boolean) + * Set whether this marshaller should output the xml declaration. + *

    The default is {@code true}, the XML declaration will be written. + * @see org.exolab.castor.xml.Marshaller#setMarshalAsDocument(boolean) */ - public void setIgnoreExtraElements(boolean ignoreExtraElements) { - this.ignoreExtraElements = ignoreExtraElements; + public void setMarshalAsDocument(boolean marshalAsDocument) { + this.marshalAsDocument = marshalAsDocument; } /** - * Set the namespace mappings. Property names are interpreted as namespace prefixes; values are namespace URIs. - * - * @see org.exolab.castor.xml.Marshaller#setNamespaceMapping(String, String) + * Set whether this marshaller should output for given type the {@code xsi:type} attribute. + *

    The default is {@code true}, the {@code xsi:type} attribute will be written. + * @see org.exolab.castor.xml.Marshaller#setMarshalExtendedType(boolean) */ - public void setNamespaceMappings(Map namespaceMappings) { - this.namespaceMappings = namespaceMappings; + public void setMarshalExtendedType(boolean marshalExtendedType) { + this.marshalExtendedType = marshalExtendedType; } /** - * Returns whether this marshaller should output namespaces. + * Set the name of the root element. + * @see org.exolab.castor.xml.Marshaller#setRootElement(String) */ - public boolean isSuppressNamespaces() { - return suppressNamespaces; + public void setRootElement(String rootElement) { + this.rootElement = rootElement; } /** - * Sets whether this marshaller should output namespaces. The default is {@code false}, i.e. namespaces are written. - * - * @see org.exolab.castor.xml.Marshaller#setSuppressNamespaces(boolean) + * Set the value of {@code xsi:noNamespaceSchemaLocation} attribute. When set, the + * {@code xsi:noNamespaceSchemaLocation} attribute will be written for the root element. + * @see org.exolab.castor.xml.Marshaller#setNoNamespaceSchemaLocation(String) */ - public void setSuppressNamespaces(boolean suppressNamespaces) { - this.suppressNamespaces = suppressNamespaces; + public void setNoNamespaceSchemaLocation(String noNamespaceSchemaLocation) { + this.noNamespaceSchemaLocation = noNamespaceSchemaLocation; } /** - * Sets whether this marshaller should output the xsi:type attribute. + * Set the value of {@code xsi:schemaLocation} attribute. When set, the + * {@code xsi:schemaLocation} attribute will be written for the root element. + * @see org.exolab.castor.xml.Marshaller#setSchemaLocation(String) */ - public boolean isSuppressXsiType() { - return suppressXsiType; + public void setSchemaLocation(String schemaLocation) { + this.schemaLocation = schemaLocation; } /** - * Sets whether this marshaller should output the {@code xsi:type} attribute. The default is {@code false}, i.e. the - * {@code xsi:type} is written. - * - * @see org.exolab.castor.xml.Marshaller#setSuppressXSIType(boolean) + * Sets whether this marshaller should output the {@code xsi:type} attribute for the root element. + * This can be useful when the type of the element can not be simply determined from the element name. + *

    The default is {@code false}: The {@code xsi:type} attribute for the root element won't be written. + * @see org.exolab.castor.xml.Marshaller#setUseXSITypeAtRoot(boolean) */ - public void setSuppressXsiType(boolean suppressXsiType) { - this.suppressXsiType = suppressXsiType; + public void setUseXSITypeAtRoot(boolean useXSITypeAtRoot) { + this.useXSITypeAtRoot = useXSITypeAtRoot; } /** - * Sets whether this marshaller should output the xml declaration.

    The default is {@code true}, the xml - * declaration will be written. - * - * @see org.exolab.castor.xml.Marshaller#setMarshalAsDocument(boolean) + * Set whether the Castor {@link Unmarshaller} should preserve "ignorable" whitespace. + *

    Default is {@code false}. + * @see org.exolab.castor.xml.Unmarshaller#setWhitespacePreserve(boolean) */ - public void setMarshalAsDocument(boolean marshalAsDocument) { - this.marshalAsDocument = marshalAsDocument; + public void setWhitespacePreserve(boolean whitespacePreserve) { + this.whitespacePreserve = whitespacePreserve; } /** - * Sets the name of the root element. - * - * @see org.exolab.castor.xml.Marshaller#setRootElement(String) + * Set whether the Castor {@link Unmarshaller} should ignore attributes that do not match a specific field. + *

    Default is {@code true}: Extra attributes are ignored. + * @see org.exolab.castor.xml.Unmarshaller#setIgnoreExtraAttributes(boolean) */ - public void setRootElement(String rootElement) { - this.rootElement = rootElement; + public void setIgnoreExtraAttributes(boolean ignoreExtraAttributes) { + this.ignoreExtraAttributes = ignoreExtraAttributes; } /** - * Sets whether this marshaller should output for given type the {@code xsi:type} attribute.

    The default is {@code - * true}, the {@code xsi:type} attribute will be written. - * - * @see org.exolab.castor.xml.Marshaller#setMarshalExtendedType(boolean) + * Set whether the Castor {@link Unmarshaller} should ignore elements that do not match a specific field. + *

    Default is {@code false}: Extra elements are flagged as an error. + * @see org.exolab.castor.xml.Unmarshaller#setIgnoreExtraElements(boolean) */ - public void setMarshalExtendedType(boolean marshalExtendedType) { - this.marshalExtendedType = marshalExtendedType; + public void setIgnoreExtraElements(boolean ignoreExtraElements) { + this.ignoreExtraElements = ignoreExtraElements; } /** - * Sets the value of {@code xsi:noNamespaceSchemaLocation} attribute. When set, the {@code - * xsi:noNamespaceSchemaLocation} attribute will be written for the root element. - * - * @see org.exolab.castor.xml.Marshaller#setNoNamespaceSchemaLocation(String) + * Set the expected root object for the unmarshaller, into which the source will be unmarshalled. + * @see org.exolab.castor.xml.Unmarshaller#setObject(Object) + * @deprecated in favor of {@link #setRootObject} */ - public void setNoNamespaceSchemaLocation(String noNamespaceSchemaLocation) { - this.noNamespaceSchemaLocation = noNamespaceSchemaLocation; + @Deprecated + public void setObject(Object root) { + this.rootObject = root; } /** - * Sets the value of {@code xsi:schemaLocation} attribute.When set, the {@code xsi:schemaLocation} attribute will be - * written for the root element. - * - * @see org.exolab.castor.xml.Marshaller#setSchemaLocation(String) + * Set the expected root object for the unmarshaller, into which the source will be unmarshalled. + * @see org.exolab.castor.xml.Unmarshaller#setObject(Object) */ - public void setSchemaLocation(String schemaLocation) { - this.schemaLocation = schemaLocation; + public void setRootObject(Object root) { + this.rootObject = root; } /** - * Sets whether this marshaller should output the {@code xsi:type} attribute for the root element. This can be useful - * when the type of the element can not be simply determined from the element name.

    The default is {@code false}, - * the {@code xsi:type} attribute for the root element won't be written. - * - * @see org.exolab.castor.xml.Marshaller#setUseXSITypeAtRoot(boolean) + * Set whether this unmarshaller should re-use objects. + * This will be only used when unmarshalling to an existing object. + *

    The default is {@code false}, which means that the objects won't be re-used. + * @see org.exolab.castor.xml.Unmarshaller#setReuseObjects(boolean) */ - public void setUseXSITypeAtRoot(boolean useXSITypeAtRoot) { - this.useXSITypeAtRoot = useXSITypeAtRoot; + public void setReuseObjects(boolean reuseObjects) { + this.reuseObjects = reuseObjects; } /** - * Sets the processing instructions that will be used by during marshalling. Keys are the processing targets and - * values - * contain the processing data. - * + * Sets whether this unmarshaller should clear collections upon the first use. + *

    The default is {@code false} which means that marshaller won't clear collections. + * @see org.exolab.castor.xml.Unmarshaller#setClearCollections(boolean) + */ + public void setClearCollections(boolean clearCollections) { + this.clearCollections = clearCollections; + } + + /** + * Set Castor-specific properties for marshalling and unmarshalling. + * Each entry key is considered the property name and each value the property value. + * @see org.exolab.castor.xml.Marshaller#setProperty(String, String) + * @see org.exolab.castor.xml.Unmarshaller#setProperty(String, String) + */ + public void setCastorProperties(Map castorProperties) { + this.castorProperties = castorProperties; + } + + /** + * Set the map containing document type definition for the marshaller. + * Each entry has system id as key and public id as value. + * @see org.exolab.castor.xml.Marshaller#setDoctype(String, String) + */ + public void setDoctypes(Map doctypes) { + this.doctypes = doctypes; + } + + /** + * Sets the processing instructions that will be used by during marshalling. + * Keys are the processing targets and values contain the processing data. * @see org.exolab.castor.xml.Marshaller#addProcessingInstruction(String, String) */ public void setProcessingInstructions(Map processingInstructions) { this.processingInstructions = processingInstructions; } + /** + * Set the namespace mappings. + * Property names are interpreted as namespace prefixes; values are namespace URIs. + * @see org.exolab.castor.xml.Marshaller#setNamespaceMapping(String, String) + */ + public void setNamespaceMappings(Map namespaceMappings) { + this.namespaceMappings = namespaceMappings; + } + /** * Set the namespace to package mappings. Property names are represents the namespaces URI, values are packages. - * * @see org.exolab.castor.xml.Marshaller#setNamespaceMapping(String, String) */ public void setNamespaceToPackageMapping(Map namespaceToPackageMapping) { @@ -352,58 +397,45 @@ public void setNamespaceToPackageMapping(Map namespaceToPackageM } /** - * Sets the expected object for the unmarshaller, into which the source will be unmarshalled. - * - * @see org.exolab.castor.xml.Unmarshaller#setObject(Object) + * Set the {@link EntityResolver} to be used during unmarshalling. + * This resolver will used to resolve system and public ids. + * @see org.exolab.castor.xml.Unmarshaller#setEntityResolver(EntityResolver) */ - public void setObject(Object root) { - this.root = root; + public void setEntityResolver(EntityResolver entityResolver) { + this.entityResolver = entityResolver; } /** - * Sets whether this unmarshaller should re-use objects. This will be only used when unmarshalling to existing - * object.

    The default is {@code false}, which means that the objects won't be re-used. - * - * @see org.exolab.castor.xml.Unmarshaller#setReuseObjects(boolean) + * Set the {@link XMLClassDescriptorResolver} to be used during unmarshalling. + * This resolver will used to resolve class descriptors. + * @see org.exolab.castor.xml.Unmarshaller#setResolver(XMLClassDescriptorResolver) */ - public void setReuseObjects(boolean reuseObjects) { - this.reuseObjects = reuseObjects; + public void setClassDescriptorResolver(XMLClassDescriptorResolver classDescriptorResolver) { + this.classDescriptorResolver = classDescriptorResolver; } /** - * Sets whether this unmarshaller should clear collections upon the first use.

    The default is {@code false}, - * which means that marshaller won't clear collections. - * - * @see org.exolab.castor.xml.Unmarshaller#setClearCollections(boolean) + * Set the Castor {@link IDResolver} to be used during unmarshalling. + * @see org.exolab.castor.xml.Unmarshaller#setIDResolver(IDResolver) */ - public void setClearCollections(boolean clearCollections) { - this.clearCollections = clearCollections; + public void setIdResolver(IDResolver idResolver) { + this.idResolver = idResolver; + } + + /** + * Set the Castor {@link ObjectFactory} to be used during unmarshalling. + * @see org.exolab.castor.xml.Unmarshaller#setObjectFactory(ObjectFactory) + */ + public void setObjectFactory(ObjectFactory objectFactory) { + this.objectFactory = objectFactory; } public void setBeanClassLoader(ClassLoader classLoader) { - this.classLoader = classLoader; + this.beanClassLoader = classLoader; } - public final void afterPropertiesSet() throws CastorMappingException, IOException { - if (logger.isInfoEnabled()) { - if (!ObjectUtils.isEmpty(this.mappingLocations)) { - logger.info( - "Configured using [" + StringUtils.arrayToCommaDelimitedString(this.mappingLocations) + "]"); - } - if (!ObjectUtils.isEmpty(this.targetClasses)) { - logger.info("Configured for target classes " + StringUtils.arrayToCommaDelimitedString(targetClasses) + - "]"); - } - if (!ObjectUtils.isEmpty(this.targetPackages)) { - logger.info( - "Configured for target packages [" + StringUtils.arrayToCommaDelimitedString(targetPackages) + - "]"); - } - if (ObjectUtils.isEmpty(this.mappingLocations) && ObjectUtils.isEmpty(this.targetClasses) && - ObjectUtils.isEmpty(this.targetPackages)) { - logger.info("Using default configuration"); - } - } + + public void afterPropertiesSet() throws CastorMappingException, IOException { try { this.xmlContext = createXMLContext(this.mappingLocations, this.targetClasses, this.targetPackages); } @@ -416,9 +448,8 @@ public final void afterPropertiesSet() throws CastorMappingException, IOExceptio } /** - * Create the Castor {@code XMLContext}. Subclasses can override this to create a custom context.

    The default - * implementation loads mapping files if defined, or the target class or packages if defined. - * + * Create the Castor {@code XMLContext}. Subclasses can override this to create a custom context. + *

    The default implementation loads mapping files if defined, or the target class or packages if defined. * @return the created resolver * @throws MappingException when the mapping file cannot be loaded * @throws IOException in case of I/O errors @@ -442,9 +473,15 @@ protected XMLContext createXMLContext(Resource[] mappingLocations, Class[] targe if (!ObjectUtils.isEmpty(targetPackages)) { context.addPackages(targetPackages); } + if (this.castorProperties != null) { + for (Map.Entry property : this.castorProperties.entrySet()) { + context.setProperty(property.getKey(), property.getValue()); + } + } return context; } + /** * Returns {@code true} for all classes, i.e. Castor supports arbitrary classes. */ @@ -462,6 +499,7 @@ protected final void marshalDomNode(Object graph, Node node) throws XmlMappingEx @Override protected final void marshalSaxHandlers(Object graph, ContentHandler contentHandler, LexicalHandler lexicalHandler) throws XmlMappingException { + Marshaller marshaller = xmlContext.createMarshaller(); marshaller.setContentHandler(contentHandler); marshal(graph, marshaller); @@ -470,6 +508,7 @@ protected final void marshalSaxHandlers(Object graph, ContentHandler contentHand @Override protected final void marshalOutputStream(Object graph, OutputStream outputStream) throws XmlMappingException, IOException { + marshalWriter(graph, new OutputStreamWriter(outputStream, encoding)); } @@ -502,42 +541,29 @@ private void marshal(Object graph, Marshaller marshaller) { /** * Template method that allows for customizing of the given Castor {@link Marshaller}. - * - *

    The default implementation invokes - *
      - *
    1. {@link Marshaller#setValidation(boolean)},
    2. - *
    3. {@link Marshaller#setSuppressNamespaces(boolean)},
    4. - *
    5. {@link Marshaller#setSuppressXSIType(boolean)},
    6. - *
    7. {@link Marshaller#setMarshalAsDocument(boolean)},
    8. - *
    9. {@link Marshaller#setRootElement(String)},
    10. - *
    11. {@link Marshaller#setMarshalExtendedType(boolean)},
    12. - *
    13. {@link Marshaller#setNoNamespaceSchemaLocation(String)},
    14. - *
    15. {@link Marshaller#setSchemaLocation(String)} and
    16. - *
    17. {@link Marshaller#setUseXSITypeAtRoot(boolean)}.
    18. - *
    - * with the property set on this marshaller. - * It also calls {@link Marshaller#setNamespaceMapping(String, String)} - * with the {@linkplain #setNamespaceMappings(java.util.Map) namespace mappings} and - * {@link Marshaller#addProcessingInstruction(String, String)} with the - * {@linkplain #setProcessingInstructions(java.util.Map) processing instructions}. */ protected void customizeMarshaller(Marshaller marshaller) { marshaller.setValidation(this.validating); - marshaller.setSuppressNamespaces(isSuppressNamespaces()); - marshaller.setSuppressXSIType(isSuppressXsiType()); - marshaller.setMarshalAsDocument(marshalAsDocument); - marshaller.setRootElement(rootElement); - marshaller.setMarshalExtendedType(marshalExtendedType); - marshaller.setNoNamespaceSchemaLocation(noNamespaceSchemaLocation); - marshaller.setSchemaLocation(schemaLocation); - marshaller.setUseXSITypeAtRoot(useXSITypeAtRoot); - if (processingInstructions != null) { - for (Map.Entry processingInstruction : processingInstructions.entrySet()) { + marshaller.setSuppressNamespaces(this.suppressNamespaces); + marshaller.setSuppressXSIType(this.suppressXsiType); + marshaller.setMarshalAsDocument(this.marshalAsDocument); + marshaller.setMarshalExtendedType(this.marshalExtendedType); + marshaller.setRootElement(this.rootElement); + marshaller.setNoNamespaceSchemaLocation(this.noNamespaceSchemaLocation); + marshaller.setSchemaLocation(this.schemaLocation); + marshaller.setUseXSITypeAtRoot(this.useXSITypeAtRoot); + if (this.doctypes != null) { + for (Map.Entry doctype : this.doctypes.entrySet()) { + marshaller.setDoctype(doctype.getKey(), doctype.getValue()); + } + } + if (this.processingInstructions != null) { + for (Map.Entry processingInstruction : this.processingInstructions.entrySet()) { marshaller.addProcessingInstruction(processingInstruction.getKey(), processingInstruction.getValue()); } } if (this.namespaceMappings != null) { - for (Map.Entry entry : namespaceMappings.entrySet()) { + for (Map.Entry entry : this.namespaceMappings.entrySet()) { marshaller.setNamespaceMapping(entry.getKey(), entry.getValue()); } } @@ -619,48 +645,45 @@ private Unmarshaller createUnmarshaller() { /** * Template method that allows for customizing of the given Castor {@link Unmarshaller}. - * - *

    The default implementation invokes - *
      - *
    1. {@link Unmarshaller#setValidation(boolean)}, - *
    2. {@link Unmarshaller#setWhitespacePreserve(boolean)}, - *
    3. {@link Unmarshaller#setIgnoreExtraAttributes(boolean)}, - *
    4. {@link Unmarshaller#setIgnoreExtraElements(boolean)}, - *
    5. {@link Unmarshaller#setClassLoader(ClassLoader)}, - *
    6. {@link Unmarshaller#setObject(Object)}, - *
    7. {@link Unmarshaller#setReuseObjects(boolean)} and - *
    8. {@link Unmarshaller#setClearCollections(boolean)} - *
    - * with the properties set on this marshaller. - * It also calls {@link Unmarshaller#addNamespaceToPackageMapping(String, String)} with the - * {@linkplain #setNamespaceMappings(java.util.Map) namespace to package mapping}. */ protected void customizeUnmarshaller(Unmarshaller unmarshaller) { unmarshaller.setValidation(this.validating); unmarshaller.setWhitespacePreserve(this.whitespacePreserve); unmarshaller.setIgnoreExtraAttributes(this.ignoreExtraAttributes); unmarshaller.setIgnoreExtraElements(this.ignoreExtraElements); - unmarshaller.setClassLoader(classLoader); - unmarshaller.setObject(root); - unmarshaller.setReuseObjects(reuseObjects); - unmarshaller.setClearCollections(clearCollections); - if (namespaceToPackageMapping != null) { - for (Map.Entry mapping : namespaceToPackageMapping.entrySet()) { + unmarshaller.setObject(this.rootObject); + unmarshaller.setReuseObjects(this.reuseObjects); + unmarshaller.setClearCollections(this.clearCollections); + if (this.namespaceToPackageMapping != null) { + for (Map.Entry mapping : this.namespaceToPackageMapping.entrySet()) { unmarshaller.addNamespaceToPackageMapping(mapping.getKey(), mapping.getValue()); } } - + if (this.entityResolver != null) { + unmarshaller.setEntityResolver(this.entityResolver); + } + if (this.classDescriptorResolver != null) { + unmarshaller.setResolver(this.classDescriptorResolver); + } + if (this.idResolver != null) { + unmarshaller.setIDResolver(this.idResolver); + } + if (this.objectFactory != null) { + unmarshaller.setObjectFactory(this.objectFactory); + } + if (this.beanClassLoader != null) { + unmarshaller.setClassLoader(this.beanClassLoader); + } } /** * Convert the given {@code XMLException} to an appropriate exception from the - * {@code org.springframework.oxm} hierarchy.

    A boolean flag is used to indicate whether this exception - * occurs - * during marshalling or unmarshalling, since Castor itself does not make this distinction in its exception hierarchy. - * + * {@code org.springframework.oxm} hierarchy. + *

    A boolean flag is used to indicate whether this exception occurs during marshalling or + * unmarshalling, since Castor itself does not make this distinction in its exception hierarchy. * @param ex Castor {@code XMLException} that occurred - * @param marshalling indicates whether the exception occurs during marshalling ({@code true}), or unmarshalling - * ({@code false}) + * @param marshalling indicates whether the exception occurs during marshalling ({@code true}), + * or unmarshalling ({@code false}) * @return the corresponding {@code XmlMappingException} */ protected XmlMappingException convertCastorException(XMLException ex, boolean marshalling) { diff --git a/spring-oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java b/spring-oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java index 956c00323339..4ffd0c495d62 100644 --- a/spring-oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java +++ b/spring-oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -155,14 +155,16 @@ public class Jaxb2Marshaller private LSResourceResolver schemaResourceResolver; - private boolean mtomEnabled = false; - private boolean lazyInit = false; + private boolean mtomEnabled = false; + private boolean supportJaxbElementClass = false; private boolean checkForXmlRootElement = true; + private Class mappedClass; + private ClassLoader beanClassLoader; private ResourceLoader resourceLoader; @@ -329,14 +331,6 @@ public void setSchemaResourceResolver(LSResourceResolver schemaResourceResolver) this.schemaResourceResolver = schemaResourceResolver; } - /** - * Specify whether MTOM support should be enabled or not. - * Default is {@code false}: marshalling using XOP/MTOM not being enabled. - */ - public void setMtomEnabled(boolean mtomEnabled) { - this.mtomEnabled = mtomEnabled; - } - /** * Set whether to lazily initialize the {@link JAXBContext} for this marshaller. * Default is {@code false} to initialize on startup; can be switched to {@code true}. @@ -346,14 +340,22 @@ public void setLazyInit(boolean lazyInit) { this.lazyInit = lazyInit; } + /** + * Specify whether MTOM support should be enabled or not. + * Default is {@code false}: marshalling using XOP/MTOM not being enabled. + */ + public void setMtomEnabled(boolean mtomEnabled) { + this.mtomEnabled = mtomEnabled; + } + /** * Specify whether the {@link #supports(Class)} returns {@code true} for the {@link JAXBElement} class. *

    Default is {@code false}, meaning that {@code supports(Class)} always returns {@code false} for - * {@code JAXBElement} classes (though {@link #supports(Type)} can return {@code true}, since it can obtain the - * type parameters of {@code JAXBElement}). + * {@code JAXBElement} classes (though {@link #supports(Type)} can return {@code true}, since it can + * obtain the type parameters of {@code JAXBElement}). *

    This property is typically enabled in combination with usage of classes like - * {@link org.springframework.web.servlet.view.xml.MarshallingView MarshallingView}, since the {@code ModelAndView} - * does not offer type parameter information at runtime. + * {@link org.springframework.web.servlet.view.xml.MarshallingView MarshallingView}, + * since the {@code ModelAndView} does not offer type parameter information at runtime. * @see #supports(Class) * @see #supports(Type) */ @@ -376,6 +378,14 @@ public void setCheckForXmlRootElement(boolean checkForXmlRootElement) { this.checkForXmlRootElement = checkForXmlRootElement; } + /** + * Specify a JAXB mapped class for partial unmarshalling. + * @see javax.xml.bind.Unmarshaller#unmarshal(javax.xml.transform.Source, Class) + */ + public void setMappedClass(Class mappedClass) { + this.mappedClass = mappedClass; + } + public void setBeanClassLoader(ClassLoader classLoader) { this.beanClassLoader = classLoader; } @@ -384,6 +394,7 @@ public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } + public final void afterPropertiesSet() throws Exception { boolean hasContextPath = StringUtils.hasLength(this.contextPath); boolean hasClassesToBeBound = !ObjectUtils.isEmpty(this.classesToBeBound); @@ -406,7 +417,10 @@ public final void afterPropertiesSet() throws Exception { } } - protected JAXBContext getJaxbContext() { + /** + * Return the JAXBContext used by this marshaller, lazily building it if necessary. + */ + public JAXBContext getJaxbContext() { if (this.jaxbContext != null) { return this.jaxbContext; } @@ -514,10 +528,8 @@ private Schema loadSchema(Resource[] resources, String schemaLanguage) throws IO public boolean supports(Class clazz) { - if (this.supportJaxbElementClass && JAXBElement.class.isAssignableFrom(clazz)) { - return true; - } - return supportsInternal(clazz, this.checkForXmlRootElement); + return ((this.supportJaxbElementClass && JAXBElement.class.isAssignableFrom(clazz)) || + supportsInternal(clazz, this.checkForXmlRootElement)); } public boolean supports(Type genericType) { @@ -707,6 +719,9 @@ public Object unmarshal(Source source, MimeContainer mimeContainer) throws XmlMa if (StaxUtils.isStaxSource(source)) { return unmarshalStaxSource(unmarshaller, source); } + else if (this.mappedClass != null) { + return unmarshaller.unmarshal(source, this.mappedClass); + } else { return unmarshaller.unmarshal(source); } @@ -716,7 +731,7 @@ public Object unmarshal(Source source, MimeContainer mimeContainer) throws XmlMa } } - private Object unmarshalStaxSource(Unmarshaller jaxbUnmarshaller, Source staxSource) throws JAXBException { + protected Object unmarshalStaxSource(Unmarshaller jaxbUnmarshaller, Source staxSource) throws JAXBException { XMLStreamReader streamReader = StaxUtils.getXMLStreamReader(staxSource); if (streamReader != null) { return jaxbUnmarshaller.unmarshal(streamReader); diff --git a/spring-oxm/src/main/java/org/springframework/oxm/xstream/XStreamMarshaller.java b/spring-oxm/src/main/java/org/springframework/oxm/xstream/XStreamMarshaller.java index b53c20a5b92a..dd36d17b8bfd 100644 --- a/spring-oxm/src/main/java/org/springframework/oxm/xstream/XStreamMarshaller.java +++ b/spring-oxm/src/main/java/org/springframework/oxm/xstream/XStreamMarshaller.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -114,7 +114,7 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin /** * Returns the XStream instance used by this marshaller. */ - public XStream getXStream() { + public final XStream getXStream() { return this.xstream; } @@ -125,7 +125,7 @@ public XStream getXStream() { * @see XStream#NO_REFERENCES */ public void setMode(int mode) { - this.getXStream().setMode(mode); + this.xstream.setMode(mode); } /** @@ -137,10 +137,10 @@ public void setMode(int mode) { public void setConverters(ConverterMatcher[] converters) { for (int i = 0; i < converters.length; i++) { if (converters[i] instanceof Converter) { - this.getXStream().registerConverter((Converter) converters[i], i); + this.xstream.registerConverter((Converter) converters[i], i); } else if (converters[i] instanceof SingleValueConverter) { - this.getXStream().registerConverter((SingleValueConverter) converters[i], i); + this.xstream.registerConverter((SingleValueConverter) converters[i], i); } else { throw new IllegalArgumentException("Invalid ConverterMatcher [" + converters[i] + "]"); @@ -155,9 +155,8 @@ else if (converters[i] instanceof SingleValueConverter) { */ public void setAliases(Map aliases) throws ClassNotFoundException { Map> classMap = toClassMap(aliases); - for (Map.Entry> entry : classMap.entrySet()) { - this.getXStream().alias(entry.getKey(), entry.getValue()); + this.xstream.alias(entry.getKey(), entry.getValue()); } } @@ -169,15 +168,13 @@ public void setAliases(Map aliases) throws ClassNotFoundException { */ public void setAliasesByType(Map aliases) throws ClassNotFoundException { Map> classMap = toClassMap(aliases); - for (Map.Entry> entry : classMap.entrySet()) { - this.getXStream().aliasType(entry.getKey(), entry.getValue()); + this.xstream.aliasType(entry.getKey(), entry.getValue()); } } private Map> toClassMap(Map map) throws ClassNotFoundException { Map> result = new LinkedHashMap>(map.size()); - for (Map.Entry entry : map.entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); @@ -198,10 +195,7 @@ else if (value instanceof String) { } /** - * Sets a field alias/type map, consiting of field names - * @param aliases - * @throws ClassNotFoundException - * @throws NoSuchFieldException + * Set a field alias/type map, consiting of field names. * @see XStream#aliasField(String, Class, String) */ public void setFieldAliases(Map aliases) throws ClassNotFoundException, NoSuchFieldException { @@ -213,8 +207,9 @@ public void setFieldAliases(Map aliases) throws ClassNotFoundExc String className = field.substring(0, idx); Class clazz = ClassUtils.forName(className, classLoader); String fieldName = field.substring(idx + 1); - this.getXStream().aliasField(alias, clazz, fieldName); - } else { + this.xstream.aliasField(alias, clazz, fieldName); + } + else { throw new IllegalArgumentException("Field name [" + field + "] does not contain '.'"); } } @@ -226,7 +221,7 @@ public void setFieldAliases(Map aliases) throws ClassNotFoundExc */ public void setUseAttributeForTypes(Class[] types) { for (Class type : types) { - this.getXStream().useAttributeFor(type); + this.xstream.useAttributeFor(type); } } @@ -242,7 +237,7 @@ public void setUseAttributeFor(Map attributes) { for (Map.Entry entry : attributes.entrySet()) { if (entry.getKey() instanceof String) { if (entry.getValue() instanceof Class) { - this.getXStream().useAttributeFor((String) entry.getKey(), (Class) entry.getValue()); + this.xstream.useAttributeFor((String) entry.getKey(), (Class) entry.getValue()); } else { throw new IllegalArgumentException( @@ -253,14 +248,14 @@ public void setUseAttributeFor(Map attributes) { else if (entry.getKey() instanceof Class) { Class key = (Class) entry.getKey(); if (entry.getValue() instanceof String) { - this.getXStream().useAttributeFor(key, (String) entry.getValue()); + this.xstream.useAttributeFor(key, (String) entry.getValue()); } else if (entry.getValue() instanceof List) { List list = (List) entry.getValue(); for (Object o : list) { if (o instanceof String) { - this.getXStream().useAttributeFor(key, (String) o); + this.xstream.useAttributeFor(key, (String) o); } } } @@ -286,7 +281,7 @@ public void setImplicitCollections(Map, String> implicitCollections) { for (Map.Entry, String> entry : implicitCollections.entrySet()) { String[] collectionFields = StringUtils.commaDelimitedListToStringArray(entry.getValue()); for (String collectionField : collectionFields) { - this.getXStream().addImplicitCollection(entry.getKey(), collectionField); + this.xstream.addImplicitCollection(entry.getKey(), collectionField); } } } @@ -300,7 +295,7 @@ public void setOmittedFields(Map, String> omittedFields) { for (Map.Entry, String> entry : omittedFields.entrySet()) { String[] fields = StringUtils.commaDelimitedListToStringArray(entry.getValue()); for (String field : fields) { - this.getXStream().omitField(entry.getKey(), field); + this.xstream.omitField(entry.getKey(), field); } } } @@ -311,7 +306,7 @@ public void setOmittedFields(Map, String> omittedFields) { */ public void setAnnotatedClass(Class annotatedClass) { Assert.notNull(annotatedClass, "'annotatedClass' must not be null"); - this.getXStream().processAnnotations(annotatedClass); + this.xstream.processAnnotations(annotatedClass); } /** @@ -320,7 +315,7 @@ public void setAnnotatedClass(Class annotatedClass) { */ public void setAnnotatedClasses(Class[] annotatedClasses) { Assert.notEmpty(annotatedClasses, "'annotatedClasses' must not be empty"); - this.getXStream().processAnnotations(annotatedClasses); + this.xstream.processAnnotations(annotatedClasses); } /** @@ -330,7 +325,7 @@ public void setAnnotatedClasses(Class[] annotatedClasses) { * @see XStream#autodetectAnnotations(boolean) */ public void setAutodetectAnnotations(boolean autodetectAnnotations) { - this.getXStream().autodetectAnnotations(autodetectAnnotations); + this.xstream.autodetectAnnotations(autodetectAnnotations); } /** @@ -363,7 +358,7 @@ public void setBeanClassLoader(ClassLoader classLoader) { public final void afterPropertiesSet() throws Exception { - customizeXStream(getXStream()); + customizeXStream(this.xstream); } /** @@ -458,7 +453,7 @@ protected void marshalWriter(Object graph, Writer writer) throws XmlMappingExcep */ private void marshal(Object graph, HierarchicalStreamWriter streamWriter) { try { - getXStream().marshal(graph, streamWriter); + this.xstream.marshal(graph, streamWriter); } catch (Exception ex) { throw convertXStreamException(ex, true); @@ -541,7 +536,7 @@ protected Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource */ private Object unmarshal(HierarchicalStreamReader streamReader) { try { - return getXStream().unmarshal(streamReader); + return this.xstream.unmarshal(streamReader); } catch (Exception ex) { throw convertXStreamException(ex, false); @@ -574,4 +569,5 @@ protected XmlMappingException convertXStreamException(Exception ex, boolean mars return new UncategorizedMappingException("Unknown XStream exception", ex); } } + } diff --git a/spring-oxm/src/main/java/org/springframework/oxm/xstream/XStreamUtils.java b/spring-oxm/src/main/java/org/springframework/oxm/xstream/XStreamUtils.java deleted file mode 100644 index 04f53b6973b5..000000000000 --- a/spring-oxm/src/main/java/org/springframework/oxm/xstream/XStreamUtils.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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.oxm.xstream; - -import com.thoughtworks.xstream.converters.ConversionException; -import com.thoughtworks.xstream.io.StreamException; -import com.thoughtworks.xstream.mapper.CannotResolveClassException; - -import org.springframework.oxm.MarshallingFailureException; -import org.springframework.oxm.UncategorizedMappingException; -import org.springframework.oxm.UnmarshallingFailureException; -import org.springframework.oxm.XmlMappingException; - -/** - * Generic utility methods for working with XStream. Mainly for internal use within the framework. - * - * @author Arjen Poutsma - * @author Juergen Hoeller - * @since 3.0 - */ -abstract class XStreamUtils { - - /** - * Convert the given XStream exception to an appropriate exception from the - * {@code org.springframework.oxm} hierarchy. - *

    A boolean flag is used to indicate whether this exception occurs during marshalling or - * unmarshalling, since XStream itself does not make this distinction in its exception hierarchy. - * @param ex XStream exception that occured - * @param marshalling indicates whether the exception occurs during marshalling ({@code true}), - * or unmarshalling ({@code false}) - * @return the corresponding {@code XmlMappingException} - */ - public static XmlMappingException convertXStreamException(Exception ex, boolean marshalling) { - if (ex instanceof StreamException || ex instanceof CannotResolveClassException || - ex instanceof ConversionException) { - if (marshalling) { - return new MarshallingFailureException("XStream marshalling exception", ex); - } - else { - return new UnmarshallingFailureException("XStream unmarshalling exception", ex); - } - } - else { - // fallback - return new UncategorizedMappingException("Unknown XStream exception", ex); - } - } - -} diff --git a/spring-oxm/src/test/java/org/springframework/oxm/castor/CastorUnmarshallerTests.java b/spring-oxm/src/test/java/org/springframework/oxm/castor/CastorUnmarshallerTests.java index 367b4aa5c15e..585640893be5 100644 --- a/spring-oxm/src/test/java/org/springframework/oxm/castor/CastorUnmarshallerTests.java +++ b/spring-oxm/src/test/java/org/springframework/oxm/castor/CastorUnmarshallerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,12 +30,9 @@ import org.springframework.oxm.Unmarshaller; import static org.hamcrest.CoreMatchers.*; - import static org.junit.Assert.*; /** - * Tests the {@link CastorMarshaller} class. - * * @author Arjen Poutsma * @author Jakub Narloch */ @@ -114,7 +111,6 @@ public void testSetBothTargetClassesAndMapping() throws IOException { @Test public void testWhitespacePreserveTrue() throws Exception { - getCastorUnmarshaller().setWhitespacePreserve(true); Object result = unmarshalFlights(); testFlights(result); @@ -122,7 +118,6 @@ public void testWhitespacePreserveTrue() throws Exception { @Test public void testWhitespacePreserveFalse() throws Exception { - getCastorUnmarshaller().setWhitespacePreserve(false); Object result = unmarshalFlights(); testFlights(result); @@ -130,7 +125,6 @@ public void testWhitespacePreserveFalse() throws Exception { @Test public void testIgnoreExtraAttributesTrue() throws Exception { - getCastorUnmarshaller().setIgnoreExtraAttributes(true); Object result = unmarshal(EXTRA_ATTRIBUTES_STRING); testFlights(result); @@ -146,7 +140,6 @@ public void testIgnoreExtraAttributesFalse() throws Exception { @Test @Ignore("Not working yet") public void testIgnoreExtraElementsTrue() throws Exception { - getCastorUnmarshaller().setIgnoreExtraElements(true); getCastorUnmarshaller().setValidating(false); Object result = unmarshal(EXTRA_ELEMENTS_STRING); @@ -161,22 +154,19 @@ public void testIgnoreExtraElementsFalse() throws Exception { } @Test - public void testObject() throws Exception { - + public void testRootObject() throws Exception { Flights flights = new Flights(); - getCastorUnmarshaller().setObject(flights); + getCastorUnmarshaller().setRootObject(flights); Object result = unmarshalFlights(); - testFlights(result); assertSame("Result Flights is different object.", flights, result); } @Test public void testClearCollectionsTrue() throws Exception { - Flights flights = new Flights(); flights.setFlight(new Flight[]{new Flight()}); - getCastorUnmarshaller().setObject(flights); + getCastorUnmarshaller().setRootObject(flights); getCastorUnmarshaller().setClearCollections(true); Object result = unmarshalFlights(); @@ -188,10 +178,9 @@ public void testClearCollectionsTrue() throws Exception { @Test @Ignore("Fails on the builder server for some reason") public void testClearCollectionsFalse() throws Exception { - Flights flights = new Flights(); flights.setFlight(new Flight[]{new Flight(), null}); - getCastorUnmarshaller().setObject(flights); + getCastorUnmarshaller().setRootObject(flights); getCastorUnmarshaller().setClearCollections(false); Object result = unmarshalFlights(); diff --git a/spring-test-mvc/src/main/java/org/springframework/test/util/JsonPathExpectationsHelper.java b/spring-test-mvc/src/main/java/org/springframework/test/util/JsonPathExpectationsHelper.java index 054e3b18c9aa..87608730ad30 100644 --- a/spring-test-mvc/src/main/java/org/springframework/test/util/JsonPathExpectationsHelper.java +++ b/spring-test-mvc/src/main/java/org/springframework/test/util/JsonPathExpectationsHelper.java @@ -17,7 +17,8 @@ package org.springframework.test.util; import static org.springframework.test.util.AssertionErrors.assertEquals; -import static org.springframework.test.util.AssertionErrors.*; +import static org.springframework.test.util.AssertionErrors.assertTrue; +import static org.springframework.test.util.AssertionErrors.fail; import static org.springframework.test.util.MatcherAssertionErrors.assertThat; import java.text.ParseException; @@ -96,6 +97,10 @@ public void assertValue(String responseContent, Object expectedValue) throws Par } actualValue = actualValueList.get(0); } + else if (actualValue != null && expectedValue != null) { + assertEquals("For JSON path " + this.expression + " type of value", + expectedValue.getClass(), actualValue.getClass()); + } assertEquals("JSON path" + this.expression, expectedValue, actualValue); } diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/request/MockMultipartHttpServletRequestBuilder.java b/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/request/MockMultipartHttpServletRequestBuilder.java index 8be3cf86ea46..78ff9d2d565c 100644 --- a/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/request/MockMultipartHttpServletRequestBuilder.java +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/request/MockMultipartHttpServletRequestBuilder.java @@ -84,15 +84,16 @@ public Object merge(Object parent) { if (parent == null) { return this; } - if (!(parent instanceof MockMultipartHttpServletRequestBuilder)) { + if (parent instanceof MockHttpServletRequestBuilder) { + super.merge(parent); + if (parent instanceof MockMultipartHttpServletRequestBuilder) { + MockMultipartHttpServletRequestBuilder parentBuilder = (MockMultipartHttpServletRequestBuilder) parent; + this.files.addAll(parentBuilder.files); + } + } + else { throw new IllegalArgumentException("Cannot merge with [" + parent.getClass().getName() + "]"); } - - super.merge(parent); - - MockMultipartHttpServletRequestBuilder parentBuilder = (MockMultipartHttpServletRequestBuilder) parent; - this.files.addAll(parentBuilder.files); - return this; } diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/setup/DefaultMockMvcBuilder.java b/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/setup/DefaultMockMvcBuilder.java index a44f43a816ce..7e8b7afd6355 100644 --- a/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/setup/DefaultMockMvcBuilder.java +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/setup/DefaultMockMvcBuilder.java @@ -42,7 +42,7 @@ * @author Rob Winch * @since 3.2 */ -public class DefaultMockMvcBuilder extends MockMvcBuilderSupport +public class DefaultMockMvcBuilder extends MockMvcBuilderSupport implements MockMvcBuilder { private final WebApplicationContext webAppContext; diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/setup/StandaloneMockMvcBuilder.java b/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/setup/StandaloneMockMvcBuilder.java index b9597c196e8b..079a42fc012a 100644 --- a/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/setup/StandaloneMockMvcBuilder.java +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/setup/StandaloneMockMvcBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -201,15 +201,22 @@ public StandaloneMockMvcBuilder setCustomReturnValueHandlers(HandlerMethodReturn return this; } - /** - * Set the HandlerExceptionResolver types to use. + * Set the HandlerExceptionResolver types to use as a list. */ public StandaloneMockMvcBuilder setHandlerExceptionResolvers(List exceptionResolvers) { this.handlerExceptionResolvers = exceptionResolvers; return this; } + /** + * Set the HandlerExceptionResolver types to use as an array. + */ + public StandaloneMockMvcBuilder setHandlerExceptionResolvers(HandlerExceptionResolver... exceptionResolvers) { + this.handlerExceptionResolvers = Arrays.asList(exceptionResolvers); + return this; + } + /** * Set up view resolution with the given {@link ViewResolver}s. * If not set, an {@link InternalResourceViewResolver} is used by default. diff --git a/spring-test-mvc/src/test/java/org/springframework/test/util/JsonPathExpectationsHelperTests.java b/spring-test-mvc/src/test/java/org/springframework/test/util/JsonPathExpectationsHelperTests.java new file mode 100644 index 000000000000..b3f42091ef4e --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/util/JsonPathExpectationsHelperTests.java @@ -0,0 +1,45 @@ +/* + * Copyright 2004-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import org.junit.Test; + + +/** + * Test fixture for {@link JsonPathExpectationsHelper}. + * + * @author Rossen Stoyanchev + */ +public class JsonPathExpectationsHelperTests { + + + @Test + public void test() throws Exception { + try { + new JsonPathExpectationsHelper("$.nr").assertValue("{ \"nr\" : 5 }", "5"); + fail("Expected exception"); + } + catch (AssertionError ex) { + assertEquals("For JSON path $.nr type of value expected: but was:", + ex.getMessage()); + } + } + +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/servlet/request/MockMultipartHttpServletRequestBuilderTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/servlet/request/MockMultipartHttpServletRequestBuilderTests.java new file mode 100644 index 000000000000..e73e3dd4c334 --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/servlet/request/MockMultipartHttpServletRequestBuilderTests.java @@ -0,0 +1,51 @@ +/* + * Copyright 2004-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.web.servlet.request; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import org.junit.Test; +import org.springframework.http.HttpMethod; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockServletContext; + + +/** + * Test fixture for {@link MockMultipartHttpServletRequestBuilder}. + * + * @author Rossen Stoyanchev + */ +public class MockMultipartHttpServletRequestBuilderTests { + + + @Test + public void test() { + MockHttpServletRequestBuilder parent = new MockHttpServletRequestBuilder(HttpMethod.GET, "/"); + parent.characterEncoding("UTF-8"); + Object result = new MockMultipartHttpServletRequestBuilder("/fileUpload").merge(parent); + + assertNotNull(result); + assertEquals(MockMultipartHttpServletRequestBuilder.class, result.getClass()); + + MockMultipartHttpServletRequestBuilder builder = (MockMultipartHttpServletRequestBuilder) result; + MockHttpServletRequest request = builder.buildRequest(new MockServletContext()); + + assertEquals("UTF-8", request.getCharacterEncoding()); + } + +} \ No newline at end of file diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/servlet/setup/Spr10277Tests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/servlet/setup/Spr10277Tests.java new file mode 100644 index 000000000000..101285c5201a --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/servlet/setup/Spr10277Tests.java @@ -0,0 +1,43 @@ +package org.springframework.test.web.servlet.setup; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.filter.CharacterEncodingFilter; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +/** + * Test for SPR-10277 (Multiple method chaining when building MockMvc). + * + * @author Wesley Hall + */ +@RunWith(SpringJUnit4ClassRunner.class) +@WebAppConfiguration +@ContextConfiguration +public class Spr10277Tests { + + @Autowired + private WebApplicationContext wac; + + @Test + public void chainMultiple() { + MockMvcBuilders + .webAppContextSetup(wac) + .addFilter(new CharacterEncodingFilter() ) + .defaultRequest(get("/").contextPath("/mywebapp")) + .build(); + } + + @Configuration + @EnableWebMvc + static class WebConfig extends WebMvcConfigurerAdapter { + } +} diff --git a/spring-test/src/main/java/org/springframework/test/context/web/ServletTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/web/ServletTestExecutionListener.java index 01302bb11008..b87c987dacdf 100644 --- a/spring-test/src/main/java/org/springframework/test/context/web/ServletTestExecutionListener.java +++ b/spring-test/src/main/java/org/springframework/test/context/web/ServletTestExecutionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -126,6 +126,7 @@ private void setUpRequestContextIfNecessary(TestContext testContext) { RequestContextHolder.setRequestAttributes(servletWebRequest); if (wac instanceof ConfigurableApplicationContext) { + @SuppressWarnings("resource") ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) wac; ConfigurableListableBeanFactory bf = configurableApplicationContext.getBeanFactory(); bf.registerResolvableDependency(MockHttpServletResponse.class, response); diff --git a/spring-test/src/main/java/org/springframework/test/jdbc/JdbcTestUtils.java b/spring-test/src/main/java/org/springframework/test/jdbc/JdbcTestUtils.java index 532a82c01fb1..383b20dff901 100644 --- a/spring-test/src/main/java/org/springframework/test/jdbc/JdbcTestUtils.java +++ b/spring-test/src/main/java/org/springframework/test/jdbc/JdbcTestUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,13 +23,13 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.EncodedResource; import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessResourceFailureException; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.SqlParameterValue; import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; import org.springframework.util.StringUtils; @@ -42,6 +42,7 @@ * @author Thomas Risberg * @author Sam Brannen * @author Juergen Hoeller + * @author Phillip Webb * @since 2.5.4 */ public class JdbcTestUtils { @@ -60,7 +61,7 @@ public class JdbcTestUtils { * @return the number of rows in the table */ public static int countRowsInTable(JdbcTemplate jdbcTemplate, String tableName) { - return jdbcTemplate.queryForInt("SELECT COUNT(0) FROM " + tableName); + return jdbcTemplate.queryForObject("SELECT COUNT(0) FROM " + tableName, Integer.class); } /** @@ -82,7 +83,7 @@ public static int countRowsInTableWhere(JdbcTemplate jdbcTemplate, String tableN if (StringUtils.hasText(whereClause)) { sql += " WHERE " + whereClause; } - return jdbcTemplate.queryForInt(sql); + return jdbcTemplate.queryForObject(sql, Integer.class); } /** @@ -103,6 +104,39 @@ public static int deleteFromTables(JdbcTemplate jdbcTemplate, String... tableNam return totalRowCount; } + /** + * Delete rows from the given table, using the provided {@code WHERE} clause. + *

    If the provided {@code WHERE} clause contains text, it will be prefixed + * with {@code " WHERE "} and then appended to the generated {@code DELETE} + * statement. For example, if the provided table name is {@code "person"} and + * the provided where clause is {@code "name = 'Bob' and age > 25"}, the + * resulting SQL statement to execute will be + * {@code "DELETE FROM person WHERE name = 'Bob' and age > 25"}. + *

    As an alternative to hard-coded values, the {@code "?"} placeholder can + * be used within the {@code WHERE} clause, binding to the given arguments. + * @param jdbcTemplate the JdbcTemplate with which to perform JDBC operations + * @param tableName the name of the table to delete rows from + * @param whereClause the {@code WHERE} clause to append to the query + * @param args arguments to bind to the query (leaving it to the PreparedStatement + * to guess the corresponding SQL type); may also contain {@link SqlParameterValue} + * objects which indicate not only the argument value but also the SQL type and + * optionally the scale. + * @return the number of rows deleted from the table + */ + public static int deleteFromTableWhere(JdbcTemplate jdbcTemplate, String tableName, + String whereClause, Object... args) { + String sql = "DELETE FROM " + tableName; + if (StringUtils.hasText(whereClause)) { + sql += " WHERE " + whereClause; + } + int rowCount = (args != null && args.length > 0 ? jdbcTemplate.update(sql, args) + : jdbcTemplate.update(sql)); + if (logger.isInfoEnabled()) { + logger.info("Deleted " + rowCount + " rows from table " + tableName); + } + return rowCount; + } + /** * Drop the specified tables. * @param jdbcTemplate the JdbcTemplate with which to perform JDBC operations diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/AbstractTransactionalAnnotatedConfigClassTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/AbstractTransactionalAnnotatedConfigClassTests.java index 54b55c8c440e..59cef2c1dbe0 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/AbstractTransactionalAnnotatedConfigClassTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/AbstractTransactionalAnnotatedConfigClassTests.java @@ -76,7 +76,7 @@ public void setDataSource(DataSource dataSource) { } private int countRowsInTable(String tableName) { - return jdbcTemplate.queryForInt("SELECT COUNT(0) FROM " + tableName); + return jdbcTemplate.queryForObject("SELECT COUNT(0) FROM " + tableName, Integer.class); } private int createPerson(String name) { diff --git a/spring-test/src/test/java/org/springframework/test/jdbc/JdbcTestUtilsTests.java b/spring-test/src/test/java/org/springframework/test/jdbc/JdbcTestUtilsTests.java index d5fd1b68e4b7..6ebdf918192b 100644 --- a/spring-test/src/test/java/org/springframework/test/jdbc/JdbcTestUtilsTests.java +++ b/spring-test/src/test/java/org/springframework/test/jdbc/JdbcTestUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,25 +16,38 @@ package org.springframework.test.jdbc; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.mockito.BDDMockito.given; import java.io.LineNumberReader; import java.util.ArrayList; import java.util.List; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.support.EncodedResource; +import org.springframework.jdbc.core.JdbcTemplate; /** * Unit tests for {@link JdbcTestUtils}. * * @author Thomas Risberg * @author Sam Brannen + * @author Phillip Webb * @since 2.5.4 */ +@RunWith(MockitoJUnitRunner.class) public class JdbcTestUtilsTests { + @Mock + private JdbcTemplate jdbcTemplate; + @Test public void containsDelimiters() { assertTrue("test with ';' is wrong", !JdbcTestUtils.containsSqlScriptDelimiters("select 1\n select ';'", ';')); @@ -104,4 +117,26 @@ public void readAndSplitScriptContainingComments() throws Exception { assertEquals("statement 4 not split correctly", statement4, statements.get(3)); } + @Test + public void testDeleteNoWhere() throws Exception { + given(jdbcTemplate.update("DELETE FROM person")).willReturn(10); + int deleted = JdbcTestUtils.deleteFromTableWhere(jdbcTemplate, "person", null); + assertThat(deleted, equalTo(10)); + } + + @Test + public void testDeleteWhere() throws Exception { + given(jdbcTemplate.update("DELETE FROM person WHERE name = 'Bob' and age > 25")).willReturn(10); + int deleted = JdbcTestUtils.deleteFromTableWhere(jdbcTemplate, "person", "name = 'Bob' and age > 25"); + assertThat(deleted, equalTo(10)); + } + + @Test + public void deleteWhereAndArguments() throws Exception { + given(jdbcTemplate.update("DELETE FROM person WHERE name = ? and age > ?", "Bob", 25)).willReturn(10); + int deleted = JdbcTestUtils.deleteFromTableWhere(jdbcTemplate, "person", "name = ? and age > ?", "Bob", 25); + assertThat(deleted, equalTo(10)); + } + + } diff --git a/spring-tx/src/main/java/org/springframework/jca/endpoint/GenericMessageEndpointFactory.java b/spring-tx/src/main/java/org/springframework/jca/endpoint/GenericMessageEndpointFactory.java index fac5eba990cd..61f8db5b4e4a 100644 --- a/spring-tx/src/main/java/org/springframework/jca/endpoint/GenericMessageEndpointFactory.java +++ b/spring-tx/src/main/java/org/springframework/jca/endpoint/GenericMessageEndpointFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -141,11 +141,11 @@ protected ClassLoader getEndpointClassLoader() { /** - * Internal exception thrown when a ResourceExeption has been encountered + * Internal exception thrown when a ResourceException has been encountered * during the endpoint invocation. *

    Will only be used if the ResourceAdapter does not invoke the * endpoint's {@code beforeDelivery} and {@code afterDelivery} - * directly, leavng it up to the concrete endpoint to apply those - + * directly, leaving it up to the concrete endpoint to apply those - * and to handle any ResourceExceptions thrown from them. */ @SuppressWarnings("serial") diff --git a/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java b/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java index 9f9b9503ddf2..bcd2c7788054 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java +++ b/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java @@ -16,12 +16,8 @@ package org.springframework.transaction.interceptor; -import java.lang.reflect.Method; -import java.util.Properties; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.InitializingBean; @@ -31,8 +27,13 @@ import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.TransactionSystemException; +import org.springframework.transaction.support.CallbackPreferringPlatformTransactionManager; +import org.springframework.transaction.support.TransactionCallback; import org.springframework.util.StringUtils; +import java.lang.reflect.Method; +import java.util.Properties; + /** * Base class for transactional aspects, such as the {@link TransactionInterceptor} * or an AspectJ aspect. @@ -231,6 +232,90 @@ public void afterPropertiesSet() { } + /** + * General delegate for around-advice-based subclasses, delegating to several other template + * methods on this class. Able to handle {@link CallbackPreferringPlatformTransactionManager} + * as well as regular {@link PlatformTransactionManager} implementations. + * @param method the Method being invoked + * @param targetClass the target class that we're invoking the method on + * @param invocation the callback to use for proceeding with the target invocation + * @return the return value of the method, if any + * @throws Throwable propagated from the target invocation + */ + protected Object invokeWithinTransaction(Method method, Class targetClass, final InvocationCallback invocation) + throws Throwable { + + // If the transaction attribute is null, the method is non-transactional. + final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass); + final PlatformTransactionManager tm = determineTransactionManager(txAttr); + final String joinpointIdentification = methodIdentification(method, targetClass); + + if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) { + // Standard transaction demarcation with getTransaction and commit/rollback calls. + TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); + Object retVal = null; + try { + // This is an around advice: Invoke the next interceptor in the chain. + // This will normally result in a target object being invoked. + retVal = invocation.proceedWithInvocation(); + } + catch (Throwable ex) { + // target invocation exception + completeTransactionAfterThrowing(txInfo, ex); + throw ex; + } + finally { + cleanupTransactionInfo(txInfo); + } + commitTransactionAfterReturning(txInfo); + return retVal; + } + + else { + // It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in. + try { + Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, + new TransactionCallback() { + public Object doInTransaction(TransactionStatus status) { + TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status); + try { + return invocation.proceedWithInvocation(); + } + catch (Throwable ex) { + if (txAttr.rollbackOn(ex)) { + // A RuntimeException: will lead to a rollback. + if (ex instanceof RuntimeException) { + throw (RuntimeException) ex; + } + else { + throw new ThrowableHolderException(ex); + } + } + else { + // A normal return value: will lead to a commit. + return new ThrowableHolder(ex); + } + } + finally { + cleanupTransactionInfo(txInfo); + } + } + }); + + // Check result: It might indicate a Throwable to rethrow. + if (result instanceof ThrowableHolder) { + throw ((ThrowableHolder) result).getThrowable(); + } + else { + return result; + } + } + catch (ThrowableHolderException ex) { + throw ex.getCause(); + } + } + } + /** * Determine the specific transaction manager to use for the given transaction. */ @@ -250,23 +335,6 @@ else if (this.transactionManagerBeanName != null) { } } - /** - * Create a transaction if necessary, based on the given method and class. - *

    Performs a default TransactionAttribute lookup for the given method. - * @param method the method about to execute - * @param targetClass the class that the method is being invoked on - * @return a TransactionInfo object, whether or not a transaction was created. - * The {@code hasTransaction()} method on TransactionInfo can be used to - * tell if there was a transaction created. - * @see #getTransactionAttributeSource() - */ - protected TransactionInfo createTransactionIfNecessary(Method method, Class targetClass) { - // If the transaction attribute is null, the method is non-transactional. - TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass); - PlatformTransactionManager tm = determineTransactionManager(txAttr); - return createTransactionIfNecessary(tm, txAttr, methodIdentification(method, targetClass)); - } - /** * Convenience method to return a String representation of this Method * for use in logging. Can be overridden in subclasses to provide a @@ -297,6 +365,26 @@ protected String methodIdentification(Method method) { return null; } + /** + * Create a transaction if necessary, based on the given method and class. + *

    Performs a default TransactionAttribute lookup for the given method. + * @param method the method about to execute + * @param targetClass the class that the method is being invoked on + * @return a TransactionInfo object, whether or not a transaction was created. + * The {@code hasTransaction()} method on TransactionInfo can be used to + * tell if there was a transaction created. + * @see #getTransactionAttributeSource() + * @deprecated in favor of + * {@link #createTransactionIfNecessary(PlatformTransactionManager, TransactionAttribute, String)} + */ + @Deprecated + protected TransactionInfo createTransactionIfNecessary(Method method, Class targetClass) { + // If the transaction attribute is null, the method is non-transactional. + TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass); + PlatformTransactionManager tm = determineTransactionManager(txAttr); + return createTransactionIfNecessary(tm, txAttr, methodIdentification(method, targetClass)); + } + /** * Create a transaction if necessary based on the given TransactionAttribute. *

    Allows callers to perform custom TransactionAttribute lookups through @@ -527,4 +615,50 @@ public String toString() { } } + + /** + * Simple callback interface for proceeding with the target invocation. + * Concrete interceptors/aspects adapt this to their invocation mechanism. + */ + protected interface InvocationCallback { + + Object proceedWithInvocation() throws Throwable; + } + + + /** + * Internal holder class for a Throwable, used as a return value + * from a TransactionCallback (to be subsequently unwrapped again). + */ + private static class ThrowableHolder { + + private final Throwable throwable; + + public ThrowableHolder(Throwable throwable) { + this.throwable = throwable; + } + + public final Throwable getThrowable() { + return this.throwable; + } + } + + + /** + * Internal holder class for a Throwable, used as a RuntimeException to be + * thrown from a TransactionCallback (and subsequently unwrapped again). + */ + @SuppressWarnings("serial") + private static class ThrowableHolderException extends RuntimeException { + + public ThrowableHolderException(Throwable throwable) { + super(throwable); + } + + @Override + public String toString() { + return getCause().toString(); + } + } + } diff --git a/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionInterceptor.java b/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionInterceptor.java index 11f72759a7f6..db46387077c3 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionInterceptor.java +++ b/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,21 +16,17 @@ package org.springframework.transaction.interceptor; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; -import java.util.Properties; - import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; - import org.springframework.aop.support.AopUtils; import org.springframework.beans.factory.BeanFactory; import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.TransactionStatus; -import org.springframework.transaction.support.CallbackPreferringPlatformTransactionManager; -import org.springframework.transaction.support.TransactionCallback; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Properties; /** * AOP Alliance MethodInterceptor for declarative transaction @@ -40,7 +36,7 @@ *

    Derives from the {@link TransactionAspectSupport} class which * contains the integration with Spring's underlying transaction API. * TransactionInterceptor simply calls the relevant superclass methods - * such as {@link #createTransactionIfNecessary} in the correct order. + * such as {@link #invokeWithinTransaction} in the correct order. * *

    TransactionInterceptors are thread-safe. * @@ -94,76 +90,12 @@ public Object invoke(final MethodInvocation invocation) throws Throwable { // as well as the method, which may be from an interface. Class targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null); - // If the transaction attribute is null, the method is non-transactional. - final TransactionAttribute txAttr = - getTransactionAttributeSource().getTransactionAttribute(invocation.getMethod(), targetClass); - final PlatformTransactionManager tm = determineTransactionManager(txAttr); - final String joinpointIdentification = methodIdentification(invocation.getMethod(), targetClass); - - if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) { - // Standard transaction demarcation with getTransaction and commit/rollback calls. - TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); - Object retVal = null; - try { - // This is an around advice: Invoke the next interceptor in the chain. - // This will normally result in a target object being invoked. - retVal = invocation.proceed(); - } - catch (Throwable ex) { - // target invocation exception - completeTransactionAfterThrowing(txInfo, ex); - throw ex; - } - finally { - cleanupTransactionInfo(txInfo); - } - commitTransactionAfterReturning(txInfo); - return retVal; - } - - else { - // It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in. - try { - Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, - new TransactionCallback() { - public Object doInTransaction(TransactionStatus status) { - TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status); - try { - return invocation.proceed(); - } - catch (Throwable ex) { - if (txAttr.rollbackOn(ex)) { - // A RuntimeException: will lead to a rollback. - if (ex instanceof RuntimeException) { - throw (RuntimeException) ex; - } - else { - throw new ThrowableHolderException(ex); - } - } - else { - // A normal return value: will lead to a commit. - return new ThrowableHolder(ex); - } - } - finally { - cleanupTransactionInfo(txInfo); - } - } - }); - - // Check result: It might indicate a Throwable to rethrow. - if (result instanceof ThrowableHolder) { - throw ((ThrowableHolder) result).getThrowable(); - } - else { - return result; - } + // Adapt to TransactionAspectSupport's invokeWithinTransaction... + return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() { + public Object proceedWithInvocation() throws Throwable { + return invocation.proceed(); } - catch (ThrowableHolderException ex) { - throw ex.getCause(); - } - } + }); } @@ -195,39 +127,4 @@ private void readObject(ObjectInputStream ois) throws IOException, ClassNotFound setBeanFactory((BeanFactory) ois.readObject()); } - - /** - * Internal holder class for a Throwable, used as a return value - * from a TransactionCallback (to be subsequently unwrapped again). - */ - private static class ThrowableHolder { - - private final Throwable throwable; - - public ThrowableHolder(Throwable throwable) { - this.throwable = throwable; - } - - public final Throwable getThrowable() { - return this.throwable; - } - } - - - /** - * Internal holder class for a Throwable, used as a RuntimeException to be - * thrown from a TransactionCallback (and subsequently unwrapped again). - */ - private static class ThrowableHolderException extends RuntimeException { - - public ThrowableHolderException(Throwable throwable) { - super(throwable); - } - - @Override - public String toString() { - return getCause().toString(); - } - } - } diff --git a/spring-web/src/main/java/org/springframework/http/HttpStatus.java b/spring-web/src/main/java/org/springframework/http/HttpStatus.java index 74dc2af2c216..225fbccd9f7b 100644 --- a/spring-web/src/main/java/org/springframework/http/HttpStatus.java +++ b/spring-web/src/main/java/org/springframework/http/HttpStatus.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -125,7 +125,9 @@ public enum HttpStatus { /** * {@code 302 Moved Temporarily}. * @see HTTP/1.0 + * @deprecated In favor of {@link #FOUND} which will be returned from {@code HttpStatus.valueOf(302)} */ + @Deprecated MOVED_TEMPORARILY(302, "Moved Temporarily"), /** * {@code 303 See Other}. diff --git a/spring-web/src/main/java/org/springframework/http/client/BufferingClientHttpRequestWrapper.java b/spring-web/src/main/java/org/springframework/http/client/BufferingClientHttpRequestWrapper.java index 813f98cfad98..bb8784442019 100644 --- a/spring-web/src/main/java/org/springframework/http/client/BufferingClientHttpRequestWrapper.java +++ b/spring-web/src/main/java/org/springframework/http/client/BufferingClientHttpRequestWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.util.Assert; -import org.springframework.util.FileCopyUtils; +import org.springframework.util.StreamUtils; /** * Simple implementation of {@link ClientHttpRequest} that wraps another request. @@ -53,8 +53,7 @@ public URI getURI() { @Override protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException { this.request.getHeaders().putAll(headers); - OutputStream body = this.request.getBody(); - FileCopyUtils.copy(bufferedOutput, body); + StreamUtils.copy(bufferedOutput, this.request.getBody()); ClientHttpResponse response = this.request.execute(); return new BufferingClientHttpResponseWrapper(response); } diff --git a/spring-web/src/main/java/org/springframework/http/client/BufferingClientHttpResponseWrapper.java b/spring-web/src/main/java/org/springframework/http/client/BufferingClientHttpResponseWrapper.java index f280790fec8e..f075b202bd52 100644 --- a/spring-web/src/main/java/org/springframework/http/client/BufferingClientHttpResponseWrapper.java +++ b/spring-web/src/main/java/org/springframework/http/client/BufferingClientHttpResponseWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.util.FileCopyUtils; +import org.springframework.util.StreamUtils; /** * Simple implementation of {@link ClientHttpResponse} that reads the request's body into memory, @@ -61,7 +62,7 @@ public HttpHeaders getHeaders() { public InputStream getBody() throws IOException { if (this.body == null) { - this.body = FileCopyUtils.copyToByteArray(this.response.getBody()); + this.body = StreamUtils.copyToByteArray(this.response.getBody()); } return new ByteArrayInputStream(this.body); } diff --git a/spring-web/src/main/java/org/springframework/http/client/InterceptingClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/InterceptingClientHttpRequest.java index 9d3976047991..a422614fe9f3 100644 --- a/spring-web/src/main/java/org/springframework/http/client/InterceptingClientHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/client/InterceptingClientHttpRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpRequest; -import org.springframework.util.FileCopyUtils; +import org.springframework.util.StreamUtils; /** * Wrapper for a {@link ClientHttpRequest} that has support for {@link ClientHttpRequestInterceptor}s. @@ -86,7 +86,7 @@ public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOExc delegate.getHeaders().putAll(request.getHeaders()); if (body.length > 0) { - FileCopyUtils.copy(body, delegate.getBody()); + StreamUtils.copy(body, delegate.getBody()); } return delegate.execute(); } diff --git a/spring-web/src/main/java/org/springframework/http/client/SimpleBufferingClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/SimpleBufferingClientHttpRequest.java index 989d9e4d2819..c2a786429644 100644 --- a/spring-web/src/main/java/org/springframework/http/client/SimpleBufferingClientHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/client/SimpleBufferingClientHttpRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,9 +39,12 @@ final class SimpleBufferingClientHttpRequest extends AbstractBufferingClientHttp private final HttpURLConnection connection; + private final boolean outputStreaming; - SimpleBufferingClientHttpRequest(HttpURLConnection connection) { + + SimpleBufferingClientHttpRequest(HttpURLConnection connection, boolean outputStreaming) { this.connection = connection; + this.outputStreaming = outputStreaming; } @@ -67,7 +70,7 @@ protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] buffere } } - if (this.connection.getDoOutput()) { + if (this.connection.getDoOutput() && this.outputStreaming) { this.connection.setFixedLengthStreamingMode(bufferedOutput.length); } this.connection.connect(); diff --git a/spring-web/src/main/java/org/springframework/http/client/SimpleClientHttpRequestFactory.java b/spring-web/src/main/java/org/springframework/http/client/SimpleClientHttpRequestFactory.java index 22d17b50f0be..3aa31d9b839d 100644 --- a/spring-web/src/main/java/org/springframework/http/client/SimpleClientHttpRequestFactory.java +++ b/spring-web/src/main/java/org/springframework/http/client/SimpleClientHttpRequestFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,6 +50,8 @@ public class SimpleClientHttpRequestFactory implements ClientHttpRequestFactory private int readTimeout = -1; + private boolean outputStreaming = true; + /** * Set the {@link Proxy} to use for this request factory. @@ -104,15 +106,31 @@ public void setReadTimeout(int readTimeout) { this.readTimeout = readTimeout; } + /** + * Set if the underlying URLConnection can be set to 'output streaming' mode. When + * output streaming is enabled, authentication and redirection cannot be handled + * automatically. If output streaming is disabled the + * {@link HttpURLConnection#setFixedLengthStreamingMode(int) + * setFixedLengthStreamingMode} and + * {@link HttpURLConnection#setChunkedStreamingMode(int) setChunkedStreamingMode} + * methods of the underlying connection will never be called. + *

    Default is {@code true}. + * @param outputStreaming if output streaming is enabled + */ + public void setOutputStreaming(boolean outputStreaming) { + this.outputStreaming = outputStreaming; + } + public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { HttpURLConnection connection = openConnection(uri.toURL(), this.proxy); prepareConnection(connection, httpMethod.name()); if (this.bufferRequestBody) { - return new SimpleBufferingClientHttpRequest(connection); + return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming); } else { - return new SimpleStreamingClientHttpRequest(connection, this.chunkSize); + return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, + this.outputStreaming); } } diff --git a/spring-web/src/main/java/org/springframework/http/client/SimpleStreamingClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/SimpleStreamingClientHttpRequest.java index a5e831102c47..f14f7e9025d0 100644 --- a/spring-web/src/main/java/org/springframework/http/client/SimpleStreamingClientHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/client/SimpleStreamingClientHttpRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,6 @@ package org.springframework.http.client; -import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.HttpURLConnection; @@ -27,6 +26,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; +import org.springframework.util.StreamUtils; /** * {@link ClientHttpRequest} implementation that uses standard J2SE facilities to execute streaming requests. @@ -44,10 +44,14 @@ final class SimpleStreamingClientHttpRequest extends AbstractClientHttpRequest { private OutputStream body; + private final boolean outputStreaming; - SimpleStreamingClientHttpRequest(HttpURLConnection connection, int chunkSize) { + + SimpleStreamingClientHttpRequest(HttpURLConnection connection, int chunkSize, + boolean outputStreaming) { this.connection = connection; this.chunkSize = chunkSize; + this.outputStreaming = outputStreaming; } public HttpMethod getMethod() { @@ -66,18 +70,20 @@ public URI getURI() { @Override protected OutputStream getBodyInternal(HttpHeaders headers) throws IOException { if (this.body == null) { - int contentLength = (int) headers.getContentLength(); - if (contentLength >= 0) { - this.connection.setFixedLengthStreamingMode(contentLength); - } - else { - this.connection.setChunkedStreamingMode(this.chunkSize); + if(this.outputStreaming) { + int contentLength = (int) headers.getContentLength(); + if (contentLength >= 0) { + this.connection.setFixedLengthStreamingMode(contentLength); + } + else { + this.connection.setChunkedStreamingMode(this.chunkSize); + } } writeHeaders(headers); this.connection.connect(); this.body = this.connection.getOutputStream(); } - return new NonClosingOutputStream(this.body); + return StreamUtils.nonClosing(this.body); } private void writeHeaders(HttpHeaders headers) { @@ -106,26 +112,4 @@ protected ClientHttpResponse executeInternal(HttpHeaders headers) throws IOExcep return new SimpleClientHttpResponse(this.connection); } - - private static class NonClosingOutputStream extends FilterOutputStream { - - private NonClosingOutputStream(OutputStream out) { - super(out); - } - - @Override - public void write(byte[] b) throws IOException { - super.write(b); - } - - @Override - public void write(byte[] b, int off, int let) throws IOException { - out.write(b, off, let); - } - - @Override - public void close() throws IOException { - } - } - } diff --git a/spring-web/src/main/java/org/springframework/http/converter/ByteArrayHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/ByteArrayHttpMessageConverter.java index f1ddc0080f79..d1b25f7c353f 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/ByteArrayHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/ByteArrayHttpMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; -import org.springframework.util.FileCopyUtils; +import org.springframework.util.StreamUtils; /** * Implementation of {@link HttpMessageConverter} that can read and write byte arrays. @@ -49,14 +49,9 @@ public boolean supports(Class clazz) { @Override public byte[] readInternal(Class clazz, HttpInputMessage inputMessage) throws IOException { long contentLength = inputMessage.getHeaders().getContentLength(); - if (contentLength >= 0) { - ByteArrayOutputStream bos = new ByteArrayOutputStream((int) contentLength); - FileCopyUtils.copy(inputMessage.getBody(), bos); - return bos.toByteArray(); - } - else { - return FileCopyUtils.copyToByteArray(inputMessage.getBody()); - } + ByteArrayOutputStream bos = new ByteArrayOutputStream(contentLength >= 0 ? (int) contentLength : StreamUtils.BUFFER_SIZE); + StreamUtils.copy(inputMessage.getBody(), bos); + return bos.toByteArray(); } @Override @@ -66,7 +61,7 @@ protected Long getContentLength(byte[] bytes, MediaType contentType) { @Override protected void writeInternal(byte[] bytes, HttpOutputMessage outputMessage) throws IOException { - FileCopyUtils.copy(bytes, outputMessage.getBody()); + StreamUtils.copy(bytes, outputMessage.getBody()); } } diff --git a/spring-web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java index f66a38bf192d..516eb92ab257 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ package org.springframework.http.converter; import java.io.IOException; -import java.io.InputStreamReader; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; @@ -37,9 +36,9 @@ import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; import org.springframework.util.Assert; -import org.springframework.util.FileCopyUtils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import org.springframework.util.StreamUtils; import org.springframework.util.StringUtils; /** @@ -170,7 +169,7 @@ public MultiValueMap read(Class form, MediaType contentType } byte[] bytes = builder.toString().getBytes(charset.name()); outputMessage.getHeaders().setContentLength(bytes.length); - FileCopyUtils.copy(bytes, outputMessage.getBody()); + StreamUtils.copy(bytes, outputMessage.getBody()); } private void writeMultipart(MultiValueMap parts, HttpOutputMessage outputMessage) diff --git a/spring-web/src/main/java/org/springframework/http/converter/ResourceHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/ResourceHttpMessageConverter.java index 9991fbcc8048..69f22a92e10a 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/ResourceHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/ResourceHttpMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import java.io.IOException; import java.io.InputStream; + import javax.activation.FileTypeMap; import javax.activation.MimetypesFileTypeMap; @@ -28,7 +29,7 @@ import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; import org.springframework.util.ClassUtils; -import org.springframework.util.FileCopyUtils; +import org.springframework.util.StreamUtils; import org.springframework.util.StringUtils; /** @@ -61,7 +62,7 @@ protected boolean supports(Class clazz) { protected Resource readInternal(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { - byte[] body = FileCopyUtils.copyToByteArray(inputMessage.getBody()); + byte[] body = StreamUtils.copyToByteArray(inputMessage.getBody()); return new ByteArrayResource(body); } @@ -84,7 +85,7 @@ protected Long getContentLength(Resource resource, MediaType contentType) throws protected void writeInternal(Resource resource, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { - FileCopyUtils.copy(resource.getInputStream(), outputMessage.getBody()); + StreamUtils.copy(resource.getInputStream(), outputMessage.getBody()); outputMessage.getBody().flush(); } diff --git a/spring-web/src/main/java/org/springframework/http/converter/StringHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/StringHttpMessageConverter.java index e995713d45f8..686ec93767b9 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/StringHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/StringHttpMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,6 @@ package org.springframework.http.converter; import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.util.ArrayList; @@ -27,7 +25,7 @@ import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; -import org.springframework.util.FileCopyUtils; +import org.springframework.util.StreamUtils; /** * Implementation of {@link HttpMessageConverter} that can read and write strings. @@ -84,7 +82,7 @@ public boolean supports(Class clazz) { @Override protected String readInternal(Class clazz, HttpInputMessage inputMessage) throws IOException { Charset charset = getContentTypeCharset(inputMessage.getHeaders().getContentType()); - return FileCopyUtils.copyToString(new InputStreamReader(inputMessage.getBody(), charset)); + return StreamUtils.copyToString(inputMessage.getBody(), charset); } @Override @@ -105,7 +103,7 @@ protected void writeInternal(String s, HttpOutputMessage outputMessage) throws I outputMessage.getHeaders().setAcceptCharset(getAcceptedCharsets()); } Charset charset = getContentTypeCharset(outputMessage.getHeaders().getContentType()); - FileCopyUtils.copy(s, new OutputStreamWriter(outputMessage.getBody(), charset)); + StreamUtils.copy(s, charset, outputMessage.getBody()); } /** 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 43032ba59811..b1395b498526 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 @@ -68,8 +68,9 @@ boolean required() default true; /** - * The default value to use as a fallback. Supplying a default value implicitly - * sets {@link #required()} to false. + * The default value to use as a fallback when the request parameter value + * is not provided or empty. Supplying a default value implicitly sets + * {@link #required()} to false. */ String defaultValue() default ValueConstants.DEFAULT_NONE; diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java b/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java index 9e35288687cf..a51547921012 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java @@ -92,6 +92,9 @@ else if (namedValueInfo.required) { } arg = handleNullValue(namedValueInfo.name, arg, paramType); } + else if ("".equals(arg) && (namedValueInfo.defaultValue != null)) { + arg = resolveDefaultValue(namedValueInfo.defaultValue); + } if (binderFactory != null) { WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name); diff --git a/spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java b/spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java index 103784e9c29b..60e67878b3f7 100644 --- a/spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java +++ b/spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.springframework.web.util; import java.io.ByteArrayOutputStream; +import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; @@ -38,6 +39,7 @@ * Extension of {@link UriComponents} for hierarchical URIs. * * @author Arjen Poutsma + * @author Phillip Webb * @since 3.1.3 * @see Hierarchical URIs */ @@ -406,7 +408,10 @@ public URI toUri() { else { String path = getPath(); if (StringUtils.hasLength(path) && path.charAt(0) != PATH_DELIMITER) { - path = PATH_DELIMITER + path; + // Only prefix the path delimiter if something exists before it + if(getScheme() != null || getUserInfo() != null || getHost() != null || getPort() != -1) { + path = PATH_DELIMITER + path; + } } return new URI(getScheme(), getUserInfo(), getHost(), getPort(), path, getQuery(), getFragment()); @@ -426,28 +431,15 @@ public boolean equals(Object obj) { return false; } HierarchicalUriComponents other = (HierarchicalUriComponents) obj; - if (ObjectUtils.nullSafeEquals(getScheme(), other.getScheme())) { - return false; - } - if (ObjectUtils.nullSafeEquals(getUserInfo(), other.getUserInfo())) { - return false; - } - if (ObjectUtils.nullSafeEquals(getHost(), other.getHost())) { - return false; - } - if (this.port != other.port) { - return false; - } - if (!this.path.equals(other.path)) { - return false; - } - if (!this.queryParams.equals(other.queryParams)) { - return false; - } - if (ObjectUtils.nullSafeEquals(getFragment(), other.getFragment())) { - return false; - } - return true; + boolean rtn = true; + rtn &= ObjectUtils.nullSafeEquals(getScheme(), other.getScheme()); + rtn &= ObjectUtils.nullSafeEquals(getUserInfo(), other.getUserInfo()); + rtn &= ObjectUtils.nullSafeEquals(getHost(), other.getHost()); + rtn &= getPort() == other.getPort(); + rtn &= this.path.equals(other.path); + rtn &= this.queryParams.equals(other.queryParams); + rtn &= ObjectUtils.nullSafeEquals(getFragment(), other.getFragment()); + return rtn; } @Override @@ -615,7 +607,7 @@ protected boolean isPchar(int c) { /** * Defines the contract for path (segments). */ - interface PathComponent { + interface PathComponent extends Serializable { String getPath(); diff --git a/spring-web/src/main/java/org/springframework/web/util/JavaScriptUtils.java b/spring-web/src/main/java/org/springframework/web/util/JavaScriptUtils.java index 0ee697f6d9a5..861b46fe55b1 100644 --- a/spring-web/src/main/java/org/springframework/web/util/JavaScriptUtils.java +++ b/spring-web/src/main/java/org/springframework/web/util/JavaScriptUtils.java @@ -80,6 +80,20 @@ else if (c == '\b') { else if (c == '\013') { filtered.append("\\v"); } + else if (c == '<') { + filtered.append("\\u003C"); + } + else if (c == '>') { + filtered.append("\\u003E"); + } + // Unicode for PS (line terminator in ECMA-262) + else if (c == '\u2028') { + filtered.append("\\u2028"); + } + // Unicode for LS (line terminator in ECMA-262) + else if (c == '\u2029') { + filtered.append("\\u2029"); + } else { filtered.append(c); } diff --git a/spring-web/src/main/java/org/springframework/web/util/Log4jWebConfigurer.java b/spring-web/src/main/java/org/springframework/web/util/Log4jWebConfigurer.java index 63211beaf5b0..8b50ac6fda12 100644 --- a/spring-web/src/main/java/org/springframework/web/util/Log4jWebConfigurer.java +++ b/spring-web/src/main/java/org/springframework/web/util/Log4jWebConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,12 +17,10 @@ package org.springframework.web.util; import java.io.FileNotFoundException; - import javax.servlet.ServletContext; import org.springframework.util.Log4jConfigurer; import org.springframework.util.ResourceUtils; -import org.springframework.util.SystemPropertyUtils; /** * Convenience class that performs custom log4j initialization for web environments, @@ -90,6 +88,7 @@ * context-param at all) without worrying. * * @author Juergen Hoeller + * @author Marten Deinum * @since 12.08.2003 * @see org.springframework.util.Log4jConfigurer * @see Log4jConfigListener @@ -122,9 +121,8 @@ public static void initLogging(ServletContext servletContext) { if (location != null) { // Perform actual log4j initialization; else rely on log4j's default initialization. try { - // Resolve system property placeholders before potentially - // resolving a real path. - location = SystemPropertyUtils.resolvePlaceholders(location); + // Resolve property placeholders before potentially resolving a real path. + location = ServletContextPropertyUtils.resolvePlaceholders(location, servletContext); // Leave a URL (e.g. "classpath:" or "file:") as-is. if (!ResourceUtils.isUrl(location)) { diff --git a/spring-web/src/main/java/org/springframework/web/util/OpaqueUriComponents.java b/spring-web/src/main/java/org/springframework/web/util/OpaqueUriComponents.java index 6e51c77dffdc..54d788d624e3 100644 --- a/spring-web/src/main/java/org/springframework/web/util/OpaqueUriComponents.java +++ b/spring-web/src/main/java/org/springframework/web/util/OpaqueUriComponents.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,7 @@ * Extension of {@link UriComponents} for opaque URIs. * * @author Arjen Poutsma + * @author Phillip Webb * @since 3.2 * @see Hierarchical vs Opaque URIs */ @@ -145,18 +146,11 @@ public boolean equals(Object obj) { } OpaqueUriComponents other = (OpaqueUriComponents) obj; - - if (ObjectUtils.nullSafeEquals(getScheme(), other.getScheme())) { - return false; - } - if (ObjectUtils.nullSafeEquals(this.ssp, other.ssp)) { - return false; - } - if (ObjectUtils.nullSafeEquals(getFragment(), other.getFragment())) { - return false; - } - - return true; + boolean rtn = true; + rtn &= ObjectUtils.nullSafeEquals(getScheme(), other.getScheme()); + rtn &= ObjectUtils.nullSafeEquals(this.ssp, other.ssp); + rtn &= ObjectUtils.nullSafeEquals(getFragment(), other.getFragment()); + return rtn; } @Override diff --git a/spring-web/src/main/java/org/springframework/web/util/ServletContextPropertyUtils.java b/spring-web/src/main/java/org/springframework/web/util/ServletContextPropertyUtils.java new file mode 100644 index 000000000000..46105f8aa03a --- /dev/null +++ b/spring-web/src/main/java/org/springframework/web/util/ServletContextPropertyUtils.java @@ -0,0 +1,113 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.web.util; + +import javax.servlet.ServletContext; + +import org.springframework.util.PropertyPlaceholderHelper; +import org.springframework.util.SystemPropertyUtils; + +/** + * Helper class for resolving placeholders in texts. Usually applied to file paths. + * + *

    A text may contain {@code ${...}} placeholders, to be resolved as servlet context + * init parameters or system properties: e.g. {@code ${user.dir}}. Default values can + * be supplied using the ":" separator between key and value. + * + * @author Juergen Hoeller + * @author Marten Deinum + * @since 3.2.2 + * @see SystemPropertyUtils + * @see ServletContext#getInitParameter(String) + */ +public abstract class ServletContextPropertyUtils { + + private static final PropertyPlaceholderHelper strictHelper = + new PropertyPlaceholderHelper(SystemPropertyUtils.PLACEHOLDER_PREFIX, + SystemPropertyUtils.PLACEHOLDER_SUFFIX, SystemPropertyUtils.VALUE_SEPARATOR, false); + + private static final PropertyPlaceholderHelper nonStrictHelper = + new PropertyPlaceholderHelper(SystemPropertyUtils.PLACEHOLDER_PREFIX, + SystemPropertyUtils.PLACEHOLDER_SUFFIX, SystemPropertyUtils.VALUE_SEPARATOR, true); + + + /** + * Resolve ${...} placeholders in the given text, replacing them with corresponding + * servlet context init parameter or system property values. + * @param text the String to resolve + * @param servletContext the servletContext to use for lookups. + * @return the resolved String + * @see SystemPropertyUtils#PLACEHOLDER_PREFIX + * @see SystemPropertyUtils#PLACEHOLDER_SUFFIX + * @see SystemPropertyUtils#resolvePlaceholders(String, boolean) + * @throws IllegalArgumentException if there is an unresolvable placeholder + */ + public static String resolvePlaceholders(String text, ServletContext servletContext) { + return resolvePlaceholders(text, servletContext, false); + } + + /** + * Resolve ${...} placeholders in the given text, replacing them with corresponding + * servlet context init parameter or system property values. Unresolvable placeholders + * with no default value are ignored and passed through unchanged if the flag is set to true. + * @param text the String to resolve + * @param servletContext the servletContext to use for lookups. + * @param ignoreUnresolvablePlaceholders flag to determine is unresolved placeholders are ignored + * @return the resolved String + * @see SystemPropertyUtils#PLACEHOLDER_PREFIX + * @see SystemPropertyUtils#PLACEHOLDER_SUFFIX + * @see SystemPropertyUtils#resolvePlaceholders(String, boolean) + * @throws IllegalArgumentException if there is an unresolvable placeholder and the flag is false + */ + public static String resolvePlaceholders(String text, ServletContext servletContext, boolean ignoreUnresolvablePlaceholders) { + PropertyPlaceholderHelper helper = (ignoreUnresolvablePlaceholders ? nonStrictHelper : strictHelper); + return helper.replacePlaceholders(text, new ServletContextPlaceholderResolver(text, servletContext)); + } + + + private static class ServletContextPlaceholderResolver implements PropertyPlaceholderHelper.PlaceholderResolver { + + private final String text; + + private final ServletContext servletContext; + + public ServletContextPlaceholderResolver(String text, ServletContext servletContext) { + this.text = text; + this.servletContext = servletContext; + } + + public String resolvePlaceholder(String placeholderName) { + try { + String propVal = this.servletContext.getInitParameter(placeholderName); + if (propVal == null) { + // Fall back to system properties. + propVal = System.getProperty(placeholderName); + if (propVal == null) { + // Fall back to searching the system environment. + propVal = System.getenv(placeholderName); + } + } + return propVal; + } + catch (Throwable ex) { + System.err.println("Could not resolve placeholder '" + placeholderName + "' in [" + + this.text + "] as ServletContext init-parameter or system property: " + ex); + return null; + } + } + } + +} diff --git a/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java b/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java index 141cd7b86e2a..30dcac0f843d 100644 --- a/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java +++ b/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ import java.net.URI; import java.util.ArrayList; -import java.util.Collection; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.regex.Matcher; @@ -29,6 +29,7 @@ import org.springframework.util.MultiValueMap; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; +import org.springframework.web.util.HierarchicalUriComponents.PathComponent; /** * Builder for {@link UriComponents}. @@ -46,6 +47,7 @@ * * @author Arjen Poutsma * @author Rossen Stoyanchev + * @author Phillip Webb * @since 3.1 * @see #newInstance() * @see #fromPath(String) @@ -53,7 +55,7 @@ */ public class UriComponentsBuilder { - private static final Pattern QUERY_PARAM_PATTERN = Pattern.compile("([^&=]+)=?([^&]+)?"); + private static final Pattern QUERY_PARAM_PATTERN = Pattern.compile("([^&=]+)(=?)([^&]+)?"); private static final String SCHEME_PATTERN = "([^:/?#]+):"; @@ -91,7 +93,7 @@ public class UriComponentsBuilder { private int port = -1; - private PathComponentBuilder pathBuilder = NULL_PATH_COMPONENT_BUILDER; + private CompositePathComponentBuilder pathBuilder = new CompositePathComponentBuilder(); private final MultiValueMap queryParams = new LinkedMultiValueMap(); @@ -334,7 +336,7 @@ public UriComponentsBuilder uri(URI uri) { this.port = uri.getPort(); } if (StringUtils.hasLength(uri.getRawPath())) { - this.pathBuilder = new FullPathComponentBuilder(uri.getRawPath()); + this.pathBuilder = new CompositePathComponentBuilder(uri.getRawPath()); } if (StringUtils.hasLength(uri.getRawQuery())) { this.queryParams.clear(); @@ -352,7 +354,7 @@ private void resetHierarchicalComponents() { this.userInfo = null; this.host = null; this.port = -1; - this.pathBuilder = NULL_PATH_COMPONENT_BUILDER; + this.pathBuilder = new CompositePathComponentBuilder(); this.queryParams.clear(); } @@ -436,12 +438,7 @@ public UriComponentsBuilder port(int port) { * @return this UriComponentsBuilder */ public UriComponentsBuilder path(String path) { - if (path != null) { - this.pathBuilder = this.pathBuilder.appendPath(path); - } - else { - this.pathBuilder = NULL_PATH_COMPONENT_BUILDER; - } + this.pathBuilder.addPath(path); resetSchemeSpecificPart(); return this; } @@ -453,22 +450,21 @@ public UriComponentsBuilder path(String path) { * @return this UriComponentsBuilder */ public UriComponentsBuilder replacePath(String path) { - this.pathBuilder = NULL_PATH_COMPONENT_BUILDER; - path(path); + this.pathBuilder = new CompositePathComponentBuilder(path); resetSchemeSpecificPart(); return this; } /** - * Appends the given path segments to the existing path of this builder. Each given path segments may contain URI - * template variables. + * Appends the given path segments to the existing path of this builder. Each given + * path segments may contain URI template variables. * * @param pathSegments the URI path segments * @return this UriComponentsBuilder */ public UriComponentsBuilder pathSegment(String... pathSegments) throws IllegalArgumentException { Assert.notNull(pathSegments, "'segments' must not be null"); - this.pathBuilder = this.pathBuilder.appendPathSegments(pathSegments); + this.pathBuilder.addPathSegments(pathSegments); resetSchemeSpecificPart(); return this; } @@ -496,8 +492,10 @@ public UriComponentsBuilder query(String query) { Matcher m = QUERY_PARAM_PATTERN.matcher(query); while (m.find()) { String name = m.group(1); - String value = m.group(2); - queryParam(name, value); + String eq = m.group(2); + String value = m.group(3); + queryParam(name, (value != null ? value : + (StringUtils.hasLength(eq) ? "" : null))); } } else { @@ -588,131 +586,122 @@ public UriComponentsBuilder fragment(String fragment) { return this; } - /** - * Represents a builder for {@link HierarchicalUriComponents.PathComponent} - */ - private interface PathComponentBuilder { - - HierarchicalUriComponents.PathComponent build(); - PathComponentBuilder appendPath(String path); - - PathComponentBuilder appendPathSegments(String... pathSegments); + private interface PathComponentBuilder { + PathComponent build(); } - /** - * Represents a builder for full string paths. - */ - private static class FullPathComponentBuilder implements PathComponentBuilder { - - private final StringBuilder path; + private static class CompositePathComponentBuilder implements PathComponentBuilder { - private FullPathComponentBuilder(String path) { - this.path = new StringBuilder(path); - } - - public HierarchicalUriComponents.PathComponent build() { - return new HierarchicalUriComponents.FullPathComponent(path.toString()); - } + private LinkedList componentBuilders = new LinkedList(); - public PathComponentBuilder appendPath(String path) { - this.path.append(path); - return this; - } - - public PathComponentBuilder appendPathSegments(String... pathSegments) { - PathComponentCompositeBuilder builder = new PathComponentCompositeBuilder(this); - builder.appendPathSegments(pathSegments); - return builder; + public CompositePathComponentBuilder() { } - } - - /** - * Represents a builder for paths segment paths. - */ - private static class PathSegmentComponentBuilder implements PathComponentBuilder { - - private final List pathSegments = new ArrayList(); - private PathSegmentComponentBuilder(String... pathSegments) { - this.pathSegments.addAll(removeEmptyPathSegments(pathSegments)); + public CompositePathComponentBuilder(String path) { + addPath(path); } - private Collection removeEmptyPathSegments(String... pathSegments) { - List result = new ArrayList(); - for (String segment : pathSegments) { - if (StringUtils.hasText(segment)) { - result.add(segment); + public void addPathSegments(String... pathSegments) { + if (!ObjectUtils.isEmpty(pathSegments)) { + PathSegmentComponentBuilder psBuilder = getLastBuilder(PathSegmentComponentBuilder.class); + FullPathComponentBuilder fpBuilder = getLastBuilder(FullPathComponentBuilder.class); + if (psBuilder == null) { + psBuilder = new PathSegmentComponentBuilder(); + this.componentBuilders.add(psBuilder); + if (fpBuilder != null) { + fpBuilder.removeTrailingSlash(); + } } + psBuilder.append(pathSegments); } - return result; } - public HierarchicalUriComponents.PathComponent build() { - return new HierarchicalUriComponents.PathSegmentComponent(pathSegments); + public void addPath(String path) { + if (StringUtils.hasText(path)) { + PathSegmentComponentBuilder psBuilder = getLastBuilder(PathSegmentComponentBuilder.class); + FullPathComponentBuilder fpBuilder = getLastBuilder(FullPathComponentBuilder.class); + if (psBuilder != null) { + path = path.startsWith("/") ? path : "/" + path; + } + if (fpBuilder == null) { + fpBuilder = new FullPathComponentBuilder(); + this.componentBuilders.add(fpBuilder); + } + fpBuilder.append(path); + } } - public PathComponentBuilder appendPath(String path) { - PathComponentCompositeBuilder builder = new PathComponentCompositeBuilder(this); - builder.appendPath(path); - return builder; + @SuppressWarnings("unchecked") + private T getLastBuilder(Class builderClass) { + if (!this.componentBuilders.isEmpty()) { + PathComponentBuilder last = this.componentBuilders.getLast(); + if (builderClass.isInstance(last)) { + return (T) last; + } + } + return null; } - public PathComponentBuilder appendPathSegments(String... pathSegments) { - this.pathSegments.addAll(removeEmptyPathSegments(pathSegments)); - return this; + public PathComponent build() { + int size = this.componentBuilders.size(); + List components = new ArrayList(size); + for (int i = 0; i < size; i++) { + PathComponent pathComponent = this.componentBuilders.get(i).build(); + if (pathComponent != null) { + components.add(pathComponent); + } + } + if (components.isEmpty()) { + return HierarchicalUriComponents.NULL_PATH_COMPONENT; + } + if (components.size() == 1) { + return components.get(0); + } + return new HierarchicalUriComponents.PathComponentComposite(components); } } - /** - * Represents a builder for a collection of PathComponents. - */ - private static class PathComponentCompositeBuilder implements PathComponentBuilder { + private static class FullPathComponentBuilder implements PathComponentBuilder { - private final List pathComponentBuilders = new ArrayList(); + private StringBuilder path = new StringBuilder(); - private PathComponentCompositeBuilder(PathComponentBuilder builder) { - pathComponentBuilders.add(builder); + public void append(String path) { + this.path.append(path); } - public HierarchicalUriComponents.PathComponent build() { - List pathComponents = - new ArrayList(pathComponentBuilders.size()); - - for (PathComponentBuilder pathComponentBuilder : pathComponentBuilders) { - pathComponents.add(pathComponentBuilder.build()); + public PathComponent build() { + if (this.path.length() == 0) { + return null; } - return new HierarchicalUriComponents.PathComponentComposite(pathComponents); - } - - public PathComponentBuilder appendPath(String path) { - this.pathComponentBuilders.add(new FullPathComponentBuilder(path)); - return this; + String path = this.path.toString().replace("//", "/"); + return new HierarchicalUriComponents.FullPathComponent(path); } - public PathComponentBuilder appendPathSegments(String... pathSegments) { - this.pathComponentBuilders.add(new PathSegmentComponentBuilder(pathSegments)); - return this; + public void removeTrailingSlash() { + int index = this.path.length() - 1; + if (this.path.charAt(index) == '/') { + this.path.deleteCharAt(index); + } } } + private static class PathSegmentComponentBuilder implements PathComponentBuilder { - /** - * Represents a builder for an empty path. - */ - private static PathComponentBuilder NULL_PATH_COMPONENT_BUILDER = new PathComponentBuilder() { - - public HierarchicalUriComponents.PathComponent build() { - return HierarchicalUriComponents.NULL_PATH_COMPONENT; - } + private List pathSegments = new LinkedList(); - public PathComponentBuilder appendPath(String path) { - return new FullPathComponentBuilder(path); + public void append(String... pathSegments) { + for (String pathSegment : pathSegments) { + if (StringUtils.hasText(pathSegment)) { + this.pathSegments.add(pathSegment); + } + } } - public PathComponentBuilder appendPathSegments(String... pathSegments) { - return new PathSegmentComponentBuilder(pathSegments); + public PathComponent build() { + return this.pathSegments.isEmpty() ? + null : new HierarchicalUriComponents.PathSegmentComponent(this.pathSegments); } - }; + } } diff --git a/spring-web/src/main/java/org/springframework/web/util/UrlPathHelper.java b/spring-web/src/main/java/org/springframework/web/util/UrlPathHelper.java index 7f98ee236961..362b9336580f 100644 --- a/spring-web/src/main/java/org/springframework/web/util/UrlPathHelper.java +++ b/spring-web/src/main/java/org/springframework/web/util/UrlPathHelper.java @@ -441,10 +441,8 @@ protected String determineEncoding(HttpServletRequest request) { * @return the updated URI string */ public String removeSemicolonContent(String requestUri) { - if (this.removeSemicolonContent) { - return removeSemicolonContentInternal(requestUri); - } - return removeJsessionid(requestUri); + return this.removeSemicolonContent ? + removeSemicolonContentInternal(requestUri) : removeJsessionid(requestUri); } private String removeSemicolonContentInternal(String requestUri) { diff --git a/spring-web/src/test/java/org/springframework/http/MockHttpOutputMessage.java b/spring-web/src/test/java/org/springframework/http/MockHttpOutputMessage.java index c8dfbfc079b4..9939aaf03493 100644 --- a/spring-web/src/test/java/org/springframework/http/MockHttpOutputMessage.java +++ b/spring-web/src/test/java/org/springframework/http/MockHttpOutputMessage.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package org.springframework.http; +import static org.mockito.Mockito.spy; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; @@ -28,7 +30,7 @@ public class MockHttpOutputMessage implements HttpOutputMessage { private final HttpHeaders headers = new HttpHeaders(); - private final ByteArrayOutputStream body = new ByteArrayOutputStream(); + private final ByteArrayOutputStream body = spy(new ByteArrayOutputStream()); @Override public HttpHeaders getHeaders() { diff --git a/spring-web/src/test/java/org/springframework/http/client/NoOutputStreamingBufferedSimpleHttpRequestFactoryTests.java b/spring-web/src/test/java/org/springframework/http/client/NoOutputStreamingBufferedSimpleHttpRequestFactoryTests.java new file mode 100644 index 000000000000..002f33c7e8ea --- /dev/null +++ b/spring-web/src/test/java/org/springframework/http/client/NoOutputStreamingBufferedSimpleHttpRequestFactoryTests.java @@ -0,0 +1,29 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.http.client; + + +public class NoOutputStreamingBufferedSimpleHttpRequestFactoryTests extends AbstractHttpRequestFactoryTestCase { + + @Override + protected ClientHttpRequestFactory createRequestFactory() { + SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + factory.setOutputStreaming(false); + return factory; + } + +} diff --git a/spring-web/src/test/java/org/springframework/http/client/NoOutputStreamingStreamingSimpleHttpRequestFactoryTests.java b/spring-web/src/test/java/org/springframework/http/client/NoOutputStreamingStreamingSimpleHttpRequestFactoryTests.java new file mode 100644 index 000000000000..68111693e184 --- /dev/null +++ b/spring-web/src/test/java/org/springframework/http/client/NoOutputStreamingStreamingSimpleHttpRequestFactoryTests.java @@ -0,0 +1,29 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.http.client; + + +public class NoOutputStreamingStreamingSimpleHttpRequestFactoryTests extends AbstractHttpRequestFactoryTestCase { + + @Override + protected ClientHttpRequestFactory createRequestFactory() { + SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + factory.setBufferRequestBody(false); + factory.setOutputStreaming(false); + return factory; + } +} diff --git a/spring-web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java index 908783e4c144..0ea0462a5045 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,6 +45,8 @@ import org.springframework.util.MultiValueMap; import static org.junit.Assert.*; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; /** * @author Arjen Poutsma @@ -157,6 +159,7 @@ public void writeMultipart() throws Exception { item = (FileItem) items.get(4); assertEquals("xml", item.getFieldName()); assertEquals("text/xml", item.getContentType()); + verify(outputMessage.getBody(), never()).close(); } private static class MockHttpOutputMessageRequestContext implements RequestContext { diff --git a/spring-web/src/test/java/org/springframework/remoting/jaxws/JaxWsSupportTests.java b/spring-web/src/test/java/org/springframework/remoting/jaxws/JaxWsSupportTests.java index 7ae30114deab..66637e04b259 100644 --- a/spring-web/src/test/java/org/springframework/remoting/jaxws/JaxWsSupportTests.java +++ b/spring-web/src/test/java/org/springframework/remoting/jaxws/JaxWsSupportTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,6 @@ package org.springframework.remoting.jaxws; -import static org.junit.Assert.*; - import java.net.MalformedURLException; import java.net.URL; @@ -28,7 +26,6 @@ import javax.xml.ws.WebServiceRef; import javax.xml.ws.soap.AddressingFeature; -import org.junit.Ignore; import org.junit.Test; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.support.GenericBeanDefinition; @@ -36,12 +33,12 @@ import org.springframework.context.annotation.AnnotationConfigUtils; import org.springframework.context.support.GenericApplicationContext; +import static org.junit.Assert.*; + /** * @author Juergen Hoeller * @since 2.5 */ -// TODO [SPR-10074] see https://gist.github.com/1150858 -@Ignore("see https://gist.github.com/1150858") public class JaxWsSupportTests { @Test diff --git a/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolverTests.java b/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolverTests.java index f8978d8b176b..bda24d4892a2 100644 --- a/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolverTests.java @@ -68,6 +68,7 @@ public class RequestParamMethodArgumentResolverTests { private MethodParameter paramMultipartFileList; private MethodParameter paramServlet30Part; private MethodParameter paramRequestPartAnnot; + private MethodParameter paramRequired; private NativeWebRequest webRequest; @@ -80,7 +81,7 @@ public void setUp() throws Exception { ParameterNameDiscoverer paramNameDiscoverer = new LocalVariableTableParameterNameDiscoverer(); Method method = getClass().getMethod("params", String.class, String[].class, Map.class, MultipartFile.class, - Map.class, String.class, MultipartFile.class, List.class, Part.class, MultipartFile.class); + Map.class, String.class, MultipartFile.class, List.class, Part.class, MultipartFile.class, String.class); paramNamedDefaultValueString = new MethodParameter(method, 0); paramNamedStringArray = new MethodParameter(method, 1); @@ -96,6 +97,7 @@ public void setUp() throws Exception { paramServlet30Part = new MethodParameter(method, 8); paramServlet30Part.initParameterNameDiscovery(paramNameDiscoverer); paramRequestPartAnnot = new MethodParameter(method, 9); + paramRequired = new MethodParameter(method, 10); request = new MockHttpServletRequest(); webRequest = new ServletWebRequest(request, new MockHttpServletResponse()); @@ -257,16 +259,41 @@ public void resolveSimpleTypeParamToNull() throws Exception { assertNull(result); } + // SPR-10180 + + @Test + public void resolveEmptyValueToDefault() throws Exception { + this.request.addParameter("name", ""); + Object result = resolver.resolveArgument(paramNamedDefaultValueString, null, webRequest, null); + assertEquals("bar", result); + } + + @Test + public void resolveEmptyValueWithoutDefault() throws Exception { + this.request.addParameter("stringNotAnnot", ""); + Object result = resolver.resolveArgument(paramStringNotAnnot, null, webRequest, null); + assertEquals("", result); + } + + @Test + public void resolveEmptyValueRequiredWithoutDefault() throws Exception { + this.request.addParameter("name", ""); + Object result = resolver.resolveArgument(paramRequired, null, webRequest, null); + assertEquals("", result); + } + + public void params(@RequestParam(value = "name", defaultValue = "bar") String param1, - @RequestParam("name") String[] param2, - @RequestParam("name") Map param3, - @RequestParam(value = "file") MultipartFile param4, - @RequestParam Map param5, - String stringNotAnnot, - MultipartFile multipartFileNotAnnot, - List multipartFileList, - Part servlet30Part, - @RequestPart MultipartFile requestPartAnnot) { + @RequestParam("name") String[] param2, + @RequestParam("name") Map param3, + @RequestParam(value = "file") MultipartFile param4, + @RequestParam Map param5, + String stringNotAnnot, + MultipartFile multipartFileNotAnnot, + List multipartFileList, + Part servlet30Part, + @RequestPart MultipartFile requestPartAnnot, + @RequestParam(value = "name") String paramRequired) { } } diff --git a/spring-web/src/test/java/org/springframework/web/util/JavaScriptUtilsTests.java b/spring-web/src/test/java/org/springframework/web/util/JavaScriptUtilsTests.java new file mode 100644 index 000000000000..182f18eb2dba --- /dev/null +++ b/spring-web/src/test/java/org/springframework/web/util/JavaScriptUtilsTests.java @@ -0,0 +1,67 @@ +/* + * Copyright 2004-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.util; + +import static org.junit.Assert.*; + +import java.io.UnsupportedEncodingException; + +import org.junit.Test; + +/** + * Test fixture for {@link JavaScriptUtils}. + * + * @author Rossen Stoyanchev + */ +public class JavaScriptUtilsTests { + + @Test + public void escape() { + StringBuilder sb = new StringBuilder(); + sb.append('"'); + sb.append("'"); + sb.append("\\"); + sb.append("/"); + sb.append("\t"); + sb.append("\n"); + sb.append("\r"); + sb.append("\f"); + sb.append("\b"); + sb.append("\013"); + assertEquals("\\\"\\'\\\\\\/\\t\\n\\n\\f\\b\\v", JavaScriptUtils.javaScriptEscape(sb.toString())); + } + + // SPR-9983 + + @Test + public void escapePsLsLineTerminators() { + StringBuilder sb = new StringBuilder(); + sb.append('\u2028'); + sb.append('\u2029'); + String result = JavaScriptUtils.javaScriptEscape(sb.toString()); + + assertEquals("\\u2028\\u2029", result); + } + + // SPR-9983 + + @Test + public void escapeLessThanGreaterThanSigns() throws UnsupportedEncodingException { + assertEquals("\\u003C\\u003E", JavaScriptUtils.javaScriptEscape("<>")); + } + +} diff --git a/spring-web/src/test/java/org/springframework/web/util/ServletContextPropertyUtilsTests.java b/spring-web/src/test/java/org/springframework/web/util/ServletContextPropertyUtilsTests.java new file mode 100644 index 000000000000..21082779a4ce --- /dev/null +++ b/spring-web/src/test/java/org/springframework/web/util/ServletContextPropertyUtilsTests.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */package org.springframework.web.util; + +import org.junit.Test; + +import org.springframework.mock.web.test.MockServletContext; + +import static org.junit.Assert.*; + +/** + * @author Marten Deinum + * @since 3.2.2 + */ +public class ServletContextPropertyUtilsTests { + + @Test + public void resolveAsServletContextInitParameter() { + MockServletContext servletContext = new MockServletContext(); + servletContext.setInitParameter("test.prop", "bar"); + String resolved = ServletContextPropertyUtils.resolvePlaceholders("${test.prop:foo}", servletContext); + assertEquals(resolved, "bar"); + } + + @Test + public void fallbackToSystemProperties() { + MockServletContext servletContext = new MockServletContext(); + System.setProperty("test.prop", "bar"); + try { + String resolved = ServletContextPropertyUtils.resolvePlaceholders("${test.prop:foo}", servletContext); + assertEquals(resolved, "bar"); + } + finally { + System.clearProperty("test.prop"); + } + } + +} diff --git a/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java b/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java index 8205ca6cdf81..25fbb98d59e5 100644 --- a/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,10 +27,12 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.*; /** * @author Arjen Poutsma + * @author Phillip Webb */ public class UriComponentsBuilderTests { @@ -55,7 +57,9 @@ public void fromPath() throws URISyntaxException { assertEquals("bar", result.getQuery()); assertEquals("baz", result.getFragment()); - URI expected = new URI("/foo?bar#baz"); + assertEquals("Invalid result URI String", "foo?bar#baz", result.toUriString()); + + URI expected = new URI("foo?bar#baz"); assertEquals("Invalid result URI", expected, result.toUri()); result = UriComponentsBuilder.fromPath("/foo").build(); @@ -312,4 +316,42 @@ public void buildAndExpandOpaque() { assertEquals("mailto:foo@example.com", result.toUriString()); } + @Test + public void queryParamWithValueWithEquals() throws Exception { + UriComponents uriComponents = UriComponentsBuilder.fromUriString("http://example.com/foo?bar=baz").build(); + assertThat(uriComponents.toUriString(), equalTo("http://example.com/foo?bar=baz")); + } + + @Test + public void queryParamWithoutValueWithEquals() throws Exception { + UriComponents uriComponents = UriComponentsBuilder.fromUriString("http://example.com/foo?bar=").build(); + assertThat(uriComponents.toUriString(), equalTo("http://example.com/foo?bar=")); + } + + @Test + public void queryParamWithoutValueWithoutEquals() throws Exception { + UriComponents uriComponents = UriComponentsBuilder.fromUriString("http://example.com/foo?bar").build(); + assertThat(uriComponents.toUriString(), equalTo("http://example.com/foo?bar")); + } + + @Test + public void relativeUrls() throws Exception { + assertThat(UriComponentsBuilder.fromUriString("http://example.com/foo/../bar").build().toString(), equalTo("http://example.com/foo/../bar")); + assertThat(UriComponentsBuilder.fromUriString("http://example.com/foo/../bar").build().toUriString(), equalTo("http://example.com/foo/../bar")); + assertThat(UriComponentsBuilder.fromUriString("http://example.com/foo/../bar").build().toUri().getPath(), equalTo("/foo/../bar")); + assertThat(UriComponentsBuilder.fromUriString("../../").build().toString(), equalTo("../../")); + assertThat(UriComponentsBuilder.fromUriString("../../").build().toUriString(), equalTo("../../")); + assertThat(UriComponentsBuilder.fromUriString("../../").build().toUri().getPath(), equalTo("../../")); + assertThat(UriComponentsBuilder.fromUriString("http://example.com").path("foo/../bar").build().toString(), equalTo("http://example.com/foo/../bar")); + assertThat(UriComponentsBuilder.fromUriString("http://example.com").path("foo/../bar").build().toUriString(), equalTo("http://example.com/foo/../bar")); + assertThat(UriComponentsBuilder.fromUriString("http://example.com").path("foo/../bar").build().toUri().getPath(), equalTo("/foo/../bar")); + } + + @Test + public void emptySegments() throws Exception { + assertThat(UriComponentsBuilder.fromUriString("http://example.com/abc/").path("/x/y/z").build().toString(), equalTo("http://example.com/abc/x/y/z")); + assertThat(UriComponentsBuilder.fromUriString("http://example.com/abc/").pathSegment("x", "y", "z").build().toString(), equalTo("http://example.com/abc/x/y/z")); + assertThat(UriComponentsBuilder.fromUriString("http://example.com/abc/").path("/x/").path("/y/z").build().toString(), equalTo("http://example.com/abc/x/y/z")); + assertThat(UriComponentsBuilder.fromUriString("http://example.com/abc/").pathSegment("x").path("y").build().toString(), equalTo("http://example.com/abc/x/y")); + } } diff --git a/spring-web/src/test/java/org/springframework/web/util/UriComponentsTests.java b/spring-web/src/test/java/org/springframework/web/util/UriComponentsTests.java index c9f8d609177c..2013e6df659e 100644 --- a/spring-web/src/test/java/org/springframework/web/util/UriComponentsTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/UriComponentsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +16,24 @@ package org.springframework.web.util; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.net.URI; import java.net.URISyntaxException; import org.junit.Test; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.not; import static org.junit.Assert.*; -/** @author Arjen Poutsma */ +/** + * @author Arjen Poutsma + * @author Phillip Webb + */ public class UriComponentsTests { @Test @@ -75,4 +85,38 @@ public void normalize() { assertEquals("http://example.com/bar", uriComponents.normalize().toString()); } + @Test + public void serializable() throws Exception { + UriComponents uriComponents = UriComponentsBuilder.fromUriString( + "http://example.com").path("/{foo}").query("bar={baz}").build(); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos); + oos.writeObject(uriComponents); + ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray())); + UriComponents readObject = (UriComponents) ois.readObject(); + assertThat(uriComponents.toString(), equalTo(readObject.toString())); + } + + @Test + public void equalsHierarchicalUriComponents() throws Exception { + UriComponents uriComponents1 = UriComponentsBuilder.fromUriString("http://example.com").path("/{foo}").query("bar={baz}").build(); + UriComponents uriComponents2 = UriComponentsBuilder.fromUriString("http://example.com").path("/{foo}").query("bar={baz}").build(); + UriComponents uriComponents3 = UriComponentsBuilder.fromUriString("http://example.com").path("/{foo}").query("bin={baz}").build(); + assertThat(uriComponents1, instanceOf(HierarchicalUriComponents.class)); + assertThat(uriComponents1, equalTo(uriComponents1)); + assertThat(uriComponents1, equalTo(uriComponents2)); + assertThat(uriComponents1, not(equalTo(uriComponents3))); + } + + @Test + public void equalsOpaqueUriComponents() throws Exception { + UriComponents uriComponents1 = UriComponentsBuilder.fromUriString("http:example.com/foo/bar").build(); + UriComponents uriComponents2 = UriComponentsBuilder.fromUriString("http:example.com/foo/bar").build(); + UriComponents uriComponents3 = UriComponentsBuilder.fromUriString("http:example.com/foo/bin").build(); + assertThat(uriComponents1, instanceOf(OpaqueUriComponents.class)); + assertThat(uriComponents1, equalTo(uriComponents1)); + assertThat(uriComponents1, equalTo(uriComponents2)); + assertThat(uriComponents1, not(equalTo(uriComponents3))); + } + } diff --git a/spring-webmvc-tiles3/src/main/java/org/springframework/web/servlet/view/tiles3/TilesView.java b/spring-webmvc-tiles3/src/main/java/org/springframework/web/servlet/view/tiles3/TilesView.java index df8cd2d4532a..67dec5912430 100644 --- a/spring-webmvc-tiles3/src/main/java/org/springframework/web/servlet/view/tiles3/TilesView.java +++ b/spring-webmvc-tiles3/src/main/java/org/springframework/web/servlet/view/tiles3/TilesView.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,9 @@ import org.apache.tiles.request.render.Renderer; import org.apache.tiles.request.servlet.ServletRequest; import org.apache.tiles.request.servlet.ServletUtil; +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.servlet.support.JstlUtils; import org.springframework.web.servlet.support.RequestContext; import org.springframework.web.servlet.support.RequestContextUtils; @@ -95,7 +98,12 @@ public void afterPropertiesSet() throws Exception { @Override public boolean checkResource(final Locale locale) throws Exception { - Request request = new ServletRequest(this.applicationContext, null, null) { + HttpServletRequest servletRequest = null; + RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); + if(requestAttributes != null && requestAttributes instanceof ServletRequestAttributes) { + servletRequest = ((ServletRequestAttributes)requestAttributes).getRequest(); + } + Request request = new ServletRequest(this.applicationContext, servletRequest, null) { @Override public Locale getRequestLocale() { return locale; 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 3dca4eda9429..a3f3c3754780 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 @@ -26,6 +26,7 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; import org.springframework.beans.BeanUtils; import org.springframework.context.ApplicationContext; @@ -866,10 +867,18 @@ protected void doOptions(HttpServletRequest request, HttpServletResponse respons return; } } - super.doOptions(request, response); - String allowedMethods = response.getHeader("Allow"); - allowedMethods += ", " + RequestMethod.PATCH.name(); - response.setHeader("Allow", allowedMethods); + + // Use response wrapper for Servlet 2.5 compatibility where + // the getHeader() method does not exist + super.doOptions(request, new HttpServletResponseWrapper(response) { + @Override + public void setHeader(String name, String value) { + if("Allow".equals(name)) { + value = (StringUtils.hasLength(value) ? value + ", " : "") + RequestMethod.PATCH.name(); + } + super.setHeader(name, value); + } + }); } /** 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 9d409a3fec5e..7fef39364c37 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 @@ -154,7 +154,6 @@ public BeanDefinition parse(Element element, ParserContext parserContext) { handlerMappingDef.setSource(source); handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); handlerMappingDef.getPropertyValues().add("order", 0); - handlerMappingDef.getPropertyValues().add("removeSemicolonContent", false); handlerMappingDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager); String methodMappingName = parserContext.getReaderContext().registerWithGeneratedName(handlerMappingDef); 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 e85f18912600..c3d1fc08833e 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 @@ -192,7 +192,6 @@ public void setApplicationContext(ApplicationContext applicationContext) throws public RequestMappingHandlerMapping requestMappingHandlerMapping() { RequestMappingHandlerMapping handlerMapping = new RequestMappingHandlerMapping(); handlerMapping.setOrder(0); - handlerMapping.setRemoveSemicolonContent(false); handlerMapping.setInterceptors(getInterceptors()); handlerMapping.setContentNegotiationManager(mvcContentNegotiationManager()); return handlerMapping; diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMapping.java index ea079c493867..b790a1e62b2c 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMapping.java @@ -127,6 +127,7 @@ public void setUrlDecode(boolean urlDecode) { /** * Set if ";" (semicolon) content should be stripped from the request URI. + *

    The default value is {@code false}. * @see org.springframework.web.util.UrlPathHelper#setRemoveSemicolonContent(boolean) */ public void setRemoveSemicolonContent(boolean removeSemicolonContent) { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java index c8f11c8175f1..ccdec77d51dd 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java @@ -37,6 +37,7 @@ import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.HandlerMethodSelector; import org.springframework.web.servlet.HandlerMapping; +import org.springframework.web.util.UrlPathHelper; /** * Abstract base class for {@link HandlerMapping} implementations that define a @@ -61,6 +62,12 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap private final MultiValueMap urlMap = new LinkedMultiValueMap(); + public AbstractHandlerMethodMapping() { + UrlPathHelper pathHelper = new UrlPathHelper(); + pathHelper.setRemoveSemicolonContent(false); + setUrlPathHelper(pathHelper); + } + /** * Whether to detect handler methods in beans in ancestor ApplicationContexts. *

    Default is "false": Only beans in the current ApplicationContext are diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/PatternsRequestCondition.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/PatternsRequestCondition.java index 357e31fe368c..53642385ca90 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/PatternsRequestCondition.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/PatternsRequestCondition.java @@ -42,9 +42,16 @@ */ public final class PatternsRequestCondition extends AbstractRequestCondition { + private static UrlPathHelper pathHelperNoSemicolonContent; + + static { + pathHelperNoSemicolonContent = new UrlPathHelper(); + pathHelperNoSemicolonContent.setRemoveSemicolonContent(true); + } + private final Set patterns; - private final UrlPathHelper urlPathHelper; + private final UrlPathHelper pathHelper; private final PathMatcher pathMatcher; @@ -105,7 +112,7 @@ private PatternsRequestCondition(Collection patterns, UrlPathHelper urlP List fileExtensions) { this.patterns = Collections.unmodifiableSet(prependLeadingSlash(patterns)); - this.urlPathHelper = urlPathHelper != null ? urlPathHelper : new UrlPathHelper(); + this.pathHelper = urlPathHelper != null ? urlPathHelper : new UrlPathHelper(); this.pathMatcher = pathMatcher != null ? pathMatcher : new AntPathMatcher(); this.useSuffixPatternMatch = useSuffixPatternMatch; this.useTrailingSlashMatch = useTrailingSlashMatch; @@ -179,7 +186,7 @@ else if (!other.patterns.isEmpty()) { else { result.add(""); } - return new PatternsRequestCondition(result, this.urlPathHelper, this.pathMatcher, this.useSuffixPatternMatch, + return new PatternsRequestCondition(result, this.pathHelper, this.pathMatcher, this.useSuffixPatternMatch, this.useTrailingSlashMatch, this.fileExtensions); } @@ -206,17 +213,24 @@ public PatternsRequestCondition getMatchingCondition(HttpServletRequest request) if (this.patterns.isEmpty()) { return this; } - String lookupPath = this.urlPathHelper.getLookupPathForRequest(request); + + String lookupPath = this.pathHelper.getLookupPathForRequest(request); + String lookupPathNoSemicolonContent = (lookupPath.indexOf(';') != -1) ? + pathHelperNoSemicolonContent.getLookupPathForRequest(request) : null; + List matches = new ArrayList(); for (String pattern : patterns) { String match = getMatchingPattern(pattern, lookupPath); + if (match == null && lookupPathNoSemicolonContent != null) { + match = getMatchingPattern(pattern, lookupPathNoSemicolonContent); + } if (match != null) { matches.add(match); } } Collections.sort(matches, this.pathMatcher.getPatternComparator(lookupPath)); return matches.isEmpty() ? null : - new PatternsRequestCondition(matches, this.urlPathHelper, this.pathMatcher, this.useSuffixPatternMatch, + new PatternsRequestCondition(matches, this.pathHelper, this.pathMatcher, this.useSuffixPatternMatch, this.useTrailingSlashMatch, this.fileExtensions); } @@ -225,7 +239,7 @@ private String getMatchingPattern(String pattern, String lookupPath) { return pattern; } if (this.useSuffixPatternMatch) { - if (useSmartSuffixPatternMatch(pattern, lookupPath)) { + if (!this.fileExtensions.isEmpty() && lookupPath.indexOf('.') != -1) { for (String extension : this.fileExtensions) { if (this.pathMatcher.match(pattern + extension, lookupPath)) { return pattern + extension; @@ -251,14 +265,6 @@ private String getMatchingPattern(String pattern, String lookupPath) { return null; } - /** - * Whether to match by known file extensions. Return "true" if file extensions - * are configured, and the lookup path has a suffix. - */ - private boolean useSmartSuffixPatternMatch(String pattern, String lookupPath) { - return (!this.fileExtensions.isEmpty() && lookupPath.indexOf('.') != -1) ; - } - /** * Compare the two conditions based on the URL patterns they contain. * Patterns are compared one at a time, from top to bottom via @@ -272,7 +278,7 @@ private boolean useSmartSuffixPatternMatch(String pattern, String lookupPath) { * the best matches on top. */ public int compareTo(PatternsRequestCondition other, HttpServletRequest request) { - String lookupPath = this.urlPathHelper.getLookupPathForRequest(request); + String lookupPath = this.pathHelper.getLookupPathForRequest(request); Comparator patternComparator = this.pathMatcher.getPatternComparator(lookupPath); Iterator iterator = patterns.iterator(); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java index cc242bf756a9..89fcb4e9e1c3 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java @@ -205,7 +205,12 @@ else if (patternAndMethodMatches.isEmpty() && !allowedMethods.isEmpty()) { if (!consumableMediaTypes.isEmpty()) { MediaType contentType = null; if (StringUtils.hasLength(request.getContentType())) { - contentType = MediaType.parseMediaType(request.getContentType()); + try { + contentType = MediaType.parseMediaType(request.getContentType()); + } + catch (IllegalArgumentException ex) { + throw new HttpMediaTypeNotSupportedException(ex.getMessage()); + } } throw new HttpMediaTypeNotSupportedException(contentType, new ArrayList(consumableMediaTypes)); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/ParameterMethodNameResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/ParameterMethodNameResolver.java index bd9405b65bb7..1a2e175fb1a0 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/ParameterMethodNameResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/ParameterMethodNameResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,14 +50,14 @@ * *

    Note that the second strategy also supports the use of submit buttons of * type 'image'. That is, an image submit button named 'reset' will normally be - * submitted by the browser as two request paramters called 'reset.x', and - * 'reset.y'. When checking for the existence of a paramter from the + * submitted by the browser as two request parameters called 'reset.x', and + * 'reset.y'. When checking for the existence of a parameter from the * {@code methodParamNames} list, to indicate that a specific method should - * be called, the code will look for request parameter in the "reset" form - * (exactly as spcified in the list), and in the "reset.x" form ('.x' appended to - * the name in the list). In this way it can handle both normal and image submit - * buttons. The actual method name resolved if there is a match will always be - * the bare form without the ".x". + * be called, the code will look for a request parameter in the "reset" form + * (exactly as specified in the list), and in the "reset.x" form ('.x' appended + * to the name in the list). In this way it can handle both normal and image + * submit buttons. The actual method name resolved, if there is a match, will + * always be the bare form without the ".x". * *

    Note: If both strategies are configured, i.e. both "paramName" * and "methodParamNames" are specified, then both will be checked for any given @@ -69,7 +69,7 @@ * *

    For both resolution strategies, the method name is of course coming from * some sort of view code, (such as a JSP page). While this may be acceptable, - * it is sometimes desireable to treat this only as a 'logical' method name, + * it is sometimes desirable to treat this only as a 'logical' method name, * with a further mapping to a 'real' method name. As such, an optional * 'logical' mapping may be specified for this purpose. * diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/AbstractCachingViewResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/AbstractCachingViewResolver.java index c0a66c521608..51b6cbc2f2fb 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/AbstractCachingViewResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/AbstractCachingViewResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,9 @@ import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import org.springframework.web.context.support.WebApplicationObjectSupport; import org.springframework.web.servlet.View; @@ -42,19 +45,38 @@ public abstract class AbstractCachingViewResolver extends WebApplicationObjectSu /** Default maximum number of entries for the view cache: 1024 */ public static final int DEFAULT_CACHE_LIMIT = 1024; + /** Dummy marker object for unresolved views in the cache Maps */ + private static final View UNRESOLVED_VIEW = new View() { + public String getContentType() { + return null; + } + public void render(Map model, HttpServletRequest request, HttpServletResponse response) { + } + }; + + /** The maximum number of entries in the cache */ private volatile int cacheLimit = DEFAULT_CACHE_LIMIT; /** Whether we should refrain from resolving views again if unresolved once */ private boolean cacheUnresolved = true; - /** Map from view key to View instance */ + /** Fast access cache for Views, returning already cached instances without a global lock */ + private final Map viewAccessCache = new ConcurrentHashMap(DEFAULT_CACHE_LIMIT); + + /** Map from view key to View instance, synchronized for View creation */ @SuppressWarnings("serial") - private final Map viewCache = + private final Map viewCreationCache = new LinkedHashMap(DEFAULT_CACHE_LIMIT, 0.75f, true) { @Override protected boolean removeEldestEntry(Map.Entry eldest) { - return size() > getCacheLimit(); + if (size() > getCacheLimit()) { + viewAccessCache.remove(eldest.getKey()); + return true; + } + else { + return false; + } } }; @@ -122,20 +144,27 @@ public View resolveViewName(String viewName, Locale locale) throws Exception { } else { Object cacheKey = getCacheKey(viewName, locale); - synchronized (this.viewCache) { - View view = this.viewCache.get(cacheKey); - if (view == null && (!this.cacheUnresolved || !this.viewCache.containsKey(cacheKey))) { - // Ask the subclass to create the View object. - view = createView(viewName, locale); - if (view != null || this.cacheUnresolved) { - this.viewCache.put(cacheKey, view); - if (logger.isTraceEnabled()) { - logger.trace("Cached view [" + cacheKey + "]"); + View view = this.viewAccessCache.get(cacheKey); + if (view == null) { + synchronized (this.viewCreationCache) { + view = this.viewCreationCache.get(cacheKey); + if (view == null) { + // Ask the subclass to create the View object. + view = createView(viewName, locale); + if (view == null && this.cacheUnresolved) { + view = UNRESOLVED_VIEW; + } + if (view != null) { + this.viewAccessCache.put(cacheKey, view); + this.viewCreationCache.put(cacheKey, view); + if (logger.isTraceEnabled()) { + logger.trace("Cached view [" + cacheKey + "]"); + } } } } - return view; } + return (view != UNRESOLVED_VIEW ? view : null); } } @@ -166,17 +195,16 @@ public void removeFromCache(String viewName, Locale locale) { else { Object cacheKey = getCacheKey(viewName, locale); Object cachedView; - synchronized (this.viewCache) { - cachedView = this.viewCache.remove(cacheKey); + synchronized (this.viewCreationCache) { + this.viewAccessCache.remove(cacheKey); + cachedView = this.viewCreationCache.remove(cacheKey); } - if (cachedView == null) { + if (logger.isDebugEnabled()) { // Some debug output might be useful... - if (logger.isDebugEnabled()) { + if (cachedView == null) { logger.debug("No cached instance for view '" + cacheKey + "' was found"); } - } - else { - if (logger.isDebugEnabled()) { + else { logger.debug("Cache for view " + cacheKey + " has been cleared"); } } @@ -189,8 +217,9 @@ public void removeFromCache(String viewName, Locale locale) { */ public void clearCache() { logger.debug("Clearing entire view cache"); - synchronized (this.viewCache) { - this.viewCache.clear(); + synchronized (this.viewCreationCache) { + this.viewAccessCache.clear(); + this.viewCreationCache.clear(); } } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/ResourceBundleViewResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/ResourceBundleViewResolver.java index 2726e0537c31..77c3a67e036a 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/ResourceBundleViewResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/ResourceBundleViewResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -160,7 +160,7 @@ protected ClassLoader getBundleClassLoader() { *

    View definitions that define their own parent or carry their own * class can still override this. Strictly speaking, the rule that a * default parent setting does not apply to a bean definition that - * carries a class is there for backwards compatiblity reasons. + * carries a class is there for backwards compatibility reasons. * It still matches the typical use case. */ public void setDefaultParentView(String defaultParentView) { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/freemarker/spring.ftl b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/freemarker/spring.ftl index 43b29af322eb..bfb41221f7c5 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/freemarker/spring.ftl +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/freemarker/spring.ftl @@ -309,8 +309,8 @@ <@bind path /> <#assign id="${status.expression?replace('[','')?replace(']','')}"> <#assign isSelected = status.value?? && status.value?string=="true"> - - checked="checked" ${attributes}/> + + checked="checked" ${attributes}/> <#-- diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJackson2JsonView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJackson2JsonView.java index 7510ab304e69..29c670a13896 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJackson2JsonView.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJackson2JsonView.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.springframework.web.servlet.view.json; import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.io.OutputStream; import java.util.Collections; import java.util.HashMap; @@ -27,7 +28,6 @@ import com.fasterxml.jackson.core.JsonEncoding; import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; @@ -48,13 +48,15 @@ * @author Jeremy Grelle * @author Arjen Poutsma * @author Rossen Stoyanchev + * @author Juergen Hoeller * @since 3.1.2 * @see org.springframework.http.converter.json.MappingJackson2HttpMessageConverter */ public class MappingJackson2JsonView extends AbstractView { /** - * Default content type. Overridable as bean property. + * Default content type: "application/json". + * Overridable through {@link #setContentType}. */ public static final String DEFAULT_CONTENT_TYPE = "application/json"; @@ -75,8 +77,9 @@ public class MappingJackson2JsonView extends AbstractView { private boolean updateContentLength = false; + /** - * Construct a new {@code JacksonJsonView}, setting the content type to {@code application/json}. + * Construct a new {@code MappingJackson2JsonView}, setting the content type to {@code application/json}. */ public MappingJackson2JsonView() { setContentType(DEFAULT_CONTENT_TYPE); @@ -85,13 +88,11 @@ public MappingJackson2JsonView() { /** - * Sets the {@code ObjectMapper} for this view. - * If not set, a default {@link ObjectMapper#ObjectMapper() ObjectMapper} is used. - *

    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. + * Set the {@code ObjectMapper} for this view. + * If not set, a default {@link ObjectMapper#ObjectMapper() ObjectMapper} will be used. + *

    Setting a custom-configured {@code ObjectMapper} is one way to take further control of + * the JSON serialization process. The other option 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"); @@ -99,14 +100,15 @@ public void setObjectMapper(ObjectMapper objectMapper) { configurePrettyPrint(); } - private void configurePrettyPrint() { - if (this.prettyPrint != null) { - this.objectMapper.configure(SerializationFeature.INDENT_OUTPUT, this.prettyPrint); - } + /** + * Return the {@code ObjectMapper} for this view. + */ + public final ObjectMapper getObjectMapper() { + return this.objectMapper; } /** - * Set the {@code JsonEncoding} for this converter. + * Set the {@code JsonEncoding} for this view. * By default, {@linkplain JsonEncoding#UTF8 UTF-8} is used. */ public void setEncoding(JsonEncoding encoding) { @@ -114,9 +116,16 @@ public void setEncoding(JsonEncoding encoding) { this.encoding = encoding; } + /** + * Return the {@code JsonEncoding} for this view. + */ + public final JsonEncoding getEncoding() { + return this.encoding; + } + /** * Indicates whether the JSON output by this view should be prefixed with "{} && ". - * Default is false. + * Default is {@code 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 @@ -127,12 +136,11 @@ public void setPrefixJson(boolean prefixJson) { } /** - * Whether to use the {@link DefaultPrettyPrinter} when writing JSON. + * Whether to use the default pretty printer when writing JSON. * This is a shortcut for setting up an {@code ObjectMapper} as follows: *

     	 * ObjectMapper mapper = new ObjectMapper();
     	 * mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
    -	 * converter.setObjectMapper(mapper);
     	 * 
    *

    The default value is {@code false}. */ @@ -141,6 +149,12 @@ public void setPrettyPrint(boolean prettyPrint) { configurePrettyPrint(); } + private void configurePrettyPrint() { + if (this.prettyPrint != null) { + this.objectMapper.configure(SerializationFeature.INDENT_OUTPUT, this.prettyPrint); + } + } + /** * Set the attribute in the model that should be rendered by this view. * When set, all other model attributes will be ignored. @@ -160,7 +174,7 @@ public void setModelKeys(Set modelKeys) { /** * Return the attributes in the model that should be rendered by this view. */ - public Set getModelKeys() { + public final Set getModelKeys() { return this.modelKeys; } @@ -179,7 +193,7 @@ public void setRenderedAttributes(Set renderedAttributes) { * @deprecated use {@link #getModelKeys()} instead */ @Deprecated - public Set getRenderedAttributes() { + public final Set getRenderedAttributes() { return this.modelKeys; } @@ -212,6 +226,7 @@ public void setUpdateContentLength(boolean updateContentLength) { this.updateContentLength = updateContentLength; } + @Override protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) { setResponseContentType(request, response); @@ -227,34 +242,21 @@ protected void prepareResponse(HttpServletRequest request, HttpServletResponse r protected void renderMergedOutputModel(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception { - OutputStream stream = this.updateContentLength ? createTemporaryOutputStream() : response.getOutputStream(); - + OutputStream stream = (this.updateContentLength ? createTemporaryOutputStream() : response.getOutputStream()); Object value = filterModel(model); - JsonGenerator generator = this.objectMapper.getJsonFactory().createJsonGenerator(stream, this.encoding); - - // A workaround for JsonGenerators not applying serialization features - // https://github.com/FasterXML/jackson-databind/issues/12 - if (this.objectMapper.isEnabled(SerializationFeature.INDENT_OUTPUT)) { - generator.useDefaultPrettyPrinter(); - } - - if (this.prefixJson) { - generator.writeRaw("{} && "); - } - this.objectMapper.writeValue(generator, value); - + writeContent(stream, value, this.prefixJson); if (this.updateContentLength) { writeToResponse(response, (ByteArrayOutputStream) stream); } } /** - * Filters out undesired attributes from the given model. + * Filter out undesired attributes from the given model. * The return value can be either another {@link Map} or a single value object. *

    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 + * @return the value to be rendered */ protected Object filterModel(Map model) { Map result = new HashMap(model.size()); @@ -267,4 +269,27 @@ protected Object filterModel(Map model) { return (this.extractValueFromSingleKeyModel && result.size() == 1 ? result.values().iterator().next() : result); } + /** + * Write the actual JSON content to the stream. + * @param stream the output stream to use + * @param value the value to be rendered, as returned from {@link #filterModel} + * @param prefixJson whether the JSON output by this view should be prefixed + * with "{} && " (as indicated through {@link #setPrefixJson}) + * @throws IOException if writing failed + */ + protected void writeContent(OutputStream stream, Object value, boolean prefixJson) throws IOException { + JsonGenerator generator = this.objectMapper.getJsonFactory().createJsonGenerator(stream, this.encoding); + + // A workaround for JsonGenerators not applying serialization features + // https://github.com/FasterXML/jackson-databind/issues/12 + if (this.objectMapper.isEnabled(SerializationFeature.INDENT_OUTPUT)) { + generator.useDefaultPrettyPrinter(); + } + + if (prefixJson) { + generator.writeRaw("{} && "); + } + this.objectMapper.writeValue(generator, value); + } + } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJacksonJsonView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJacksonJsonView.java index 5b97da49a82a..ef0ad59d9952 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJacksonJsonView.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJacksonJsonView.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,12 +17,12 @@ package org.springframework.web.servlet.view.json; import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.io.OutputStream; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; - import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -30,15 +30,13 @@ import org.codehaus.jackson.JsonGenerator; import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.map.SerializationConfig; -import org.codehaus.jackson.map.SerializerFactory; + import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.validation.BindingResult; import org.springframework.web.servlet.View; import org.springframework.web.servlet.view.AbstractView; -import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; - /** * Spring MVC {@link View} that renders JSON content by serializing the model for the current request * using Jackson's {@link ObjectMapper}. @@ -50,13 +48,15 @@ * @author Jeremy Grelle * @author Arjen Poutsma * @author Rossen Stoyanchev + * @author Juergen Hoeller * @since 3.0 * @see org.springframework.http.converter.json.MappingJacksonHttpMessageConverter */ public class MappingJacksonJsonView extends AbstractView { /** - * Default content type. Overridable as bean property. + * Default content type: "application/json". + * Overridable through {@link #setContentType}. */ public static final String DEFAULT_CONTENT_TYPE = "application/json"; @@ -77,8 +77,9 @@ public class MappingJacksonJsonView extends AbstractView { private boolean updateContentLength = false; + /** - * Construct a new {@code JacksonJsonView}, setting the content type to {@code application/json}. + * Construct a new {@code MappingJacksonJsonView}, setting the content type to {@code application/json}. */ public MappingJacksonJsonView() { setContentType(DEFAULT_CONTENT_TYPE); @@ -87,13 +88,11 @@ public MappingJacksonJsonView() { /** - * Sets the {@code ObjectMapper} for this view. - * If not set, a default {@link ObjectMapper#ObjectMapper() ObjectMapper} is used. - *

    Setting a custom-configured {@code ObjectMapper} is one way to take further control - * of the JSON serialization process. For example, an extended {@link 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. + * Set the {@code ObjectMapper} for this view. + * If not set, a default {@link ObjectMapper#ObjectMapper() ObjectMapper} will be used. + *

    Setting a custom-configured {@code ObjectMapper} is one way to take further control of + * the JSON serialization process. The other option 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"); @@ -101,14 +100,15 @@ public void setObjectMapper(ObjectMapper objectMapper) { configurePrettyPrint(); } - private void configurePrettyPrint() { - if (this.prettyPrint != null) { - this.objectMapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, this.prettyPrint); - } + /** + * Return the {@code ObjectMapper} for this view. + */ + public final ObjectMapper getObjectMapper() { + return this.objectMapper; } /** - * Set the {@code JsonEncoding} for this converter. + * Set the {@code JsonEncoding} for this view. * By default, {@linkplain JsonEncoding#UTF8 UTF-8} is used. */ public void setEncoding(JsonEncoding encoding) { @@ -116,9 +116,16 @@ public void setEncoding(JsonEncoding encoding) { this.encoding = encoding; } + /** + * Return the {@code JsonEncoding} for this view. + */ + public final JsonEncoding getEncoding() { + return this.encoding; + } + /** * Indicates whether the JSON output by this view should be prefixed with "{} && ". - * Default is false. + * Default is {@code 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 @@ -129,12 +136,11 @@ public void setPrefixJson(boolean prefixJson) { } /** - * Whether to use the {@link DefaultPrettyPrinter} when writing JSON. + * Whether to use the default pretty printer 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);
     	 * 
    *

    The default value is {@code false}. */ @@ -143,6 +149,12 @@ public void setPrettyPrint(boolean prettyPrint) { configurePrettyPrint(); } + private void configurePrettyPrint() { + if (this.prettyPrint != null) { + this.objectMapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, this.prettyPrint); + } + } + /** * Set the attribute in the model that should be rendered by this view. * When set, all other model attributes will be ignored. @@ -162,7 +174,7 @@ public void setModelKeys(Set modelKeys) { /** * Return the attributes in the model that should be rendered by this view. */ - public Set getModelKeys() { + public final Set getModelKeys() { return this.modelKeys; } @@ -181,7 +193,7 @@ public void setRenderedAttributes(Set renderedAttributes) { * @deprecated use {@link #getModelKeys()} instead */ @Deprecated - public Set getRenderedAttributes() { + public final Set getRenderedAttributes() { return this.modelKeys; } @@ -230,34 +242,21 @@ protected void prepareResponse(HttpServletRequest request, HttpServletResponse r protected void renderMergedOutputModel(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception { - OutputStream stream = this.updateContentLength ? createTemporaryOutputStream() : response.getOutputStream(); - + OutputStream stream = (this.updateContentLength ? createTemporaryOutputStream() : response.getOutputStream()); Object value = filterModel(model); - JsonGenerator generator = this.objectMapper.getJsonFactory().createJsonGenerator(stream, this.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)) { - generator.useDefaultPrettyPrinter(); - } - - if (this.prefixJson) { - generator.writeRaw("{} && "); - } - this.objectMapper.writeValue(generator, value); - + writeContent(stream, value, this.prefixJson); if (this.updateContentLength) { writeToResponse(response, (ByteArrayOutputStream) stream); } } /** - * Filters out undesired attributes from the given model. + * Filter out undesired attributes from the given model. * The return value can be either another {@link Map} or a single value object. *

    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 + * @return the value to be rendered */ protected Object filterModel(Map model) { Map result = new HashMap(model.size()); @@ -270,4 +269,27 @@ protected Object filterModel(Map model) { return (this.extractValueFromSingleKeyModel && result.size() == 1 ? result.values().iterator().next() : result); } + /** + * Write the actual JSON content to the stream. + * @param stream the output stream to use + * @param value the value to be rendered, as returned from {@link #filterModel} + * @param prefixJson whether the JSON output by this view should be prefixed + * with "{} && " (as indicated through {@link #setPrefixJson}) + * @throws IOException if writing failed + */ + protected void writeContent(OutputStream stream, Object value, boolean prefixJson) throws IOException { + JsonGenerator generator = this.objectMapper.getJsonFactory().createJsonGenerator(stream, this.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)) { + generator.useDefaultPrettyPrinter(); + } + + if (prefixJson) { + generator.writeRaw("{} && "); + } + this.objectMapper.writeValue(generator, value); + } + } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/DispatcherServletTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/DispatcherServletTests.java index b73e69ae6f28..667fb643b4b5 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/DispatcherServletTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/DispatcherServletTests.java @@ -61,6 +61,8 @@ import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; /** * @author Rod Johnson @@ -857,9 +859,10 @@ protected ConfigurableWebEnvironment createEnvironment() { public void testAllowedOptionsIncludesPatchMethod() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(getServletContext(), "OPTIONS", "/foo"); - MockHttpServletResponse response = new MockHttpServletResponse(); + MockHttpServletResponse response = spy(new MockHttpServletResponse()); DispatcherServlet servlet = new DispatcherServlet(); servlet.service(request, response); + verify(response, never()).getHeader(anyString()); // SPR-10341 assertThat(response.getHeader("Allow"), equalTo("GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH")); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/condition/PatternsRequestConditionTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/condition/PatternsRequestConditionTests.java index 9c38b7c39c49..72f6639713f4 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/condition/PatternsRequestConditionTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/condition/PatternsRequestConditionTests.java @@ -27,6 +27,7 @@ import org.junit.Test; import org.springframework.mock.web.test.MockHttpServletRequest; +import org.springframework.web.util.UrlPathHelper; /** * @author Rossen Stoyanchev @@ -185,6 +186,16 @@ public void matchPatternContainsExtension() { assertNull(match); } + @Test + public void matchIgnorePathParams() { + UrlPathHelper pathHelper = new UrlPathHelper(); + pathHelper.setRemoveSemicolonContent(false); + PatternsRequestCondition condition = new PatternsRequestCondition(new String[] {"/foo/bar"}, pathHelper, null, true, true); + PatternsRequestCondition match = condition.getMatchingCondition(new MockHttpServletRequest("GET", "/foo;q=1/bar;s=1")); + + assertNotNull(match); + } + @Test public void compareEqualPatterns() { PatternsRequestCondition c1 = new PatternsRequestCondition("/foo*"); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMappingTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMappingTests.java index 2db8412a827f..099197c07dea 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMappingTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMappingTests.java @@ -90,7 +90,6 @@ public void setUp() throws Exception { this.handlerMapping = new TestRequestMappingInfoHandlerMapping(); this.handlerMapping.registerHandler(testController); - this.handlerMapping.setRemoveSemicolonContent(false); } @Test @@ -181,6 +180,19 @@ private void testMediaTypeNotSupported(String url) throws Exception { } } + @Test + public void testMediaTypeNotValue() throws Exception { + try { + MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/person/1"); + request.setContentType("bogus"); + this.handlerMapping.getHandler(request); + fail("HttpMediaTypeNotSupportedException expected"); + } + catch (HttpMediaTypeNotSupportedException ex) { + assertEquals("Invalid media type \"bogus\": does not contain '/'", ex.getMessage()); + } + } + @Test public void mediaTypeNotAccepted() throws Exception { testMediaTypeNotAccepted("/persons"); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/freemarker/FreeMarkerMacroTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/freemarker/FreeMarkerMacroTests.java index 17a4c4f50c49..9e4f320ef684 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/freemarker/FreeMarkerMacroTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/freemarker/FreeMarkerMacroTests.java @@ -107,7 +107,7 @@ protected void processTemplate(Template template, SimpleHash fmModel, HttpServle fv.setApplicationContext(wac); fv.setExposeSpringMacroHelpers(true); - Map model = new HashMap(); + Map model = new HashMap(); model.put("tb", new TestBean("juergen", 99)); fv.render(model, request, response); } @@ -126,7 +126,7 @@ protected void processTemplate(Template template, SimpleHash model, HttpServletR fv.setApplicationContext(wac); fv.setExposeSpringMacroHelpers(true); - Map model = new HashMap(); + Map model = new HashMap(); model.put(FreeMarkerView.SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE, helperTool); try { @@ -265,6 +265,13 @@ public void testForm17() throws Exception { assertEquals("", getMacroOutput("FORM17")); } + @Test + public void testForm18() throws Exception { + String output = getMacroOutput("FORM18"); + assertTrue("Wrong output: " + output, output.startsWith("")); + assertTrue("Wrong output: " + output, output.contains("")); + } + private String getMacroOutput(String name) throws Exception { String macro = fetchMacro(name); @@ -274,30 +281,32 @@ private String getMacroOutput(String name) throws Exception { FileCopyUtils.copy("<#import \"spring.ftl\" as spring />\n" + macro, new FileWriter(resource.getPath())); DummyMacroRequestContext rc = new DummyMacroRequestContext(request); - Map msgMap = new HashMap(); + Map msgMap = new HashMap(); msgMap.put("hello", "Howdy"); msgMap.put("world", "Mundo"); rc.setMessageMap(msgMap); - Map themeMsgMap = new HashMap(); + Map themeMsgMap = new HashMap(); themeMsgMap.put("hello", "Howdy!"); themeMsgMap.put("world", "Mundo!"); rc.setThemeMessageMap(themeMsgMap); rc.setContextPath("/springtest"); - TestBean tb = new TestBean("Darren", 99); - tb.setSpouse(new TestBean("Fred")); - tb.setJedi(true); - request.setAttribute("command", tb); + TestBean darren = new TestBean("Darren", 99); + TestBean fred = new TestBean("Fred"); + fred.setJedi(true); + darren.setSpouse(fred); + darren.setJedi(true); + request.setAttribute("command", darren); - HashMap names = new HashMap(); + Map names = new HashMap(); names.put("Darren", "Darren Davison"); names.put("John", "John Doe"); names.put("Fred", "Fred Bloggs"); names.put("Rob&Harrop", "Rob Harrop"); Configuration config = fc.getConfiguration(); - Map model = new HashMap(); - model.put("command", tb); + Map model = new HashMap(); + model.put("command", darren); model.put("springMacroRequestContext", rc); model.put("msgArgs", new Object[] { "World" }); model.put("nameOptionMap", names); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/freemarker/test.ftl b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/freemarker/test.ftl index 8440b4f945f4..b1e015618618 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/freemarker/test.ftl +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/freemarker/test.ftl @@ -89,3 +89,6 @@ FORM16 FORM17 <@spring.formInput "command.spouses[0].name", ""/> + +FORM18 +<@spring.formCheckbox "command.spouses[0].jedi" /> diff --git a/src/dist/changelog.txt b/src/dist/changelog.txt index 7cd00264a9df..1e6da7b4efe0 100644 --- a/src/dist/changelog.txt +++ b/src/dist/changelog.txt @@ -3,23 +3,44 @@ SPRING FRAMEWORK CHANGELOG http://www.springsource.org -Changes in version 3.2.2 (2013-03-07) +Changes in version 3.2.2 (2013-03-11) -------------------------------------- * official support for Hibernate 4.2 (SPR-10255) +* fixed missing inter-dependencies in module POMs (SPR-10218) +* marked spring-web module as 'distributable' in order for session replication to work on Tomcat (SPR-10219) +* DefaultListableBeanFactory caches target type per bean definition and allows for specifying it in advance (SPR-10335) * ConfigurationClassPostProcessor consistently uses ClassLoader, not loading core JDK annotations via ASM (SPR-10249) +* ConfigurationClassPostProcessor detects covariant return type mismatch, avoiding infinite recursion (SPR-10261) * ConfigurationClassPostProcessor allows for overriding of scoped-proxy bean definitions (SPR-10265) * "depends-on" attribute on lang namespace element actually respected at runtime now (SPR-8625) * allow for ordering of mixed AspectJ before/after advices (SPR-9438) * added "maximumAutoGrowSize" property to SpelParserConfiguration (SPR-10229) +* fixed regression in SpringValidatorAdapter's retrieval of invalid values (SPR-10243) +* support 'unless' expression for cache veto (SPR-8871) +* @Async's qualifier works for target class annotations behind a JDK proxy as well (SPR-10274) * @Scheduled provides String variants of fixedDelay, fixedRate, initialDelay for placeholder support (SPR-8067) +* refined CronSequenceGenerator's rounding up of seconds to address second-specific cron expressions (SPR-9459) +* @Transactional in AspectJ mode works with CallbackPreferringPlatformTransactionManager (WebSphere) as well (SPR-9268) +* LazyConnectionDataSourceProxy catches setReadOnly exception analogous to DataSourceUtils (SPR-10312) * SQLErrorCodeSQLExceptionTranslator tries to find SQLException with actual error code among causes (SPR-10260) +* added "createTemporaryLob" flag to DefaultLobHandler, using JDBC 4.0's createBlob/Clob mechanism (SPR-10339) +* deprecated OracleLobHandler in favor of DefaultLobHandler for the Oracle 10g driver and higher (SPR-10339) +* deprecated (NamedParameter)JdbcTemplate's queryForInt/Long operations in favor of queryForObject (SPR-10257) +* added useful query variants without parameters to NamedParameterJdbcTemplate, for convenience in DAOs (SPR-10256) +* "packagesToScan" feature for Hibernate 3 and Hibernate 4 detects annotated packages as well (SPR-7748, SPR-10288) +* HibernateTransactionManager for Hibernate 4 supports "entityInterceptor(BeanName)" property (SPR-10301) +* DefaultJdoDialect supports the JDO 2.2+ isolation level feature out of the box (SPR-10323) * DefaultMessageListenerContainer invokes specified ExceptionListener for recovery exceptions as well (SPR-10230) * DefaultMessageListenerContainer logs recovery failures at error level and exposes "isRecovering()" method (SPR-10230) +* added "mappedClass" property to Jaxb2Marshaller, introducing support for partial unmarshalling (SPR-10282) +* added "entityResolver", "classDescriptorResolver", "doctypes" and further properties to CastorMarshaller (SPR-8470) +* deprecated CastorMarshaller's "object" property in favor of "rootObject" (SPR-8470) * MediaType throws dedicated InvalidMediaTypeException instead of generic IllegalArgumentException (SPR-10226) -* marked spring-web module as 'distributable' in order for session replication to work on Tomcat (SPR-10219) +* AbstractCachingViewResolver does not use global lock for accessing existing View instances anymore (SPR-3145) +* MappingJackson(2)JsonView allows subclasses to access the ObjectMapper and to override content writing (SPR-7619) +* Log4jWebConfigurer supports resolving placeholders against ServletContext init-parameters as well (SPR-10284) * consistent use of LinkedHashMaps and independent getAttributeNames Enumeration in Servlet/Portlet mocks (SPR-10224) -* support 'unless' expression for cache veto (SPR-8871) Changes in version 3.2.1 (2013-01-24) diff --git a/src/eclipse/org.eclipse.jdt.ui.prefs b/src/eclipse/org.eclipse.jdt.ui.prefs index 4aa12d7db504..7631dce007a0 100644 --- a/src/eclipse/org.eclipse.jdt.ui.prefs +++ b/src/eclipse/org.eclipse.jdt.ui.prefs @@ -55,8 +55,8 @@ eclipse.preferences.version=1 formatter_profile=_Spring formatter_settings_version=12 org.eclipse.jdt.ui.ignorelowercasenames=true -org.eclipse.jdt.ui.importorder=java;javax;org;com; +org.eclipse.jdt.ui.importorder=java;javax;org;com;\#; org.eclipse.jdt.ui.javadoc=true org.eclipse.jdt.ui.ondemandthreshold=9999 -org.eclipse.jdt.ui.staticondemandthreshold=9999 +org.eclipse.jdt.ui.staticondemandthreshold=1 org.eclipse.jdt.ui.text.custom_code_templates= diff --git a/src/reference/docbook/aop-api.xml b/src/reference/docbook/aop-api.xml index 4099fbc940e7..a616d7b30d15 100644 --- a/src/reference/docbook/aop-api.xml +++ b/src/reference/docbook/aop-api.xml @@ -1405,32 +1405,32 @@ assertEquals("Added two advisors",

    - Using the "autoproxy" facility + Using the "auto-proxy" facility So far we've considered explicit creation of AOP proxies using a ProxyFactoryBean or similar factory bean. - Spring also allows us to use "autoproxy" bean definitions, which can + Spring also allows us to use "auto-proxy" bean definitions, which can automatically proxy selected bean definitions. This is built on Spring "bean post processor" infrastructure, which enables modification of any bean definition as the container loads. In this model, you set up some special bean definitions in your XML bean definition file to configure the auto proxy infrastructure. This - allows you just to declare the targets eligible for autoproxying: you + allows you just to declare the targets eligible for auto-proxying: you don't need to use ProxyFactoryBean. There are two ways to do this: - Using an autoproxy creator that refers to specific beans in the + Using an auto-proxy creator that refers to specific beans in the current context. - A special case of autoproxy creation that deserves to be - considered separately; autoproxy creation driven by source-level + A special case of auto-proxy creation that deserves to be + considered separately; auto-proxy creation driven by source-level metadata attributes. @@ -1439,7 +1439,7 @@ assertEquals("Added two advisors", Autoproxy bean definitions The org.springframework.aop.framework.autoproxy - package provides the following standard autoproxy creators. + package provides the following standard auto-proxy creators.
    BeanNameAutoProxyCreator @@ -1484,7 +1484,7 @@ assertEquals("Added two advisors", A more general and extremely powerful auto proxy creator is DefaultAdvisorAutoProxyCreator. This will automagically apply eligible advisors in the current context, without - the need to include specific bean names in the autoproxy advisor's + the need to include specific bean names in the auto-proxy advisor's bean definition. It offers the same merit of consistent configuration and avoidance of duplication as BeanNameAutoProxyCreator. @@ -1561,7 +1561,7 @@ assertEquals("Added two advisors", AbstractAdvisorAutoProxyCreator This is the superclass of DefaultAdvisorAutoProxyCreator. You - can create your own autoproxy creators by subclassing this class, in + can create your own auto-proxy creators by subclassing this class, in the unlikely event that advisor definitions offer insufficient customization to the behavior of the framework DefaultAdvisorAutoProxyCreator. @@ -1571,17 +1571,17 @@ assertEquals("Added two advisors",
    Using metadata-driven auto-proxying - A particularly important type of autoproxying is driven by + A particularly important type of auto-proxying is driven by metadata. This produces a similar programming model to .NET - ServicedComponents. Instead of using XML deployment - descriptors as in EJB, configuration for transaction management and + ServicedComponents. Instead of defining metadata + in XML descriptors, configuration for transaction management and other enterprise services is held in source-level attributes. In this case, you use the DefaultAdvisorAutoProxyCreator, in combination with Advisors that understand metadata attributes. The metadata specifics are held in the pointcut part of the candidate advisors, rather than in the - autoproxy creation class itself. + auto-proxy creation class itself. This is really a special case of the DefaultAdvisorAutoProxyCreator, but deserves @@ -1589,8 +1589,8 @@ assertEquals("Added two advisors", contained in the advisors, not the AOP framework itself.) The /attributes directory of the JPetStore - sample application shows the use of attribute-driven autoproxying. In - this case, there's no need to use the + sample application shows the use of attribute-driven auto-proxying. + In this case, there's no need to use the TransactionProxyFactoryBean. Simply defining transactional attributes on business objects is sufficient, because of the use of metadata-aware pointcuts. The bean definitions include the @@ -1669,7 +1669,7 @@ assertEquals("Added two advisors", to that of .NET ServicedComponents. - This mechanism is extensible. It's possible to do autoproxying + This mechanism is extensible. It's possible to do auto-proxying based on custom attributes. You need to: @@ -1866,8 +1866,8 @@ System.out.println("Max pool size is " + conf.getMaxSize()); if resources are cached. - Simpler pooling is available using autoproxying. It's possible to - set the TargetSources used by any autoproxy creator. + Simpler pooling is available using auto-proxying. It's possible to + set the TargetSources used by any auto-proxy creator.
    diff --git a/src/reference/docbook/aop.xml b/src/reference/docbook/aop.xml index 7078e1535aa7..fafebe612761 100644 --- a/src/reference/docbook/aop.xml +++ b/src/reference/docbook/aop.xml @@ -2847,8 +2847,8 @@ public class Account { When used as a marker interface in this way, Spring will configure new instances of the annotated type (Account in - this case) using a prototype-scoped bean definition with the same name - as the fully-qualified type name + this case) using a bean definition (typically prototype-scoped) with the + same name as the fully-qualified type name (com.xyz.myapp.domain.Account). Since the default name for a bean is the fully-qualified name of its type, a convenient way to declare the prototype definition is simply to omit the @@ -2875,7 +2875,7 @@ public class Account { new Account instances. You can also use autowiring to avoid having to specify a - prototype-scoped bean definition at all. To have Spring apply autowiring + dedicated bean definition at all. To have Spring apply autowiring use the 'autowire' property of the @Configurable annotation: specify either @Configurable(autowire=Autowire.BY_TYPE) or @@ -2969,8 +2969,8 @@ public class AppConfig { Instances of @Configurable objects created before the aspect has been configured will - result in a warning being issued to the log and no configuration of the - object taking place. An example might be a bean in the Spring + result in a message being issued to the debug log and no configuration + of the object taking place. An example might be a bean in the Spring configuration that creates domain objects when it is initialized by Spring. In this case you can use the "depends-on" bean attribute to manually specify that the bean depends on the configuration diff --git a/src/reference/docbook/beans-dependencies.xml b/src/reference/docbook/beans-dependencies.xml index 0723be50fc26..7c0984129028 100644 --- a/src/reference/docbook/beans-dependencies.xml +++ b/src/reference/docbook/beans-dependencies.xml @@ -727,8 +727,7 @@ public class ExampleBean { container ignores these values. It also ignores the scope flag. Inner beans are always anonymous and they are - always scoped as prototypes. It is + always created with the outer bean. It is not possible to inject inner beans into collaborating beans other than into the enclosing bean.
    diff --git a/src/reference/docbook/beans-extension-points.xml b/src/reference/docbook/beans-extension-points.xml index 551496d7beaf..d2a0aa80c343 100644 --- a/src/reference/docbook/beans-extension-points.xml +++ b/src/reference/docbook/beans-extension-points.xml @@ -65,8 +65,7 @@ />. - The - org.springframework.beans.factory.config.BeanPostProcessor + The org.springframework.beans.factory.config.BeanPostProcessor interface consists of exactly two callback methods. When such a class is registered as a post-processor with the container, for each bean instance that is created by the container, the post-processor gets a callback from @@ -93,8 +92,7 @@ Programmatically registering <interfacename>BeanPostProcessors </interfacename> - - While the recommended approach for BeanPostProcessor + While the recommended approach for BeanPostProcessor registration is through ApplicationContext auto-detection (as described above), it is also possible to register them programmatically @@ -108,8 +106,7 @@ registration that dictates the order of execution. Note also that BeanPostProcessors registered programmatically are always processed before those registered through - auto-detection, regardless of any explicit ordering. - + auto-detection, regardless of any explicit ordering. @@ -135,6 +132,14 @@ Bean foo is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying). + + Note that if you have beans wired into your BeanPostProcessor + using autowiring or @Resource (which may fall back to autowiring), + Spring might access unexpected beans when searching for type-matching dependency candidates, + and therefore make them ineligible for auto-proxying or other kinds of bean post-processing. + For example, if you have a dependency annotated with @Resource + where the field/setter name does not directly correspond to the declared name of a bean and + no name attribute is used, then Spring will access other beans for matching them by type. The following examples show how to write, register, and use diff --git a/src/reference/docbook/cache.xml b/src/reference/docbook/cache.xml index 2ed4ab2b3ffd..01fa2d5a875f 100644 --- a/src/reference/docbook/cache.xml +++ b/src/reference/docbook/cache.xml @@ -57,7 +57,7 @@ Note that just like other services in Spring Framework, the caching service is an abstraction (not a cache implementation) and requires the use of an actual storage to store the cache data - that is, the abstraction frees the developer from having to write the caching logic but does not provide the actual stores. There are two integrations available out of the box, for JDK java.util.concurrent.ConcurrentMap - and Ehcache - see for more information on plugging in other cache stores/providers. + and EhCache - see for more information on plugging in other cache stores/providers.
    @@ -558,20 +558,28 @@ public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)]]><
    - Ehcache-based <interfacename>Cache</interfacename> + EhCache-based <interfacename>Cache</interfacename> - The Ehcache implementation is located under org.springframework.cache.ehcache package. Again, to use it, one simply needs to declare the appropriate + The EhCache implementation is located under org.springframework.cache.ehcache package. Again, to use it, one simply needs to declare the appropriate CacheManager: - + ]]> This setup bootstraps ehcache library inside Spring IoC (through bean ehcache) which is then wired into the dedicated CacheManager implementation. Note the entire ehcache-specific configuration is read from the resource ehcache.xml.
    +
    + GemFire-based <interfacename>Cache</interfacename> + + GemFire is a memory-oriented/disk-backed, elastically scalable, continuously available, active (with built-in pattern-based subscription notifications), + globally replicated database and provides fully-featured edge caching. For further information on how to use GemFire as a CacheManager (and more), please refer + to the Spring GemFire reference documentation. +
    +
    Dealing with caches without a backing store diff --git a/src/reference/docbook/jdbc.xml b/src/reference/docbook/jdbc.xml index e3b3d7f03c40..f4af0e3b233c 100644 --- a/src/reference/docbook/jdbc.xml +++ b/src/reference/docbook/jdbc.xml @@ -159,19 +159,12 @@ parameters for an SQL statement. - - SimpleJdbcTemplate combines - the most frequently used operations of JdbcTemplate and - NamedParameterJdbcTemplate. - - SimpleJdbcInsert and SimpleJdbcCall optimize database metadata to limit the amount of necessary configuration. This approach simplifies coding so that you only need to provide the name of the table or procedure - and provide a map of parameters matching the column names. + and provide a map of parameters matching the column names. This only works if the database provides adequate metadata. If the database doesn't provide this metadata, you will have to provide explicit configuration of the parameters. @@ -201,8 +194,7 @@ TR: OK. I removed the sentence since it isn;t entirely accurate. The implementat contains the JdbcTemplate class and its various callback interfaces, plus a variety of related classes. A subpackage named org.springframework.jdbc.core.simple contains - the SimpleJdbcTemplate class and the related - SimpleJdbcInsert and + the SimpleJdbcInsert and SimpleJdbcCall classes. Another subpackage named org.springframework.jdbc.core.namedparam contains the NamedParameterJdbcTemplate class and the related @@ -434,8 +426,6 @@ private static final class ActorMapper implements RowMapper<Actor> { A common practice when using the JdbcTemplate class (and the associated SimpleJdbcTemplate - and NamedParameterJdbcTemplate classes) is to configure a DataSource in your Spring configuration file, and then dependency-inject that @@ -693,107 +683,6 @@ public int countOfActors(Actor exampleActor) { of an application.
    -
    - <classname>SimpleJdbcTemplate</classname> - - The SimpleJdbcTemplate class wraps the - classic JdbcTemplate and leverages Java 5 - language features such as varargs and autoboxing. - - - In Spring 3.0, the original JdbcTemplate - also supports Java 5-enhanced syntax with generics and varargs. - However, the SimpleJdbcTemplate provides a - simpler API that works best when you do not need access to all the - methods that the JdbcTemplate offers. Also, because the - SimpleJdbcTemplate was designed for Java 5, it - has more methods that take advantage of varargs due to different - ordering of the parameters. - - - The value-add of the SimpleJdbcTemplate - class in the area of syntactic-sugar is best illustrated with a - before-and-after example. The next code snippet shows data access code - that uses the classic JdbcTemplate, followed by a - code snippet that does the same job with the - SimpleJdbcTemplate. - - // classic JdbcTemplate-style... -private JdbcTemplate jdbcTemplate; - -public void setDataSource(DataSource dataSource) { - this.jdbcTemplate = new JdbcTemplate(dataSource); -} - -public Actor findActor(String specialty, int age) { - - String sql = "select id, first_name, last_name from T_ACTOR" + - " where specialty = ? and age = ?"; - - RowMapper<Actor> mapper = new RowMapper<Actor>() { - public Actor mapRow(ResultSet rs, int rowNum) throws SQLException { - Actor actor = new Actor(); - actor.setId(rs.getLong("id")); - actor.setFirstName(rs.getString("first_name")); - actor.setLastName(rs.getString("last_name")); - return actor; - } - }; - - - // notice the wrapping up of the arguments in an array - return (Actor) jdbcTemplate.queryForObject(sql, new Object[] {specialty, age}, mapper); -} - - Here is the same method, with the - SimpleJdbcTemplate. - - // SimpleJdbcTemplate-style... -private SimpleJdbcTemplate simpleJdbcTemplate; - -public void setDataSource(DataSource dataSource) { - this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource); -} - -public Actor findActor(String specialty, int age) { - - String sql = "select id, first_name, last_name from T_ACTOR" + - " where specialty = ? and age = ?"; - RowMapper<Actor> mapper = new RowMapper<Actor>() { - public Actor mapRow(ResultSet rs, int rowNum) throws SQLException { - Actor actor = new Actor(); - actor.setId(rs.getLong("id")); - actor.setFirstName(rs.getString("first_name")); - actor.setLastName(rs.getString("last_name")); - return actor; - } - }; - - // notice the use of varargs since the parameter values now come - // after the RowMapper parameter - return this.simpleJdbcTemplate.queryForObject(sql, mapper, specialty, age); -} - - See for guidelines on - how to use the SimpleJdbcTemplate class in the - context of an application. - - - The SimpleJdbcTemplate class only offers - a subset of the methods exposed on the - JdbcTemplate class. If you need to use a method - from the JdbcTemplate that is not defined on - the SimpleJdbcTemplate, you can always access - the underlying JdbcTemplate by calling the - getJdbcOperations() method on the - SimpleJdbcTemplate, which then allows you to - invoke the method that you want. The only downside is that the methods - on the JdbcOperations interface are not - generic, so you are back to casting and so on. - -
    -
    <interfacename>SQLExceptionTranslator</interfacename> @@ -1370,9 +1259,7 @@ dataSource.setPassword(""); Most JDBC drivers provide improved performance if you batch multiple calls to the same prepared statement. By grouping updates into batches you - limit the number of round trips to the database. This section covers batch - processing using both the JdbcTemplate and the - SimpleJdbcTemplate. + limit the number of round trips to the database.
    Basic batch operations with the JdbcTemplate @@ -1579,11 +1466,11 @@ TR: Revised, please review.-->For this example, the initializing method is the you will see examples of multiple ones later. public class JdbcActorDao implements ActorDao { - private SimpleJdbcTemplate simpleJdbcTemplate; + private JdbcTemplate jdbcTemplate; private SimpleJdbcInsert insertActor; public void setDataSource(DataSource dataSource) { - this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource); + this.jdbcTemplate = new JdbcTemplate(dataSource); this.insertActor = new SimpleJdbcInsert(dataSource).withTableName("t_actor"); } @@ -1618,11 +1505,11 @@ TR: Revised, please review.-->For this example, the initializing method is the usingGeneratedKeyColumns method. public class JdbcActorDao implements ActorDao { - private SimpleJdbcTemplate simpleJdbcTemplate; + private JdbcTemplate jdbcTemplate; private SimpleJdbcInsert insertActor; public void setDataSource(DataSource dataSource) { - this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource); + this.jdbcTemplate = new JdbcTemplate(dataSource); this.insertActor = new SimpleJdbcInsert(dataSource) .withTableName("t_actor") @@ -1658,11 +1545,11 @@ TR: Revised, please review.-->For this example, the initializing method is the column names with the usingColumns method: public class JdbcActorDao implements ActorDao { - private SimpleJdbcTemplate simpleJdbcTemplate; + private JdbcTemplate jdbcTemplate; private SimpleJdbcInsert insertActor; public void setDataSource(DataSource dataSource) { - this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource); + this.jdbcTemplate = new JdbcTemplate(dataSource); this.insertActor = new SimpleJdbcInsert(dataSource) .withTableName("t_actor") @@ -1697,11 +1584,11 @@ TR: Revised, please review.-->For this example, the initializing method is the to extract the parameter values. Here is an example: public class JdbcActorDao implements ActorDao { - private SimpleJdbcTemplate simpleJdbcTemplate; + private JdbcTemplate jdbcTemplate; private SimpleJdbcInsert insertActor; public void setDataSource(DataSource dataSource) { - this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource); + this.jdbcTemplate = new JdbcTemplate(dataSource); this.insertActor = new SimpleJdbcInsert(dataSource) .withTableName("t_actor") @@ -1721,11 +1608,11 @@ TR: Revised, please review.-->For this example, the initializing method is the can be chained. public class JdbcActorDao implements ActorDao { - private SimpleJdbcTemplate simpleJdbcTemplate; + private JdbcTemplate jdbcTemplate; private SimpleJdbcInsert insertActor; public void setDataSource(DataSource dataSource) { - this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource); + this.jdbcTemplate = new JdbcTemplate(dataSource); this.insertActor = new SimpleJdbcInsert(dataSource) .withTableName("t_actor") @@ -1786,11 +1673,11 @@ END;The in_id parameter contains the procedure. public class JdbcActorDao implements ActorDao { - private SimpleJdbcTemplate simpleJdbcTemplate; + private JdbcTemplate jdbcTemplate; private SimpleJdbcCall procReadActor; public void setDataSource(DataSource dataSource) { - this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource); + this.jdbcTemplate = new JdbcTemplate(dataSource); this.procReadActor = new SimpleJdbcCall(dataSource) .withProcedureName("read_actor"); @@ -2004,11 +1891,11 @@ END; method. public class JdbcActorDao implements ActorDao { - private SimpleJdbcTemplate simpleJdbcTemplate; + private JdbcTemplate jdbcTemplate; private SimpleJdbcCall funcGetActorName; public void setDataSource(DataSource dataSource) { - this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource); + this.jdbcTemplate = new JdbcTemplate(dataSource); JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); jdbcTemplate.setResultsMapCaseInsensitive(true); this.funcGetActorName = @@ -2062,11 +1949,9 @@ END;To call this procedure you declare the newInstance method. public class JdbcActorDao implements ActorDao { - private SimpleJdbcTemplate simpleJdbcTemplate; private SimpleJdbcCall procReadAllActors; public void setDataSource(DataSource dataSource) { - this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource); JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); jdbcTemplate.setResultsMapCaseInsensitive(true); this.procReadAllActors = @@ -2679,7 +2564,7 @@ clobReader.close();]]> or you need to generate the SQL string dynamically once you know how many placeholders are required. The named parameter support provided in the NamedParameterJdbcTemplate and - SimpleJdbcTemplate takes the latter approach. + JdbcTemplate takes the latter approach. Pass in the values as a java.util.List of primitive objects. This list will be used to insert the required placeholders and pass in the values during the statement diff --git a/src/reference/docbook/mvc.xml b/src/reference/docbook/mvc.xml index c8aebb4f8a4f..a9e8489af349 100644 --- a/src/reference/docbook/mvc.xml +++ b/src/reference/docbook/mvc.xml @@ -1062,7 +1062,7 @@ public class RelativePathUriTemplateController { example: -@RequestMapping("/spring-web/{symbolicName:[a-z-]+}-{version:\d\.\d\.\d}.{extension:\.[a-z]}") +@RequestMapping("/spring-web/{symbolicName:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{extension:\\.[a-z]+}") public void handle(@PathVariable String version, @PathVariable String extension) { // ... } @@ -2157,6 +2157,14 @@ public class EditPetForm { available through the ServletRequest.getParameter*() family of methods. + + + As HttpPutFormContentFilter consumes the body of the + request, it should not be configured for PUT or PATCH URLs that rely on other + converters for application/x-www-form-urlencoded. This includes + @RequestBody MultiValueMap<String, String> and + HttpEntity<MultiValueMap<String, String>>. +
    @@ -2594,7 +2602,7 @@ deferredResult.setResult(data); ... - <web-app> + </web-app> The DispatcherServlet and any diff --git a/src/reference/docbook/new-in-3.2.xml b/src/reference/docbook/new-in-3.2.xml index 4ffd3cf6cdf0..8d403c6ead9b 100644 --- a/src/reference/docbook/new-in-3.2.xml +++ b/src/reference/docbook/new-in-3.2.xml @@ -120,7 +120,7 @@ @ExceptionHandler method that handles standard Spring MVC exceptions and returns a ResponseEntity that allowing customizing and - writing the response with HTTP message converters. This servers as an + writing the response with HTTP message converters. This serves as an alternative to the DefaultHandlerExceptionResolver, which does the same but returns a ModelAndView instead. diff --git a/src/reference/docbook/oxm.xml b/src/reference/docbook/oxm.xml index 00ae3966209f..1a7cbac17c5f 100644 --- a/src/reference/docbook/oxm.xml +++ b/src/reference/docbook/oxm.xml @@ -332,6 +332,9 @@ public class Application { xmlbeans-marshaller + + castor-marshaller + jibx-marshaller @@ -475,6 +478,86 @@ public class Application { ]]> +
    + XML Schema-based Configuration + + The castor-marshaller tag configures a + org.springframework.oxm.castor.CastorMarshaller. + Here is an example: + + + + ]]> + + + The marshaller instance can be configured in two ways, by specifying either the location of + a mapping file (through the mapping-location property), or by + identifying Java POJOs (through the target-class or + target-package properties) for which there exist corresponding + XML descriptor classes. The latter way is usually used in conjunction with XML code generation + from XML schemas. + + + + Available attributes are: + + + + + + + + Attribute + Description + Required + + + + + + id + + the id of the marshaller + no + + + + encoding + + the encoding to use for unmarshalling from XML + no + + + + target-class + + a Java class name for a POJO for which an XML class descriptor is available (as + generated through code generation) + + no + + + + target-package + + a Java package name that identifies a package that contains POJOs and their + corresponding Castor + XML descriptor classes (as generated through code generation from XML schemas) + + no + + + + mapping-location + + location of a Castor XML mapping file + no + + + + + +