diff --git a/build.gradle b/build.gradle index b6e1b8a03f..bf5df93e85 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 510a2698c6..4143780702 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 96237b86d1..7fbfbf5604 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 @@ import org.springframework.util.StringUtils; */ 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 abstract class AsyncExecutionAspectSupport implements BeanFactoryAware { * @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 792639b1c6..daa23a3293 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 java.util.concurrent.Future; 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 class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport * 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 class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport return null; } }); + if (Future.class.isAssignableFrom(invocation.getMethod().getReturnType())) { return result; } @@ -100,10 +107,9 @@ public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport } /** - * {@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 85342e838b..033a083016 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 23b012ecc7..fa8fc6441f 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 8e8b634ef0..8270d6962d 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 4cdc292dbb..2f1e91ccd2 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 03f446ca78..b4a3d93d1d 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 dbe15b8fb3..ce68942d40 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 7ab3fcd0c3..972a324859 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 031978ca91..816f6a1e42 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 6ff44249d0..131c82beef 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 6cb3ad60e3..660b35c136 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 54bd4ef746..f9c70661ae 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 2ea8f9e3f5..70a82b5f4e 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 70892e3949..b69e861933 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 class TransactionAspectTests extends AbstractDependencyInjectionSpringCon 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 @@ public class TransactionAspectTests extends AbstractDependencyInjectionSpringCon assertEquals(0, txManager.begun); try { toc.performTransactionalOperation(); - assertEquals(1, txManager.commits); } - catch (Throwable caught) { - if (caught instanceof AssertionFailedError) { - return; - } + finally { + assertEquals(1, txManager.begun); + assertEquals(rollback ? 0 : 1, txManager.commits); + assertEquals(rollback ? 1 : 0, txManager.rollbacks); } - - if (rollback) { - assertEquals(1, txManager.rollbacks); - } - assertEquals(1, txManager.begun); } protected void testNotTransactional(TransactionOperationCallback toc, Throwable expected) throws Throwable { @@ -258,4 +264,23 @@ public class TransactionAspectTests extends AbstractDependencyInjectionSpringCon 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 9d11eeb520..09faf0c3b2 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 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac } @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 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac * @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 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac 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 7b7822f0df..d51dc55fa7 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 class PropertiesBeanDefinitionReader extends AbstractBeanDefinitionReader * 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 7eaba17a54..64d0555d49 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 class RootBeanDefinition extends AbstractBeanDefinition { 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 class RootBeanDefinition extends AbstractBeanDefinition { } } + /** + * 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 3413b457c0..81fa4242b2 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.Assert; 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 class BeanConfigurerSupport implements BeanFactoryAware, InitializingBean /** * 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 4045578c8c..94f6376ce1 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 class DefaultListableBeanFactoryTests { 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 cc009917fd..550e87375b 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.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.core.io.Resource; import org.springframework.tests.sample.beans.TestBean; - /** * @author Rob Harrop * @author Chris Beams @@ -78,7 +77,6 @@ public final class CustomProblemReporterTests { @Override public void error(Problem problem) { - System.out.println(problem); this.errors.add(problem); } @@ -88,7 +86,6 @@ public final class CustomProblemReporterTests { @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 c8c6595e45..d0439fa93e 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.AuthPermission; 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.beans.factory.xml.XmlBeanDefinitionReader; 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 class CallbacksSecurityTests { } @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 0b65929fdd..a68028c592 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 class 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 37f573d2ae..6f012e9924 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 fc2ad00022..dcec4047ee 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.beans.factory.InitializingBean; 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 @@ import org.springframework.util.Assert; *

    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 class EhCacheFactoryBean implements FactoryBean, BeanNameAware, /** * 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 class EhCacheFactoryBean implements FactoryBean, BeanNameAware, } /** - * 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 class EhCacheFactoryBean implements FactoryBean, BeanNameAware, } /** - * 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 class EhCacheFactoryBean implements FactoryBean, BeanNameAware, } /** - * 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 class EhCacheFactoryBean implements FactoryBean, BeanNameAware, // 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 class EhCacheFactoryBean implements FactoryBean, BeanNameAware, 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 class EhCacheFactoryBean implements FactoryBean, BeanNameAware, * 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 a170eb84f8..a94c1fe2cb 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.ClassUtils; 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 @@ import org.springframework.util.ReflectionUtils; */ 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 class EhCacheManagerFactoryBean implements FactoryBean, Ini } /** - * 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 class EhCacheManagerFactoryBean implements FactoryBean, Ini } /** - * 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 class EhCacheManagerFactoryBean implements FactoryBean, Ini 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 class EhCacheManagerFactoryBean implements FactoryBean, Ini 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 fcfe221642..6e8efcd816 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 @@ public class EhCacheSupportTests extends TestCase { 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 @@ public class EhCacheSupportTests extends TestCase { 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 7d3e77d319..6bad879b76 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 @@ import java.lang.annotation.Target; * @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 @@ public @interface Caching { 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 a0c8ab1159..679352decf 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 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition; * @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 @@ public @interface Bean { 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 99678159d3..37c74ce2a1 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.FactoryBean; 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 @@ class ConfigurationClassEnhancer { 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 @@ class ConfigurationClassEnhancer { } 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 @@ class ConfigurationClassEnhancer { */ 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 @@ class ConfigurationClassEnhancer { /** * 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 @@ class ConfigurationClassEnhancer { // 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 @@ class ConfigurationClassEnhancer { } } - 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 @@ class ConfigurationClassEnhancer { } /** - * 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 @@ class ConfigurationClassEnhancer { * @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 @@ class ConfigurationClassEnhancer { 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 aedf118437..b74b53262b 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 @@ import org.springframework.util.Assert; 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 @@ public class DateFormatterRegistrar implements FormatterRegistrar { private DateToCalendarConverter dateToCalendarConverter = new DateToCalendarConverter(); public Calendar convert(Long source) { - return dateToCalendarConverter.convert(new Date(source)); + return this.dateToCalendarConverter.convert(new Date(source)); } } } diff --git a/spring-context/src/main/java/org/springframework/format/datetime/joda/JodaTimeFormatterRegistrar.java b/spring-context/src/main/java/org/springframework/format/datetime/joda/JodaTimeFormatterRegistrar.java index 51d9f9684b..7ad1a4e62e 100644 --- a/spring-context/src/main/java/org/springframework/format/datetime/joda/JodaTimeFormatterRegistrar.java +++ b/spring-context/src/main/java/org/springframework/format/datetime/joda/JodaTimeFormatterRegistrar.java @@ -75,7 +75,7 @@ public class JodaTimeFormatterRegistrar implements FormatterRegistrar { * Default is {@link DateTimeFormat#shortDate()}. */ public void setDateStyle(String dateStyle) { - factories.get(Type.DATE).setStyle(dateStyle+"-"); + this.factories.get(Type.DATE).setStyle(dateStyle+"-"); } /** @@ -83,7 +83,7 @@ public class JodaTimeFormatterRegistrar implements FormatterRegistrar { * Default is {@link DateTimeFormat#shortTime()}. */ public void setTimeStyle(String timeStyle) { - factories.get(Type.TIME).setStyle("-"+timeStyle); + this.factories.get(Type.TIME).setStyle("-"+timeStyle); } /** @@ -92,7 +92,7 @@ public class JodaTimeFormatterRegistrar implements FormatterRegistrar { * Default is {@link DateTimeFormat#shortDateTime()}. */ public void setDateTimeStyle(String dateTimeStyle) { - factories.get(Type.DATE_TIME).setStyle(dateTimeStyle); + this.factories.get(Type.DATE_TIME).setStyle(dateTimeStyle); } /** @@ -101,9 +101,9 @@ public class JodaTimeFormatterRegistrar implements FormatterRegistrar { * If set to true, the dateStyle, timeStyle, and dateTimeStyle properties are ignored. */ public void setUseIsoFormat(boolean useIsoFormat) { - factories.get(Type.DATE).setIso(useIsoFormat ? ISO.DATE : null); - factories.get(Type.TIME).setIso(useIsoFormat ? ISO.TIME : null); - factories.get(Type.DATE_TIME).setIso(useIsoFormat ? ISO.DATE_TIME : null); + this.factories.get(Type.DATE).setIso(useIsoFormat ? ISO.DATE : null); + this.factories.get(Type.TIME).setIso(useIsoFormat ? ISO.TIME : null); + this.factories.get(Type.DATE_TIME).setIso(useIsoFormat ? ISO.DATE_TIME : null); } /** @@ -174,19 +174,28 @@ public class JodaTimeFormatterRegistrar implements FormatterRegistrar { addFormatterForFields(registry, new ReadableInstantPrinter(dateTimeFormatter), new DateTimeParser(dateTimeFormatter), - ReadableInstant.class, Date.class, Calendar.class); + ReadableInstant.class); + + // In order to retain back compatibility we only register Date/Calendar + // types when a user defined formatter is specified (see SPR-10105) + if(this.formatters.containsKey(Type.DATE_TIME)) { + addFormatterForFields(registry, + new ReadableInstantPrinter(dateTimeFormatter), + new DateTimeParser(dateTimeFormatter), + Date.class, Calendar.class); + } registry.addFormatterForFieldAnnotation( new JodaDateTimeFormatAnnotationFormatterFactory()); } private DateTimeFormatter getFormatter(Type type) { - DateTimeFormatter formatter = formatters.get(type); + DateTimeFormatter formatter = this.formatters.get(type); if(formatter != null) { return formatter; } DateTimeFormatter fallbackFormatter = getFallbackFormatter(type); - return factories.get(type).createDateTimeFormatter(fallbackFormatter ); + return this.factories.get(type).createDateTimeFormatter(fallbackFormatter ); } private DateTimeFormatter getFallbackFormatter(Type type) { diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/AnnotationAsyncExecutionInterceptor.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/AnnotationAsyncExecutionInterceptor.java index a9abadab85..542aa15e32 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/AnnotationAsyncExecutionInterceptor.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/AnnotationAsyncExecutionInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,7 +39,7 @@ public class AnnotationAsyncExecutionInterceptor extends AsyncExecutionIntercept /** * Create a new {@code AnnotationAsyncExecutionInterceptor} with the given executor. * @param defaultExecutor the executor to be used by default if no more specific - * executor has been qualified at the method level using {@link Async#value()}. + * executor has been qualified at the method level using {@link Async#value()} */ public AnnotationAsyncExecutionInterceptor(Executor defaultExecutor) { super(defaultExecutor); @@ -64,7 +64,7 @@ public class AnnotationAsyncExecutionInterceptor extends AsyncExecutionIntercept if (async == null) { async = AnnotationUtils.findAnnotation(method.getDeclaringClass(), Async.class); } - return async == null ? null : async.value(); + return (async != null ? async.value() : null); } } diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationAdvisor.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationAdvisor.java index 873740c21c..35e57c59d2 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationAdvisor.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationAdvisor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,6 @@ import org.springframework.aop.Pointcut; import org.springframework.aop.support.AbstractPointcutAdvisor; import org.springframework.aop.support.ComposablePointcut; import org.springframework.aop.support.annotation.AnnotationMatchingPointcut; -import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.core.task.SimpleAsyncTaskExecutor; @@ -58,8 +57,6 @@ public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor implements B private Pointcut pointcut; - private BeanFactory beanFactory; - /** * Create a new {@code AsyncAnnotationAdvisor} for bean-style configuration. @@ -84,30 +81,15 @@ public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor implements B // If EJB 3.1 API not present, simply ignore. } this.advice = buildAdvice(executor); - this.setTaskExecutor(executor); this.pointcut = buildPointcut(asyncAnnotationTypes); } - /** - * Set the {@code BeanFactory} to be used when looking up executors by qualifier. - */ - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; - delegateBeanFactory(beanFactory); - } - - public void delegateBeanFactory(BeanFactory beanFactory) { - if (this.advice instanceof AnnotationAsyncExecutionInterceptor) { - ((AnnotationAsyncExecutionInterceptor)this.advice).setBeanFactory(beanFactory); - } - } /** - * Specify the task executor to use for asynchronous methods. + * Specify the default task executor to use for asynchronous methods. */ public void setTaskExecutor(Executor executor) { this.advice = buildAdvice(executor); - delegateBeanFactory(this.beanFactory); } /** @@ -126,6 +108,15 @@ public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor implements B 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 5f5cf4e280..68d127c72f 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 class CronSequenceGenerator { 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 eca37982e6..94f6385a1a 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 class CronTrigger implements Trigger { @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 58cd161175..3d4b907fa2 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 @@ import org.springframework.validation.annotation.Validated; * 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 7565e82099..f3c45fc2fa 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 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation. } 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 0a664afac0..44762719bc 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 @@ import edu.emory.mathcs.backport.java.util.Collections; * @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 5698ed0651..2803f0dc9f 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 class DateFormattingTests { } @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 237df0509f..c2aaf8d36a 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.joda.time.MutableDateTime; 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.annotation.DateTimeFormat.ISO; 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 class JodaTimeFormattingTests { } @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 12dbf05a50..ab05a3ea6e 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.MBeanServer; 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 @@ public abstract class AbstractMBeanServerTests extends TestCase { 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 7842ee328c..e1bd521a1d 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.JMXConnectorServer; 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 @@ public class MBeanClientInterceptorTests extends AbstractMBeanServerTests { return (IJmxTestBean) factory.getObject(); } + @Test public void testProxyClassIsDifferent() throws Exception { if (!runTests) return; @@ -85,6 +91,7 @@ public class MBeanClientInterceptorTests extends AbstractMBeanServerTests { 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 class MBeanClientInterceptorTests extends AbstractMBeanServerTests { 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 class MBeanClientInterceptorTests extends AbstractMBeanServerTests { assertEquals("The age should be 100", 100, age); } + @Test public void testSetAttributeValue() throws Exception { if (!runTests) return; @@ -111,6 +120,7 @@ public class MBeanClientInterceptorTests extends AbstractMBeanServerTests { 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 class MBeanClientInterceptorTests extends AbstractMBeanServerTests { } } + @Test public void testSetAttributeValueWithCheckedException() throws Exception { if (!runTests) return; @@ -135,6 +146,7 @@ public class MBeanClientInterceptorTests extends AbstractMBeanServerTests { } } + @Test public void testSetAttributeValueWithIOException() throws Exception { if (!runTests) return; @@ -147,6 +159,7 @@ public class MBeanClientInterceptorTests extends AbstractMBeanServerTests { } } + @Test public void testSetReadOnlyAttribute() throws Exception { if (!runTests) return; @@ -159,6 +172,7 @@ public class MBeanClientInterceptorTests extends AbstractMBeanServerTests { } } + @Test public void testInvokeNoArgs() throws Exception { if (!runTests) return; @@ -167,6 +181,7 @@ public class MBeanClientInterceptorTests extends AbstractMBeanServerTests { assertEquals("The operation should return 1", 1, result); } + @Test public void testInvokeArgs() throws Exception { if (!runTests) return; @@ -175,6 +190,7 @@ public class MBeanClientInterceptorTests extends AbstractMBeanServerTests { assertEquals("The operation should return 3", 3, result); } + @Test public void testInvokeUnexposedMethodWithException() throws Exception { if (!runTests) return; @@ -187,19 +203,13 @@ public class MBeanClientInterceptorTests extends AbstractMBeanServerTests { } } - // 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 7082efec13..74a29b0be0 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.JMXConnectorServer; 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 class RemoteMBeanClientInterceptorTestsIgnore extends MBeanClientIntercep 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 2c71236d78..1901901114 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 java.util.Date; 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 @@ public class CustomEditorConfigurerTests extends AbstractJmxTests { 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 class CustomEditorConfigurerTests extends AbstractJmxTests { 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 59740fa0db..f2bee32668 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.JmxTestBean; 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 2d7d9e2639..c08a9f73b2 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.ObjectInstance; 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.jmx.support.RegistrationPolicy; 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 final class MBeanExporterTests extends AbstractMBeanServerTests { } @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test public void testRegisterNullNotificationListenerType() throws Exception { Map listeners = new HashMap(); // put null in as a value... @@ -94,6 +99,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { } @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test public void testRegisterNotificationListenerForNonExistentMBean() throws Exception { Map listeners = new HashMap(); NotificationListener dummyListener = new NotificationListener() { @@ -117,6 +123,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { } } + @Test public void testWithSuppliedMBeanServer() throws Exception { MBeanExporter exporter = new MBeanExporter(); exporter.setBeans(getBeanMap()); @@ -127,6 +134,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { } /** 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 final class MBeanExporterTests extends AbstractMBeanServerTests { } */ + @Test public void testUserCreatedMBeanRegWithDynamicMBean() throws Exception { Map map = new HashMap(); map.put("spring:name=dynBean", new TestDynamicMBean()); @@ -153,6 +162,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { 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 final class MBeanExporterTests extends AbstractMBeanServerTests { } } + @Test public void testAutodetectWithExclude() throws Exception { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); new XmlBeanDefinitionReader(bf).loadBeanDefinitions(new ClassPathResource("autodetectMBeans.xml", getClass())); @@ -189,6 +200,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { } } + @Test public void testAutodetectLazyMBeans() throws Exception { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); new XmlBeanDefinitionReader(bf).loadBeanDefinitions(new ClassPathResource("autodetectLazyMBeans.xml", getClass())); @@ -210,6 +222,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { } } + @Test public void testAutodetectNoMBeans() throws Exception { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); new XmlBeanDefinitionReader(bf).loadBeanDefinitions(new ClassPathResource("autodetectNoMBeans.xml", getClass())); @@ -220,6 +233,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { } } + @Test public void testWithMBeanExporterListeners() throws Exception { MockMBeanExporterListener listener1 = new MockMBeanExporterListener(); MockMBeanExporterListener listener2 = new MockMBeanExporterListener(); @@ -235,6 +249,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { assertListener(listener2); } + @Test public void testExportJdkProxy() throws Exception { JmxTestBean bean = new JmxTestBean(); bean.setName("Rob Harrop"); @@ -260,6 +275,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { 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 final class MBeanExporterTests extends AbstractMBeanServerTests { assertNotNull(instance); } + @Test public void testRegisterIgnoreExisting() throws Exception { ObjectName objectName = ObjectNameManager.getInstance(OBJECT_NAME); @@ -311,6 +328,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { assertEquals("Rob Harrop", server.getAttribute(objectName, "Name")); } + @Test public void testRegisterReplaceExisting() throws Exception { ObjectName objectName = ObjectNameManager.getInstance(OBJECT_NAME); @@ -339,6 +357,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { 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 final class MBeanExporterTests extends AbstractMBeanServerTests { 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 final class MBeanExporterTests extends AbstractMBeanServerTests { exporter.afterPropertiesSet(); } + @Test public void testOnlyBonaFideMBeanIsExportedWhenAutodetectIsMBeanOnly() throws Exception { BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(Person.class); DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); @@ -405,6 +426,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { ObjectNameManager.getInstance(exportedBeanName)); } + @Test public void testBonaFideMBeanAndRegularBeanExporterWithAutodetectAll() throws Exception { BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(Person.class); DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); @@ -428,6 +450,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { ObjectNameManager.getInstance(notToBeExportedBeanName)); } + @Test public void testBonaFideMBeanIsNotExportedWithAutodetectAssembler() throws Exception { BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(Person.class); DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); @@ -450,6 +473,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { /** * 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 final class MBeanExporterTests extends AbstractMBeanServerTests { ObjectNameManager.getInstance(OBJECT_NAME)); } + @Test public void testSetAutodetectModeToOutOfRangeNegativeValue() throws Exception { try { MBeanExporter exporter = new MBeanExporter(); @@ -478,6 +503,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { } } + @Test public void testSetAutodetectModeToOutOfRangePositiveValue() throws Exception { try { MBeanExporter exporter = new MBeanExporter(); @@ -488,6 +514,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { } } + @Test public void testSetAutodetectModeNameToNull() throws Exception { try { MBeanExporter exporter = new MBeanExporter(); @@ -498,6 +525,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { } } + @Test public void testSetAutodetectModeNameToAnEmptyString() throws Exception { try { MBeanExporter exporter = new MBeanExporter(); @@ -508,6 +536,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { } } + @Test public void testSetAutodetectModeNameToAWhitespacedString() throws Exception { try { MBeanExporter exporter = new MBeanExporter(); @@ -518,6 +547,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { } } + @Test public void testSetAutodetectModeNameToARubbishValue() throws Exception { try { MBeanExporter exporter = new MBeanExporter(); @@ -528,6 +558,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { } } + @Test public void testNotRunningInBeanFactoryAndPassedBeanNameToExport() throws Exception { try { MBeanExporter exporter = new MBeanExporter(); @@ -541,6 +572,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { } } + @Test public void testNotRunningInBeanFactoryAndAutodetectionIsOn() throws Exception { try { MBeanExporter exporter = new MBeanExporter(); @@ -555,6 +587,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { /** * SPR-2158 */ + @Test public void testMBeanIsNotUnregisteredSpuriouslyIfSomeExternalProcessHasUnregisteredMBean() throws Exception { MBeanExporter exporter = new MBeanExporter(); exporter.setBeans(getBeanMap()); @@ -574,6 +607,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { /** * SPR-3302 */ + @Test public void testBeanNameCanBeUsedInNotificationListenersMap() throws Exception { String beanName = "charlesDexterWard"; BeanDefinitionBuilder testBean = BeanDefinitionBuilder.rootBeanDefinition(JmxTestBean.class); @@ -595,6 +629,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { exporter.afterPropertiesSet(); } + @Test public void testWildcardCanBeUsedInNotificationListenersMap() throws Exception { String beanName = "charlesDexterWard"; BeanDefinitionBuilder testBean = BeanDefinitionBuilder.rootBeanDefinition(JmxTestBean.class); @@ -619,6 +654,7 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests { /* * 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 4524412307..a6423dcee8 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.NotificationFilter; 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.access.NotificationListenerRegistrar; 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 @@ import org.springframework.jmx.support.ObjectNameManager; 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 class NotificationListenerTests extends AbstractMBeanServerTests { } @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 class NotificationListenerTests extends AbstractMBeanServerTests { 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 class NotificationListenerTests extends AbstractMBeanServerTests { 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 class NotificationListenerTests extends AbstractMBeanServerTests { } @SuppressWarnings("serial") + @Test public void testRegisterNotificationListenerWithFilter() throws Exception { ObjectName objectName = ObjectName.getInstance("spring:name=Test"); JmxTestBean bean = new JmxTestBean(); @@ -186,6 +194,7 @@ public class NotificationListenerTests extends AbstractMBeanServerTests { assertEquals("Listener incorrectly notified for Age", 0, listener.getCount(ageAttribute)); } + @Test public void testCreationWithNoNotificationListenerSet() { try { new NotificationListenerBean().afterPropertiesSet(); @@ -195,6 +204,7 @@ public class NotificationListenerTests extends AbstractMBeanServerTests { } @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test public void testRegisterNotificationListenerWithBeanNameAndBeanNameInBeansMap() throws Exception { String beanName = "testBean"; ObjectName objectName = ObjectName.getInstance("spring:name=Test"); @@ -225,6 +235,7 @@ public class NotificationListenerTests extends AbstractMBeanServerTests { } @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test public void testRegisterNotificationListenerWithBeanNameAndBeanInstanceInBeansMap() throws Exception { String beanName = "testBean"; ObjectName objectName = ObjectName.getInstance("spring:name=Test"); @@ -255,6 +266,7 @@ public class NotificationListenerTests extends AbstractMBeanServerTests { } @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test public void testRegisterNotificationListenerWithBeanNameBeforeObjectNameMappedToSameBeanInstance() throws Exception { String beanName = "testBean"; ObjectName objectName = ObjectName.getInstance("spring:name=Test"); @@ -286,6 +298,7 @@ public class NotificationListenerTests extends AbstractMBeanServerTests { } @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test public void testRegisterNotificationListenerWithObjectNameBeforeBeanNameMappedToSameBeanInstance() throws Exception { String beanName = "testBean"; ObjectName objectName = ObjectName.getInstance("spring:name=Test"); @@ -317,6 +330,7 @@ public class NotificationListenerTests extends AbstractMBeanServerTests { } @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test public void testRegisterNotificationListenerWithTwoBeanNamesMappedToDifferentBeanInstances() throws Exception { String beanName1 = "testBean1"; String beanName2 = "testBean2"; @@ -359,6 +373,7 @@ public class NotificationListenerTests extends AbstractMBeanServerTests { 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 class NotificationListenerTests extends AbstractMBeanServerTests { 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 a6abda7ed2..2237fcc155 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.NotificationBroadcasterSupport; 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 class NotificationPublisherTests extends AbstractMBeanServerTests { 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 class NotificationPublisherTests extends AbstractMBeanServerTests { 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 class NotificationPublisherTests extends AbstractMBeanServerTests { } /* + @Test public void testStandardMBean() throws Exception { // start the MBeanExporter ApplicationContext ctx = new ClassPathXmlApplicationContext("org/springframework/jmx/export/notificationPublisherTests.xml"); @@ -97,6 +104,7 @@ public class NotificationPublisherTests extends AbstractMBeanServerTests { } */ + @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 c95000c59d..256372eb1b 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 @@ package org.springframework.jmx.export; 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 @@ public class PropertyPlaceholderConfigurerTests extends AbstractJmxTests { return "org/springframework/jmx/export/propertyPlaceholderConfigurer.xml"; } + @Test public void testPropertiesReplaced() { IJmxTestBean bean = (IJmxTestBean) getContext().getBean("testBean"); @@ -38,6 +43,7 @@ public class PropertyPlaceholderConfigurerTests extends AbstractJmxTests { 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 d74787b7f8..11c68dcb99 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.ModelMBeanInfo; 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 class AnnotationMetadataAssemblerTests extends AbstractMetadataAssemblerT 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 2c4a06e0be..42962c5f64 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.ModelMBeanAttributeInfo; 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 abstract class AbstractJmxAssemblerTests extends AbstractJmxTests { getExpectedOperationCount(), inf.getOperations().length); } + @Test public void testRegisterAttributes() throws Exception { IJmxTestBean bean = getBean(); assertNotNull(bean); @@ -65,11 +72,13 @@ public abstract class AbstractJmxAssemblerTests extends AbstractJmxTests { 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 abstract class AbstractJmxAssemblerTests extends AbstractJmxTests { } } + @Test public void testGetMBeanOperationInfo() throws Exception { ModelMBeanInfo info = getMBeanInfoFromAssembler(); MBeanOperationInfo[] inf = info.getOperations(); @@ -98,6 +108,7 @@ public abstract class AbstractJmxAssemblerTests extends AbstractJmxTests { } } + @Test public void testDescriptionNotNull() throws Exception { ModelMBeanInfo info = getMBeanInfoFromAssembler(); @@ -105,6 +116,7 @@ public abstract class AbstractJmxAssemblerTests extends AbstractJmxTests { 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 abstract class AbstractJmxAssemblerTests extends AbstractJmxTests { 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 abstract class AbstractJmxAssemblerTests extends AbstractJmxTests { 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 abstract class AbstractJmxAssemblerTests extends AbstractJmxTests { assertEquals("Incorrect result", new Integer(50), result); } + @Test public void testAttributeInfoHasDescriptors() throws Exception { ModelMBeanInfo info = getMBeanInfoFromAssembler(); @@ -141,6 +156,7 @@ public abstract class AbstractJmxAssemblerTests extends AbstractJmxTests { desc.getFieldValue("setMethod")); } + @Test public void testAttributeHasCorrespondingOperations() throws Exception { ModelMBeanInfo info = getMBeanInfoFromAssembler(); @@ -159,6 +175,7 @@ public abstract class AbstractJmxAssemblerTests extends AbstractJmxTests { 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 99cefe7158..0c3ece7839 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.ModelMBeanAttributeInfo; 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.export.metadata.JmxAttributeSource; 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 abstract class AbstractMetadataAssemblerTests extends AbstractJmxAssemble "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 abstract class AbstractMetadataAssemblerTests extends AbstractJmxAssemble /** * 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 abstract class AbstractMetadataAssemblerTests extends AbstractJmxAssemble /** * 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 abstract class AbstractMetadataAssemblerTests extends AbstractJmxAssemble /** * 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 abstract class AbstractMetadataAssemblerTests extends AbstractJmxAssemble 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 abstract class AbstractMetadataAssemblerTests extends AbstractJmxAssemble 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 abstract class AbstractMetadataAssemblerTests extends AbstractJmxAssemble 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 abstract class AbstractMetadataAssemblerTests extends AbstractJmxAssemble 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 abstract class AbstractMetadataAssemblerTests extends AbstractJmxAssemble 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 abstract class AbstractMetadataAssemblerTests extends AbstractJmxAssemble "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 abstract class AbstractMetadataAssemblerTests extends AbstractJmxAssemble 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 d4034ed449..e7bb1d68c9 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 @@ package org.springframework.jmx.export.assembler; 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 @@ public class InterfaceBasedMBeanInfoAssemblerCustomTests extends AbstractJmxAsse 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 5cd8488eb7..1a485f377e 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.MBeanAttributeInfo; 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 class InterfaceBasedMBeanInfoAssemblerMappedTests extends AbstractJmxAsse assertFalse("Age is not writable", attr.isWritable()); } + @Test public void testWithUnknownClass() throws Exception { try { getWithMapping("com.foo.bar.Unknown"); @@ -47,6 +54,7 @@ public class InterfaceBasedMBeanInfoAssemblerMappedTests extends AbstractJmxAsse } } + @Test public void testWithNonInterface() throws Exception { try { getWithMapping("JmxTestBean"); @@ -57,7 +65,8 @@ public class InterfaceBasedMBeanInfoAssemblerMappedTests extends AbstractJmxAsse } } - 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 class InterfaceBasedMBeanInfoAssemblerMappedTests extends AbstractJmxAsse 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 2c2a8618eb..ea234b09e8 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.MBeanAttributeInfo; 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 class MethodExclusionMBeanInfoAssemblerComboTests extends AbstractJmxAsse 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 35fe108270..aa1e2d57c2 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.MBeanAttributeInfo; 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 class MethodExclusionMBeanInfoAssemblerMappedTests extends AbstractJmxAss 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 f037d87bcb..6b12c74d97 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.MBeanAttributeInfo; 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 class MethodExclusionMBeanInfoAssemblerNotMappedTests extends AbstractJmx 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 8a49293006..53335e50d7 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 java.util.Properties; 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 @@ public class MethodExclusionMBeanInfoAssemblerTests extends AbstractJmxAssembler return assembler; } + @Test public void testSupermanIsReadOnly() throws Exception { ModelMBeanInfo info = getMBeanInfoFromAssembler(); ModelMBeanAttributeInfo attr = info.getAttribute("Superman"); @@ -71,6 +76,7 @@ public class MethodExclusionMBeanInfoAssemblerTests extends AbstractJmxAssembler /* * 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 062a45febd..10b1985515 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 class MethodNameBasedMBeanInfoAssemblerMappedTests extends AbstractJmxAss 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 class MethodNameBasedMBeanInfoAssemblerMappedTests extends AbstractJmxAss 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 72da54f6f3..d85f42b508 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.MBeanOperationInfo; 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 @@ public class MethodNameBasedMBeanInfoAssemblerTests extends AbstractJmxAssembler return assembler; } + @Test public void testGetAgeIsReadOnly() throws Exception { ModelMBeanInfo info = getMBeanInfoFromAssembler(); ModelMBeanAttributeInfo attr = info.getAttribute(AGE_ATTRIBUTE); @@ -58,6 +64,7 @@ public class MethodNameBasedMBeanInfoAssemblerTests extends AbstractJmxAssembler 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 ef244b085e..6d66caa79c 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.JMXConnector; 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 class ConnectorServerFactoryBeanTestsIgnore extends AbstractMBeanServerTe } } + @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 class ConnectorServerFactoryBeanTestsIgnore extends AbstractMBeanServerTe } } + @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 class ConnectorServerFactoryBeanTestsIgnore extends AbstractMBeanServerTe } } + @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 598bc73e8c..c9c83cffae 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.JMXConnectorServer; 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 @@ public class MBeanServerConnectionFactoryBeanTests extends AbstractMBeanServerTe 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 class MBeanServerConnectionFactoryBeanTests extends AbstractMBeanServerTe } } + @Test public void testWithNoServiceUrl() throws Exception { MBeanServerConnectionFactoryBean bean = new MBeanServerConnectionFactoryBean(); try { @@ -85,16 +83,9 @@ public class MBeanServerConnectionFactoryBeanTests extends AbstractMBeanServerTe } } - // 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 class MBeanServerConnectionFactoryBeanTests extends AbstractMBeanServerTe } } + @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 f679713eb9..028f60c559 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 class AsyncExecutionTests { 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 class AsyncExecutionTests { 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 class AsyncExecutionTests { } + 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 class AsyncExecutionTests { } + 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 class AsyncExecutionTests { } + 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 0000000000..491706f9e3 --- /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 a99bffa1ed..90acc959b3 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 @@ import static org.junit.Assert.*; @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 @@ public class CronTriggerTests { 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 3e6cac5759..be4f45213a 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.Errors; 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 class ValidatorFactoryTests { 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 class ValidatorFactoryTests { } @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 3842a741c7..7d8045b51b 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 @@ public abstract class GenericTypeResolver { * 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 62cf4748ea..7326b3f045 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 @@ package org.springframework.core.convert.support; 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 @@ import org.springframework.core.convert.converter.ConverterFactory; 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 09082aad5c..0baba6a8c1 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 class SystemEnvironmentPropertySource extends MapPropertySource { 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 b2bf4405da..af2d4c62d8 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.IOException; 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 0ae86e0879..e7fefa175d 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 @@ public class AntPathMatcher implements PathMatcher { 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 @@ public class AntPathMatcher implements PathMatcher { } 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 5aac309086..2c14da3b51 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.StringWriter; 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 abstract class FileCopyUtils { 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 abstract class FileCopyUtils { /** * 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 0000000000..cc3107d81f --- /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 c001902252..801769cdfa 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 javax.xml.transform.sax.SAXResult; 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 af005ecc14..5706a4317e 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.InputSource; 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 @@ class StaxSource extends SAXSource { } /** - * 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 576f39ebcf..efafa259d4 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 @@ import org.springframework.util.ClassUtils; */ 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 abstract class StaxUtils { /** * 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 abstract class StaxUtils { /** * 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 abstract class StaxUtils { /** * 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 abstract class StaxUtils { 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 abstract class StaxUtils { /** * 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 abstract class StaxUtils { /** * 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 abstract class StaxUtils { /** * 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 abstract class StaxUtils { /** * 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 abstract class StaxUtils { return new XMLEventStreamWriter(eventWriter, eventFactory); } + /** * Inner class to avoid a static JAXP 1.4 dependency. */ @@ -349,11 +345,11 @@ public abstract class StaxUtils { } 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 7b2ff0490a..ba42cddf9d 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 class DefaultConversionTests { 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 class DefaultConversionTests { 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 dcc376eea4..662f0dab53 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.EnumSet; 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 enum TestGroup { 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 2b4860fd72..7f9fe924c4 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 class TestGroupTests { @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 0a34e1c470..05a98f405d 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 class AntPathMatcherTests { 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 0000000000..fe10d23596 --- /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 d9ae1a2efb..3f89bed512 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 7489c6ba69..710e975921 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.TypedValue; 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 c243b5d7f4..7915a3c015 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 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { } 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 c086771cbe..ce6ad5123b 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 @@ package org.springframework.expression.spel.support; 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.MethodResolver; 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 class ReflectiveMethodResolver implements MethodResolver { 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 class ReflectiveMethodResolver implements MethodResolver { } }); + // 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 class ReflectiveMethodResolver implements MethodResolver { 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 fdf04c7ba8..e6e28eedd3 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 static org.junit.Assert.fail; 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 class SpelReproTests extends ExpressionTestCase { 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 class SpelReproTests extends ExpressionTestCase { 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 0000000000..3098b293c7 --- /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 0000000000..e4776d70e6 --- /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 0000000000..3250836265 --- /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 0000000000..2f0a32e53b --- /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 8f5a9964cd..6db102fc64 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 @@ public interface JdbcOperations { * @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 @@ public interface JdbcOperations { * 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 @@ public interface JdbcOperations { * @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 @@ public interface JdbcOperations { * 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 3f0085cebf..910d206872 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 class JdbcTemplate extends JdbcAccessor implements JdbcOperations { 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 class JdbcTemplate extends JdbcAccessor implements JdbcOperations { 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 a3da020588..29accf8ffa 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 @@ public interface CallMetaDataProvider { /** * 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 0dff1678c4..c7365bed5b 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 @@ public interface TableMetaDataProvider { /** * 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 @@ public interface TableMetaDataProvider { /** * 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 0000000000..a0b525f0dc --- /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 a7f5c9f7c9..91e20f3fcd 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 @@ public interface NamedParameterJdbcOperations { 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 @@ public interface NamedParameterJdbcOperations { 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 @@ public interface NamedParameterJdbcOperations { */ 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 @@ public interface NamedParameterJdbcOperations { 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 @@ public interface NamedParameterJdbcOperations { * @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 @@ public interface NamedParameterJdbcOperations { * (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 @@ public interface NamedParameterJdbcOperations { * 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 @@ public interface NamedParameterJdbcOperations { * 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 @@ public interface NamedParameterJdbcOperations { * 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 @@ public interface NamedParameterJdbcOperations { * 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 @@ public interface NamedParameterJdbcOperations { * 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 @@ public interface NamedParameterJdbcOperations { *

    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 @@ public interface NamedParameterJdbcOperations { * @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 @@ public interface NamedParameterJdbcOperations { * @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 1abfdea3c2..d8258cefc9 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 class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations 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 class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations 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 class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations 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 class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations 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 class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations 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 7c80a14d0f..12e1dbd85e 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 @@ public abstract class AbstractJdbcCall { /** - * Get the configured {@link JdbcTemplate} + * Get the configured {@link JdbcTemplate}. */ public JdbcTemplate getJdbcTemplate() { return this.jdbcTemplate; @@ -110,7 +110,6 @@ public abstract class AbstractJdbcCall { return this.callableStatementFactory; } - /** * Set the name of the stored procedure. */ @@ -294,7 +293,7 @@ public abstract class AbstractJdbcCall { 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 98f0e5991e..cb66d803c7 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 @@ public abstract class AbstractJdbcInsert { //------------------------------------------------------------------------- - // 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 abstract class AbstractJdbcInsert { 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 abstract class AbstractJdbcInsert { } /** - * 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 @@ public abstract class AbstractJdbcInsert { } /** - * 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 @@ public abstract class AbstractJdbcInsert { /** * 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 @@ public abstract class AbstractJdbcInsert { /** * 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 @@ public abstract class AbstractJdbcInsert { } /** - * 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 @@ public abstract class AbstractJdbcInsert { 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 @@ public abstract class AbstractJdbcInsert { } 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 abstract class AbstractJdbcInsert { 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 abstract class AbstractJdbcInsert { 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 abstract class AbstractJdbcInsert { keys.put(getGeneratedKeyNames()[0], key); keyHolder.getKeyList().add(keys); } - } finally { + } + finally { JdbcUtils.closeResultSet(rs); JdbcUtils.closeStatement(keyStmt); } @@ -547,14 +538,14 @@ public abstract class AbstractJdbcInsert { } /** - * 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 @@ public abstract class AbstractJdbcInsert { /** * 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 @@ public abstract class AbstractJdbcInsert { } /** - * 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 abstract class AbstractJdbcInsert { */ private void setParameterValues(PreparedStatement preparedStatement, List values, int[] columnTypes) throws SQLException { + int colIndex = 0; for (Object value : values) { colIndex++; @@ -624,25 +611,23 @@ public abstract class AbstractJdbcInsert { } /** - * 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 parameterSource the parameter vakues provided as a {@link SqlParameterSource} * @return Map with parameter names and values */ protected 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 d687dbc2f6..6ace2d863e 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 class SimpleJdbcCall extends AbstractJdbcCall implements SimpleJdbcCallOp /** * @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 7d29df8301..98b89e9f1f 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 @@ import org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor; * 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 7ef0b32583..c106ad57f4 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.dao.DataAccessException; 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 f6ddc15b34..5495aa68ce 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 @@ import org.springframework.core.Constants;
      * 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 @@ public class LazyConnectionDataSourceProxy extends DelegatingDataSource { // 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 1321c81662..3bde77765d 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 @@ import org.springframework.util.Assert; *

    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 737a582459..4da40d61fa 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.ResultSet; 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 6964172e96..e570be24f0 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.Log; 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 @@ import org.apache.commons.logging.LogFactory; * 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 class DefaultLobHandler extends AbstractLobHandler { * {@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 class DefaultLobHandler extends AbstractLobHandler { 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 class DefaultLobHandler extends AbstractLobHandler { } 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 class DefaultLobHandler extends AbstractLobHandler { 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 class DefaultLobHandler extends AbstractLobHandler { 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 @@ public class DefaultLobHandler extends AbstractLobHandler { } 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 251a63a0e8..07e1847e4f 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 @@ import java.sql.SQLException; /** * 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 @@ import java.sql.SQLException; * 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 @@ import java.sql.SQLException; * *

    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 5399cf9c91..2b239b3504 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 @@ import org.springframework.util.FileCopyUtils; * 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 @@ import org.springframework.util.FileCopyUtils; * @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 class OracleLobHandler extends AbstractLobHandler { } /** - * 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 @@ public class OracleLobHandler extends AbstractLobHandler { ((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 @@ public class OracleLobHandler extends AbstractLobHandler { */ 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 @@ public class OracleLobHandler extends AbstractLobHandler { 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 @@ public class OracleLobHandler extends AbstractLobHandler { */ 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 0000000000..f8c52660cb --- /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 e967966aee..09129320e5 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 @@ import org.springframework.util.ReflectionUtils; /** * 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 d40a575c7a..b225ea19d2 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 @@ import org.springframework.util.ReflectionUtils; /** * 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 @@ import org.springframework.util.ReflectionUtils; */ 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 class WebSphereNativeJdbcExtractor extends NativeJdbcExtractorAdapter { */ @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 6564fe5ee7..c17c8bbc02 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.HashMap; 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.RowCallbackHandler; 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 class NamedParameterJdbcTemplateTests { 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 class NamedParameterJdbcTemplateTests { 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 class NamedParameterJdbcTemplateTests { 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 class NamedParameterJdbcTemplateTests { 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 class NamedParameterJdbcTemplateTests { } @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 class NamedParameterJdbcTemplateTests { @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 class NamedParameterJdbcTemplateTests { @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 392d02b1fa..954dd4982e 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 @@ public class JmsMessageEndpointFactory extends AbstractMessageEndpointFactory { /** - * 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 6d3e7ccf25..5871bb8a87 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 javax.sql.DataSource; 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 @@ import org.springframework.transaction.support.TransactionSynchronizationManager */ @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 class HibernateTransactionManager extends AbstractPlatformTransactionMana * 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 class HibernateTransactionManager extends AbstractPlatformTransactionMana 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 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana 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 e125abf2b7..b0aac2d2bb 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 class LocalSessionFactoryBuilder extends Configuration { 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 class LocalSessionFactoryBuilder extends Configuration { * 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 26bdd97e29..f235fc196e 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.List; 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.TransactionCallbackWithoutResult; 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 class HibernateTransactionManagerTests extends TestCase { 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(); + Connection con = mock(Connection.class); + final SessionFactory sf = mock(SessionFactory.class); + ImplementingSession session = mock(ImplementingSession.class); + Transaction tx = mock(Transaction.class); - 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(); + 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 class HibernateTransactionManagerTests extends TestCase { 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(); + Connection con = mock(Connection.class); + final SessionFactory sf = mock(SessionFactory.class); + ImplementingSession session = mock(ImplementingSession.class); + Transaction tx = mock(Transaction.class); - 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(); + 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 class HibernateTransactionManagerTests extends TestCase { }); 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(); + Connection con = mock(Connection.class); + final SessionFactory sf = mock(SessionFactory.class); + final ImplementingSession session = mock(ImplementingSession.class); + Transaction tx = mock(Transaction.class); - 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(); + 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 class HibernateTransactionManagerTests extends TestCase { }); 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(); + Connection con = mock(Connection.class); + final SessionFactory sf = mock(SessionFactory.class); + ImplementingSession session = mock(ImplementingSession.class); + Transaction tx = mock(Transaction.class); - 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(); + 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 class HibernateTransactionManagerTests extends TestCase { // 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(); + Connection con = mock(Connection.class); + final SessionFactory sf = mock(SessionFactory.class); + ImplementingSession session = mock(ImplementingSession.class); + Transaction tx = mock(Transaction.class); - 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(); + 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 class HibernateTransactionManagerTests extends TestCase { // 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(); + @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); - 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(); + 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 class HibernateTransactionManagerTests extends TestCase { }); 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(); + @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); - 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(); + 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 class HibernateTransactionManagerTests extends TestCase { }); 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(); + final SessionFactory sf = mock(SessionFactory.class); + final Session session = mock(Session.class); - sf.openSession(); - sfControl.setReturnValue(session, 1); - session.flush(); - sessionControl.setVoidCallable(1); - session.close(); - sessionControl.setReturnValue(null, 1); - sfControl.replay(); - sessionControl.replay(); + 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 class HibernateTransactionManagerTests extends TestCase { }); 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(); + 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); - 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(); + 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 class HibernateTransactionManagerTests extends TestCase { }); 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 class HibernateTransactionManagerTests extends TestCase { 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(); + Connection con = mock(Connection.class); + final SessionFactory sf = mock(SessionFactory.class); + ImplementingSession session = mock(ImplementingSession.class); + Transaction tx = mock(Transaction.class); - sf.openSession(); - sfControl.setReturnValue(session, 1); - session.beginTransaction(); - sessionControl.setReturnValue(tx, 1); - tx.commit(); + 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 class HibernateTransactionManagerTests extends TestCase { 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(); + 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); - 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(); + 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 class HibernateTransactionManagerTests extends TestCase { 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(); + 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); - 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(); + 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 class HibernateTransactionManagerTests extends TestCase { 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(); + final SessionFactory sf = mock(SessionFactory.class); + final Session session = mock(Session.class); + final Transaction tx1 = mock(Transaction.class); + final Transaction tx2 = mock(Transaction.class); - 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(); + 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 class HibernateTransactionManagerTests extends TestCase { } }); - 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 class HibernateTransactionManagerTests extends TestCase { 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 @@ public class HibernateTransactionManagerTests extends TestCase { 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 class HibernateTransactionManagerTests extends TestCase { 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 class HibernateTransactionManagerTests extends TestCase { 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 class HibernateTransactionManagerTests extends TestCase { 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(); + final SessionFactory sf = mock(SessionFactory.class); + final Session session = mock(Session.class); + Transaction tx = mock(Transaction.class); - 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(); + given(sf.openSession()).willReturn(session); + given(session.beginTransaction()).willReturn(tx); HibernateTransactionManager tm = new HibernateTransactionManager(sf); tm.setPrepareConnection(false); @@ -1411,17 +1174,10 @@ public class HibernateTransactionManagerTests extends TestCase { 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 896a91379f..abd06c801a 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 class HibernateTransactionManager extends AbstractPlatformTransactionMana (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 d944ef497d..72f5ee42ea 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 class AnnotationSessionFactoryBean extends LocalSessionFactoryBean implem @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 @@ public class AnnotationSessionFactoryBean extends LocalSessionFactoryBean implem 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 @@ public class AnnotationSessionFactoryBean extends LocalSessionFactoryBean implem * 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 38d0f7263a..9d058158b9 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 @@ package org.springframework.orm.jdo; 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.dao.support.PersistenceExceptionTranslator; 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 @@ import org.springframework.util.ReflectionUtils; /** * 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 class DefaultJdoDialect implements JdoDialect, PersistenceExceptionTransl //------------------------------------------------------------------------- /** - * 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 534beea656..d4d12ba036 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.sql.Savepoint; 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.hibernate.exception.GenericJDBCException; 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.TransactionCallbackWithoutResult; 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 class HibernateTransactionManagerTests { } @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 class HibernateTransactionManagerTests { } @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 class HibernateTransactionManagerTests { 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 538ce2572b..46003776b0 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.SQLException; 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 javax.transaction.UserTransaction; 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.SpringPersistenceManagerProxyBean; 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.TransactionCallbackWithoutResult; 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 class JdoTransactionManagerTests { } @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 20d9e5f029..a35fdac8ca 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 @@ package org.springframework.orm.jpa.openjpa; 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 78e8f86af5..7b74313114 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 @@ import org.springframework.orm.jpa.AbstractContainerEntityManagerFactoryIntegrat * 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 0405e8272e..131b782c87 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 javax.xml.stream.XMLStreamWriter; 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.XmlMappingException; 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 class CastorMarshaller extends AbstractMarshaller implements Initializing } /** - * 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 class CastorMarshaller extends AbstractMarshaller implements Initializing } /** - * 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,55 +216,8 @@ public class CastorMarshaller extends AbstractMarshaller implements Initializing } /** - * Set whether the Castor {@link Unmarshaller} should preserve "ignorable" whitespace.

    Default is - * {@code false}. - * - * @see org.exolab.castor.xml.Unmarshaller#setWhitespacePreserve(boolean) - */ - public void setWhitespacePreserve(boolean whitespacePreserve) { - this.whitespacePreserve = whitespacePreserve; - } - - /** - * 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 setIgnoreExtraAttributes(boolean ignoreExtraAttributes) { - this.ignoreExtraAttributes = ignoreExtraAttributes; - } - - /** - * 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) - */ - public void setIgnoreExtraElements(boolean ignoreExtraElements) { - this.ignoreExtraElements = ignoreExtraElements; - } - - /** - * 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; - } - - /** - * Returns whether this marshaller should output namespaces. - */ - public boolean isSuppressNamespaces() { - return suppressNamespaces; - } - - /** - * Sets whether this marshaller should output namespaces. The default is {@code false}, i.e. namespaces are written. - * + * 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 setSuppressNamespaces(boolean suppressNamespaces) { @@ -255,16 +225,8 @@ public class CastorMarshaller extends AbstractMarshaller implements Initializing } /** - * Sets whether this marshaller should output the xsi:type attribute. - */ - public boolean isSuppressXsiType() { - return suppressXsiType; - } - - /** - * Sets whether this marshaller should output the {@code xsi:type} attribute. The default is {@code false}, i.e. the - * {@code xsi:type} is written. - * + * 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 setSuppressXsiType(boolean suppressXsiType) { @@ -272,9 +234,8 @@ public class CastorMarshaller extends AbstractMarshaller implements Initializing } /** - * Sets whether this marshaller should output the xml declaration.

    The default is {@code true}, the xml - * declaration will be written. - * + * 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 setMarshalAsDocument(boolean marshalAsDocument) { @@ -282,18 +243,8 @@ public class CastorMarshaller extends AbstractMarshaller implements Initializing } /** - * Sets the name of the root element. - * - * @see org.exolab.castor.xml.Marshaller#setRootElement(String) - */ - public void setRootElement(String rootElement) { - this.rootElement = rootElement; - } - - /** - * 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. - * + * 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 setMarshalExtendedType(boolean marshalExtendedType) { @@ -301,9 +252,16 @@ public class CastorMarshaller extends AbstractMarshaller implements Initializing } /** - * Sets the value of {@code xsi:noNamespaceSchemaLocation} attribute. When set, the {@code - * xsi:noNamespaceSchemaLocation} attribute will be written for the root element. - * + * Set the name of the root element. + * @see org.exolab.castor.xml.Marshaller#setRootElement(String) + */ + public void setRootElement(String rootElement) { + this.rootElement = rootElement; + } + + /** + * 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 setNoNamespaceSchemaLocation(String noNamespaceSchemaLocation) { @@ -311,9 +269,8 @@ public class CastorMarshaller extends AbstractMarshaller implements Initializing } /** - * Sets the value of {@code xsi:schemaLocation} attribute.When set, the {@code xsi:schemaLocation} attribute will be - * written for the root element. - * + * 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 void setSchemaLocation(String schemaLocation) { @@ -321,10 +278,9 @@ public class CastorMarshaller extends AbstractMarshaller implements Initializing } /** - * 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. - * + * 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 setUseXSITypeAtRoot(boolean useXSITypeAtRoot) { @@ -332,38 +288,54 @@ public class CastorMarshaller extends AbstractMarshaller implements Initializing } /** - * 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) + * Set whether the Castor {@link Unmarshaller} should preserve "ignorable" whitespace. + *

    Default is {@code false}. + * @see org.exolab.castor.xml.Unmarshaller#setWhitespacePreserve(boolean) */ - public void setProcessingInstructions(Map processingInstructions) { - this.processingInstructions = processingInstructions; + public void setWhitespacePreserve(boolean whitespacePreserve) { + this.whitespacePreserve = whitespacePreserve; } /** - * 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) + * 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 setNamespaceToPackageMapping(Map namespaceToPackageMapping) { - this.namespaceToPackageMapping = namespaceToPackageMapping; + public void setIgnoreExtraAttributes(boolean ignoreExtraAttributes) { + this.ignoreExtraAttributes = ignoreExtraAttributes; } /** - * Sets the expected object for the unmarshaller, into which the source will be unmarshalled. - * + * 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 setIgnoreExtraElements(boolean ignoreExtraElements) { + this.ignoreExtraElements = ignoreExtraElements; + } + + /** + * 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} + */ + @Deprecated + public void setObject(Object root) { + this.rootObject = root; + } + + /** + * 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 setObject(Object root) { - this.root = root; + public void setRootObject(Object root) { + this.rootObject = root; } /** - * 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. - * + * 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 setReuseObjects(boolean reuseObjects) { @@ -371,39 +343,99 @@ public class CastorMarshaller extends AbstractMarshaller implements Initializing } /** - * Sets whether this unmarshaller should clear collections upon the first use.

    The default is {@code false}, - * which means that marshaller won't clear collections. - * + * 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; } - public void setBeanClassLoader(ClassLoader classLoader) { - this.classLoader = classLoader; + /** + * 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; } - 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"); - } - } + /** + * 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) { + this.namespaceToPackageMapping = namespaceToPackageMapping; + } + + /** + * 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 setEntityResolver(EntityResolver entityResolver) { + this.entityResolver = entityResolver; + } + + /** + * 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 setClassDescriptorResolver(XMLClassDescriptorResolver classDescriptorResolver) { + this.classDescriptorResolver = classDescriptorResolver; + } + + /** + * Set the Castor {@link IDResolver} to be used during unmarshalling. + * @see org.exolab.castor.xml.Unmarshaller#setIDResolver(IDResolver) + */ + 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.beanClassLoader = classLoader; + } + + + public void afterPropertiesSet() throws CastorMappingException, IOException { try { this.xmlContext = createXMLContext(this.mappingLocations, this.targetClasses, this.targetPackages); } @@ -416,9 +448,8 @@ public class CastorMarshaller extends AbstractMarshaller implements Initializing } /** - * 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 @@ public class CastorMarshaller extends AbstractMarshaller implements Initializing 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 @@ public class CastorMarshaller extends AbstractMarshaller implements Initializing @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 @@ public class CastorMarshaller extends AbstractMarshaller implements Initializing @Override protected final void marshalOutputStream(Object graph, OutputStream outputStream) throws XmlMappingException, IOException { + marshalWriter(graph, new OutputStreamWriter(outputStream, encoding)); } @@ -502,42 +541,29 @@ public class CastorMarshaller extends AbstractMarshaller implements Initializing /** * 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 @@ public class CastorMarshaller extends AbstractMarshaller implements Initializing /** * 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 956c003233..4ffd0c495d 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 class Jaxb2Marshaller 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 class Jaxb2Marshaller 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 class Jaxb2Marshaller 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 class Jaxb2Marshaller 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 class Jaxb2Marshaller } } - 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 @@ public class Jaxb2Marshaller 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 class Jaxb2Marshaller 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 class Jaxb2Marshaller } } - 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 b53c20a5b9..dd36d17b8b 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 class XStreamMarshaller extends AbstractMarshaller implements Initializin * @see XStream#NO_REFERENCES */ public void setMode(int mode) { - this.getXStream().setMode(mode); + this.xstream.setMode(mode); } /** @@ -137,10 +137,10 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin 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 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin */ 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 class XStreamMarshaller extends AbstractMarshaller implements Initializin */ 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 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin } /** - * 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 class XStreamMarshaller extends AbstractMarshaller implements Initializin 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 class XStreamMarshaller extends AbstractMarshaller implements Initializin */ public void setUseAttributeForTypes(Class[] types) { for (Class type : types) { - this.getXStream().useAttributeFor(type); + this.xstream.useAttributeFor(type); } } @@ -242,7 +237,7 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin 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 class XStreamMarshaller extends AbstractMarshaller implements Initializin 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 class XStreamMarshaller extends AbstractMarshaller implements Initializin 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 class XStreamMarshaller extends AbstractMarshaller implements Initializin 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 class XStreamMarshaller extends AbstractMarshaller implements Initializin */ 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 class XStreamMarshaller extends AbstractMarshaller implements Initializin */ 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 class XStreamMarshaller extends AbstractMarshaller implements Initializin * @see XStream#autodetectAnnotations(boolean) */ public void setAutodetectAnnotations(boolean autodetectAnnotations) { - this.getXStream().autodetectAnnotations(autodetectAnnotations); + this.xstream.autodetectAnnotations(autodetectAnnotations); } /** @@ -363,7 +358,7 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin public final void afterPropertiesSet() throws Exception { - customizeXStream(getXStream()); + customizeXStream(this.xstream); } /** @@ -458,7 +453,7 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin */ 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 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin */ 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 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin 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 04f53b6973..0000000000 --- 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 367b4aa5c1..585640893b 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.MarshallingException; 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 class CastorUnmarshallerTests extends AbstractUnmarshallerTests { @Test public void testWhitespacePreserveTrue() throws Exception { - getCastorUnmarshaller().setWhitespacePreserve(true); Object result = unmarshalFlights(); testFlights(result); @@ -122,7 +118,6 @@ public class CastorUnmarshallerTests extends AbstractUnmarshallerTests { @Test public void testWhitespacePreserveFalse() throws Exception { - getCastorUnmarshaller().setWhitespacePreserve(false); Object result = unmarshalFlights(); testFlights(result); @@ -130,7 +125,6 @@ public class CastorUnmarshallerTests extends AbstractUnmarshallerTests { @Test public void testIgnoreExtraAttributesTrue() throws Exception { - getCastorUnmarshaller().setIgnoreExtraAttributes(true); Object result = unmarshal(EXTRA_ATTRIBUTES_STRING); testFlights(result); @@ -146,7 +140,6 @@ public class CastorUnmarshallerTests extends AbstractUnmarshallerTests { @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 class CastorUnmarshallerTests extends AbstractUnmarshallerTests { } @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 class CastorUnmarshallerTests extends AbstractUnmarshallerTests { @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 054e3b18c9..87608730ad 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 class JsonPathExpectationsHelper { } 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 8be3cf86ea..78ff9d2d56 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 class MockMultipartHttpServletRequestBuilder extends MockHttpServletReque 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 a44f43a816..7e8b7afd63 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 @@ import org.springframework.web.servlet.DispatcherServlet; * @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 b9597c196e..079a42fc01 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 class StandaloneMockMvcBuilder extends DefaultMockMvcBuilder 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 0000000000..b3f42091ef --- /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 0000000000..e73e3dd4c3 --- /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 0000000000..101285c520 --- /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 01302bb110..b87c987dac 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 @@ public class ServletTestExecutionListener extends AbstractTestExecutionListener 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 532a82c01f..383b20dff9 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 java.util.List; 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 @@ import org.springframework.util.StringUtils; * @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 class JdbcTestUtils { if (StringUtils.hasText(whereClause)) { sql += " WHERE " + whereClause; } - return jdbcTemplate.queryForInt(sql); + return jdbcTemplate.queryForObject(sql, Integer.class); } /** @@ -103,6 +104,39 @@ public class JdbcTestUtils { 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 54b55c8c44..59cef2c1db 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 abstract class AbstractTransactionalAnnotatedConfigClassTests { } 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 d5fd1b68e4..6ebdf91819 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 class JdbcTestUtilsTests { 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 fac5eba990..61f8db5b4e 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 @@ public class GenericMessageEndpointFactory extends AbstractMessageEndpointFactor /** - * 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 9f9b9503dd..bcd2c77880 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.NoTransactionException; 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 abstract class TransactionAspectSupport implements BeanFactoryAware, Init } + /** + * 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 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init } } - /** - * 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 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init 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 abstract class TransactionAspectSupport implements BeanFactoryAware, Init } } + + /** + * 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 11f72759a7..db46387077 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,22 +16,18 @@ package org.springframework.transaction.interceptor; +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 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; - /** * AOP Alliance MethodInterceptor for declarative transaction * management using the common Spring transaction infrastructure @@ -40,7 +36,7 @@ import org.springframework.transaction.support.TransactionCallback; *

    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 class TransactionInterceptor extends TransactionAspectSupport implements // 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(); + // Adapt to TransactionAspectSupport's invokeWithinTransaction... + return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() { + public Object proceedWithInvocation() throws Throwable { + return 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; - } - } - catch (ThrowableHolderException ex) { - throw ex.getCause(); - } - } + }); } @@ -195,39 +127,4 @@ public class TransactionInterceptor extends TransactionAspectSupport implements 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 74dc2af2c2..225fbccd9f 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 813f98cfad..bb87844420 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 java.net.URI; 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 @@ final class BufferingClientHttpRequestWrapper extends AbstractBufferingClientHtt @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 f280790fec..f075b202bd 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 java.io.InputStream; 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 @@ final class BufferingClientHttpResponseWrapper implements ClientHttpResponse { 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 9d39760479..a422614fe9 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 java.util.List; 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 @@ class InterceptingClientHttpRequest extends AbstractBufferingClientHttpRequest { 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 989d9e4d28..c2a7864296 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 @@ final class SimpleBufferingClientHttpRequest extends AbstractBufferingClientHttp } } - 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 22d17b50f0..3aa31d9b83 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 class SimpleClientHttpRequestFactory implements ClientHttpRequestFactory 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 a5e831102c..f14f7e9025 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 java.util.Map; 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 @@ final class SimpleStreamingClientHttpRequest extends AbstractClientHttpRequest { @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 @@ final class SimpleStreamingClientHttpRequest extends AbstractClientHttpRequest { 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 f1ddc0080f..d1b25f7c35 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 java.io.IOException; 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 class ByteArrayHttpMessageConverter extends AbstractHttpMessageConverter< @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 @@ public class ByteArrayHttpMessageConverter extends AbstractHttpMessageConverter< @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 f66a38bf19..516eb92ab2 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.HttpInputMessage; 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 class FormHttpMessageConverter implements HttpMessageConverter 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 9991fbcc80..69f22a92e1 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 @@ package org.springframework.http.converter; import java.io.IOException; import java.io.InputStream; + import javax.activation.FileTypeMap; import javax.activation.MimetypesFileTypeMap; @@ -28,7 +29,7 @@ import org.springframework.http.HttpInputMessage; 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 @@ public class ResourceHttpMessageConverter extends AbstractHttpMessageConverter 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 @@ public class ResourceHttpMessageConverter extends AbstractHttpMessageConverter 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 @@ public class StringHttpMessageConverter extends AbstractHttpMessageConverterHierarchical URIs */ @@ -406,7 +408,10 @@ final class HierarchicalUriComponents extends UriComponents { 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 @@ final class HierarchicalUriComponents extends UriComponents { 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 @@ final class HierarchicalUriComponents extends UriComponents { /** * 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 0ee697f6d9..861b46fe55 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 @@ public class JavaScriptUtils { 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 63211beaf5..8b50ac6fda 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 @@ import org.springframework.util.SystemPropertyUtils; * 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 abstract class Log4jWebConfigurer { 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 6e51c77dff..54d788d624 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 @@ import org.springframework.util.ObjectUtils; * 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 @@ final class OpaqueUriComponents extends UriComponents { } 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 0000000000..46105f8aa0 --- /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 141cd7b86e..30dcac0f84 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 @@ package org.springframework.web.util; 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.LinkedMultiValueMap; 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 @@ import org.springframework.util.StringUtils; * * @author Arjen Poutsma * @author Rossen Stoyanchev + * @author Phillip Webb * @since 3.1 * @see #newInstance() * @see #fromPath(String) @@ -53,7 +55,7 @@ import org.springframework.util.StringUtils; */ 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 class UriComponentsBuilder { 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 @@ public class UriComponentsBuilder { 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 class UriComponentsBuilder { * @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 class UriComponentsBuilder { * @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 class UriComponentsBuilder { 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 class UriComponentsBuilder { return this; } - /** - * Represents a builder for {@link HierarchicalUriComponents.PathComponent} - */ + private interface PathComponentBuilder { - - HierarchicalUriComponents.PathComponent build(); - - PathComponentBuilder appendPath(String path); - - PathComponentBuilder appendPathSegments(String... pathSegments); + PathComponent build(); } - /** - * Represents a builder for full string paths. - */ - private static class FullPathComponentBuilder implements PathComponentBuilder { + private static class CompositePathComponentBuilder implements PathComponentBuilder { - private final StringBuilder path; + private LinkedList componentBuilders = new LinkedList(); - private FullPathComponentBuilder(String path) { - this.path = new StringBuilder(path); + public CompositePathComponentBuilder() { } - public HierarchicalUriComponents.PathComponent build() { - return new HierarchicalUriComponents.FullPathComponent(path.toString()); + public CompositePathComponentBuilder(String path) { + addPath(path); } - public PathComponentBuilder appendPath(String path) { - this.path.append(path); - return this; + 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); + } } - public PathComponentBuilder appendPathSegments(String... pathSegments) { - PathComponentCompositeBuilder builder = new PathComponentCompositeBuilder(this); - builder.appendPathSegments(pathSegments); - return builder; - } - } - - /** - * 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 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); + } } - private Collection removeEmptyPathSegments(String... pathSegments) { - List result = new ArrayList(); - for (String segment : pathSegments) { - if (StringUtils.hasText(segment)) { - result.add(segment); + @SuppressWarnings("unchecked") + private T getLastBuilder(Class builderClass) { + if (!this.componentBuilders.isEmpty()) { + PathComponentBuilder last = this.componentBuilders.getLast(); + if (builderClass.isInstance(last)) { + return (T) last; } } - return result; + return null; } - public HierarchicalUriComponents.PathComponent build() { - return new HierarchicalUriComponents.PathSegmentComponent(pathSegments); - } - - public PathComponentBuilder appendPath(String path) { - PathComponentCompositeBuilder builder = new PathComponentCompositeBuilder(this); - builder.appendPath(path); - return builder; - } - - public PathComponentBuilder appendPathSegments(String... pathSegments) { - this.pathSegments.addAll(removeEmptyPathSegments(pathSegments)); - return this; - } - } - - /** - * Represents a builder for a collection of PathComponents. - */ - private static class PathComponentCompositeBuilder implements PathComponentBuilder { - - private final List pathComponentBuilders = new ArrayList(); - - private PathComponentCompositeBuilder(PathComponentBuilder builder) { - pathComponentBuilders.add(builder); - } - - public HierarchicalUriComponents.PathComponent build() { - List pathComponents = - new ArrayList(pathComponentBuilders.size()); - - for (PathComponentBuilder pathComponentBuilder : pathComponentBuilders) { - pathComponents.add(pathComponentBuilder.build()); + 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); + } } - return new HierarchicalUriComponents.PathComponentComposite(pathComponents); - } - - public PathComponentBuilder appendPath(String path) { - this.pathComponentBuilders.add(new FullPathComponentBuilder(path)); - return this; - } - - public PathComponentBuilder appendPathSegments(String... pathSegments) { - this.pathComponentBuilders.add(new PathSegmentComponentBuilder(pathSegments)); - return this; + if (components.isEmpty()) { + return HierarchicalUriComponents.NULL_PATH_COMPONENT; + } + if (components.size() == 1) { + return components.get(0); + } + return new HierarchicalUriComponents.PathComponentComposite(components); } } + private static class FullPathComponentBuilder implements PathComponentBuilder { - /** - * Represents a builder for an empty path. - */ - private static PathComponentBuilder NULL_PATH_COMPONENT_BUILDER = new PathComponentBuilder() { + private StringBuilder path = new StringBuilder(); - public HierarchicalUriComponents.PathComponent build() { - return HierarchicalUriComponents.NULL_PATH_COMPONENT; + public void append(String path) { + this.path.append(path); } - public PathComponentBuilder appendPath(String path) { - return new FullPathComponentBuilder(path); + public PathComponent build() { + if (this.path.length() == 0) { + return null; + } + String path = this.path.toString().replace("//", "/"); + return new HierarchicalUriComponents.FullPathComponent(path); } - public PathComponentBuilder appendPathSegments(String... pathSegments) { - return new PathSegmentComponentBuilder(pathSegments); + public void removeTrailingSlash() { + int index = this.path.length() - 1; + if (this.path.charAt(index) == '/') { + this.path.deleteCharAt(index); + } } - }; + } + + private static class PathSegmentComponentBuilder implements PathComponentBuilder { + + private List pathSegments = new LinkedList(); + + public void append(String... pathSegments) { + for (String pathSegment : pathSegments) { + if (StringUtils.hasText(pathSegment)) { + this.pathSegments.add(pathSegment); + } + } + } + + 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 7f98ee2369..362b933658 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 @@ public class UrlPathHelper { * @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 c8dfbfc079..9939aaf034 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 0000000000..002f33c7e8 --- /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 0000000000..68111693e1 --- /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 908783e4c1..0ea0462a50 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.LinkedMultiValueMap; 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 class FormHttpMessageConverterTests { 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 7ae30114de..66637e04b2 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.WebServiceClient; 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.beans.factory.support.RootBeanDefinition; 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 f8978d8b17..bda24d4892 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 class RequestParamMethodArgumentResolverTests { 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 class RequestParamMethodArgumentResolverTests { 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 class RequestParamMethodArgumentResolverTests { 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 0000000000..182f18eb2d --- /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 0000000000..21082779a4 --- /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 8205ca6cdf..25fbb98d59 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.junit.Test; 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 class UriComponentsBuilderTests { 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 class UriComponentsBuilderTests { 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 c9f8d60917..2013e6df65 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 class UriComponentsTests { 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 df8cd2d453..67dec59124 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.Request; 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 class TilesView extends AbstractUrlBasedView { @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 3dca4eda94..a3f3c37547 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.ServletContext; 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 @@ public abstract class FrameworkServlet extends HttpServletBean { 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 9d409a3fec..7fef39364c 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 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser { 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 e85f189126..c3d1fc0883 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 class WebMvcConfigurationSupport implements ApplicationContextAware, Serv 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 ea079c4938..b790a1e62b 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 abstract class AbstractHandlerMapping extends WebApplicationObjectSupport /** * 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 c8f11c8175..ccdec77d51 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.util.ReflectionUtils.MethodFilter; 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 357e31fe36..53642385ca 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 @@ import org.springframework.web.util.UrlPathHelper; */ 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 @@ public final class PatternsRequestCondition extends AbstractRequestCondition 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 @@ public final class PatternsRequestCondition extends AbstractRequestCondition 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 @@ public final class PatternsRequestCondition extends AbstractRequestCondition 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 cc242bf756..89fcb4e9e1 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 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe 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 bd9405b65b..1a2e175fb1 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 @@ import org.springframework.web.util.WebUtils; * *

    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 @@ import org.springframework.web.util.WebUtils; * *

    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 c0a66c5216..51b6cbc2f2 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 @@ package org.springframework.web.servlet.view; 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 abstract class AbstractCachingViewResolver extends WebApplicationObjectSu } 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 abstract class AbstractCachingViewResolver extends WebApplicationObjectSu 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 abstract class AbstractCachingViewResolver extends WebApplicationObjectSu */ 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 2726e0537c..77c3a67e03 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 @@ public class ResourceBundleViewResolver extends AbstractCachingViewResolver *

    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 43b29af322..bfb41221f7 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 7510ab304e..29c670a138 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 javax.servlet.http.HttpServletResponse; 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 @@ import org.springframework.web.servlet.view.AbstractView; * @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 class MappingJackson2JsonView extends AbstractView { /** - * 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 class MappingJackson2JsonView extends AbstractView { 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 class MappingJackson2JsonView extends AbstractView { 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 class MappingJackson2JsonView extends AbstractView { } /** - * 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 class MappingJackson2JsonView extends AbstractView { 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 class MappingJackson2JsonView extends AbstractView { /** * 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 class MappingJackson2JsonView extends AbstractView { * @deprecated use {@link #getModelKeys()} instead */ @Deprecated - public Set getRenderedAttributes() { + public final Set getRenderedAttributes() { return this.modelKeys; } @@ -212,6 +226,7 @@ public class MappingJackson2JsonView extends AbstractView { this.updateContentLength = updateContentLength; } + @Override protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) { setResponseContentType(request, response); @@ -227,34 +242,21 @@ public class MappingJackson2JsonView extends AbstractView { 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 @@ public class MappingJackson2JsonView extends AbstractView { 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 5b97da49a8..ef0ad59d99 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.JsonEncoding; 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 @@ import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; * @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 class MappingJacksonJsonView extends AbstractView { /** - * 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 class MappingJacksonJsonView extends AbstractView { 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 class MappingJacksonJsonView extends AbstractView { 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 class MappingJacksonJsonView extends AbstractView { } /** - * 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 class MappingJacksonJsonView extends AbstractView { 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 class MappingJacksonJsonView extends AbstractView { /** * 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 class MappingJacksonJsonView extends AbstractView { * @deprecated use {@link #getModelKeys()} instead */ @Deprecated - public Set getRenderedAttributes() { + public final Set getRenderedAttributes() { return this.modelKeys; } @@ -230,34 +242,21 @@ public class MappingJacksonJsonView extends AbstractView { 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 @@ public class MappingJacksonJsonView extends AbstractView { 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 b73e69ae6f..667fb643b4 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 org.springframework.web.util.WebUtils; 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 @@ public class DispatcherServletTests extends TestCase { 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 9c38b7c39c..72f6639713 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 javax.servlet.http.HttpServletRequest; 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 class PatternsRequestConditionTests { 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 2db8412a82..099197c07d 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 class RequestMappingInfoHandlerMappingTests { this.handlerMapping = new TestRequestMappingInfoHandlerMapping(); this.handlerMapping.registerHandler(testController); - this.handlerMapping.setRemoveSemicolonContent(false); } @Test @@ -181,6 +180,19 @@ public class RequestMappingInfoHandlerMappingTests { } } + @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 17a4c4f50c..9e4f320ef6 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 @@ public class FreeMarkerMacroTests { 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 @@ public class FreeMarkerMacroTests { 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 class FreeMarkerMacroTests { 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 @@ public class FreeMarkerMacroTests { 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 8440b4f945..b1e0156186 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 7cd00264a9..1e6da7b4ef 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 4aa12d7db5..7631dce007 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 4099fbc940..a616d7b30d 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 7078e1535a..fafebe6127 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 0723be50fc..7c09841290 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 551496d7be..d2a0aa80c3 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 2ed4ab2b3f..01fa2d5a87 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 e3b3d7f03c..f4af0e3b23 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 c8aebb4f8a..a9e8489af3 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 4ffd3cf6cd..8d403c6ead 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 00ae396620..1a7cbac17c 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 + + + + + +