enableLTW;
+ private AnnotationAttributes enableLTW;
@Autowired(required=false)
private LoadTimeWeavingConfigurer ltwConfigurer;
@@ -54,7 +53,7 @@ public class LoadTimeWeavingConfiguration implements ImportAware, BeanClassLoade
private ClassLoader beanClassLoader;
public void setImportMetadata(AnnotationMetadata importMetadata) {
- this.enableLTW = importMetadata.getAnnotationAttributes(EnableLoadTimeWeaving.class.getName(), false);
+ this.enableLTW = MetadataUtils.attributesFor(importMetadata, EnableLoadTimeWeaving.class);
Assert.notNull(this.enableLTW,
"@EnableLoadTimeWeaving is not present on importing class " +
importMetadata.getClassName());
@@ -79,7 +78,8 @@ public class LoadTimeWeavingConfiguration implements ImportAware, BeanClassLoade
loadTimeWeaver = new DefaultContextLoadTimeWeaver(this.beanClassLoader);
}
- switch ((AspectJWeaving) this.enableLTW.get("aspectjWeaving")) {
+ AspectJWeaving aspectJWeaving = this.enableLTW.getEnum("aspectjWeaving");
+ switch (aspectJWeaving) {
case DISABLED:
// AJ weaving is disabled -> do nothing
break;
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/MetadataUtils.java b/spring-context/src/main/java/org/springframework/context/annotation/MetadataUtils.java
new file mode 100644
index 00000000000..a2dc4b36105
--- /dev/null
+++ b/spring-context/src/main/java/org/springframework/context/annotation/MetadataUtils.java
@@ -0,0 +1,49 @@
+/*
+ * 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.context.annotation;
+
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.core.type.AnnotationMetadata;
+import org.springframework.core.type.MethodMetadata;
+
+/**
+ * Convenience methods adapting {@link AnnotationMetadata} and {@link MethodMetadata}
+ * annotation attribute maps to the {@link AnnotationAttributes} API. As of Spring 3.1.1,
+ * both the reflection- and ASM-based implementations of these SPIs return
+ * {@link AnnotationAttributes} instances anyway, but for backward-compatibility, their
+ * signatures still return Maps. Therefore, for the usual case, these methods perform
+ * little more than a cast from Map to AnnotationAttributes.
+ *
+ * @author Chris Beams
+ * @since 3.1.1
+ * @see AnnotationAttributes#fromMap(java.util.Map)
+ */
+class MetadataUtils {
+
+ public static AnnotationAttributes attributesFor(AnnotationMetadata metadata, Class> annoClass) {
+ return attributesFor(metadata, annoClass.getName());
+ }
+
+ public static AnnotationAttributes attributesFor(AnnotationMetadata metadata, String annoClassName) {
+ return AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(annoClassName, false));
+ }
+
+ public static AnnotationAttributes attributesFor(MethodMetadata metadata, Class> targetAnno) {
+ return AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(targetAnno.getName()));
+ }
+
+}
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ProfileHelper.java b/spring-context/src/main/java/org/springframework/context/annotation/ProfileHelper.java
deleted file mode 100644
index 309aa877f10..00000000000
--- a/spring-context/src/main/java/org/springframework/context/annotation/ProfileHelper.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2002-2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.context.annotation;
-
-import org.springframework.core.type.AnnotationMetadata;
-
-class ProfileHelper {
- /**
- * Return whether the given metadata includes Profile information, whether directly or
- * through meta-annotation.
- */
- static boolean isProfileAnnotationPresent(AnnotationMetadata metadata) {
- return metadata.isAnnotated(Profile.class.getName());
- }
-
- /**
- * Return the String[] of candidate profiles from {@link Profile#value()}.
- */
- static String[] getCandidateProfiles(AnnotationMetadata metadata) {
- return (String[])metadata.getAnnotationAttributes(Profile.class.getName()).get("value");
- }
-}
\ No newline at end of file
diff --git a/spring-context/src/main/java/org/springframework/context/support/ReloadableResourceBundleMessageSource.java b/spring-context/src/main/java/org/springframework/context/support/ReloadableResourceBundleMessageSource.java
index a51d6fc4072..da8af17fb07 100644
--- a/spring-context/src/main/java/org/springframework/context/support/ReloadableResourceBundleMessageSource.java
+++ b/spring-context/src/main/java/org/springframework/context/support/ReloadableResourceBundleMessageSource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2009 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -135,7 +135,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource
* @see java.util.ResourceBundle
*/
public void setBasename(String basename) {
- setBasenames(new String[] {basename});
+ setBasenames(basename);
}
/**
@@ -153,7 +153,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource
* @see #setBasename
* @see java.util.ResourceBundle
*/
- public void setBasenames(String[] basenames) {
+ public void setBasenames(String... basenames) {
if (basenames != null) {
this.basenames = new String[basenames.length];
for (int i = 0; i < basenames.length; i++) {
diff --git a/spring-context/src/main/java/org/springframework/context/support/ResourceBundleMessageSource.java b/spring-context/src/main/java/org/springframework/context/support/ResourceBundleMessageSource.java
index 9080ebd3332..a3426dc1d5b 100644
--- a/spring-context/src/main/java/org/springframework/context/support/ResourceBundleMessageSource.java
+++ b/spring-context/src/main/java/org/springframework/context/support/ResourceBundleMessageSource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2008 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -100,7 +100,7 @@ public class ResourceBundleMessageSource extends AbstractMessageSource implement
* @see java.util.ResourceBundle#getBundle(String)
*/
public void setBasename(String basename) {
- setBasenames(new String[] {basename});
+ setBasenames(basename);
}
/**
@@ -119,7 +119,7 @@ public class ResourceBundleMessageSource extends AbstractMessageSource implement
* @see #setBasename
* @see java.util.ResourceBundle#getBundle(String)
*/
- public void setBasenames(String[] basenames) {
+ public void setBasenames(String... basenames) {
if (basenames != null) {
this.basenames = new String[basenames.length];
for (int i = 0; i < basenames.length; i++) {
diff --git a/spring-context/src/main/java/org/springframework/instrument/classloading/jboss/JBossLoadTimeWeaver.java b/spring-context/src/main/java/org/springframework/instrument/classloading/jboss/JBossLoadTimeWeaver.java
index 243724af27d..706fda38300 100644
--- a/spring-context/src/main/java/org/springframework/instrument/classloading/jboss/JBossLoadTimeWeaver.java
+++ b/spring-context/src/main/java/org/springframework/instrument/classloading/jboss/JBossLoadTimeWeaver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,6 +36,7 @@ import org.springframework.util.ClassUtils;
* Thanks to Ales Justin and Marius Bogoevici for the initial prototype.
*
* @author Costin Leau
+ * @author Juergen Hoeller
* @since 3.0
*/
public class JBossLoadTimeWeaver implements LoadTimeWeaver {
@@ -55,23 +56,18 @@ public class JBossLoadTimeWeaver implements LoadTimeWeaver {
/**
* Create a new instance of the {@link JBossLoadTimeWeaver} class using
* the supplied {@link ClassLoader}.
- * @param classLoader the ClassLoader to delegate to for
- * weaving (must not be null)
+ * @param classLoader the ClassLoader to delegate to for weaving
+ * (must not be null)
*/
public JBossLoadTimeWeaver(ClassLoader classLoader) {
Assert.notNull(classLoader, "ClassLoader must not be null");
- String loaderClassName = classLoader.getClass().getName();
-
- if (loaderClassName.startsWith("org.jboss.classloader")) {
- // JBoss AS 5 or JBoss AS 6
- this.adapter = new JBossMCAdapter(classLoader);
- }
- else if (loaderClassName.startsWith("org.jboss.modules")) {
+ if (classLoader.getClass().getName().startsWith("org.jboss.modules")) {
// JBoss AS 7
this.adapter = new JBossModulesAdapter(classLoader);
}
else {
- throw new IllegalArgumentException("Unexpected ClassLoader type: " + loaderClassName);
+ // JBoss AS 5 or JBoss AS 6
+ this.adapter = new JBossMCAdapter(classLoader);
}
}
diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/AbstractAsyncConfiguration.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/AbstractAsyncConfiguration.java
index d0bbe8899b4..57fd7b69452 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/annotation/AbstractAsyncConfiguration.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/AbstractAsyncConfiguration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,12 +17,12 @@
package org.springframework.scheduling.annotation;
import java.util.Collection;
-import java.util.Map;
import java.util.concurrent.Executor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportAware;
+import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.Assert;
@@ -37,12 +37,13 @@ import org.springframework.util.Assert;
@Configuration
public abstract class AbstractAsyncConfiguration implements ImportAware {
- protected Map enableAsync;
+ protected AnnotationAttributes enableAsync;
protected Executor executor;
public void setImportMetadata(AnnotationMetadata importMetadata) {
- enableAsync = importMetadata.getAnnotationAttributes(EnableAsync.class.getName(), false);
- Assert.notNull(enableAsync,
+ this.enableAsync = AnnotationAttributes.fromMap(
+ importMetadata.getAnnotationAttributes(EnableAsync.class.getName(), false));
+ Assert.notNull(this.enableAsync,
"@EnableAsync is not present on importing class " +
importMetadata.getClassName());
}
diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/ProxyAsyncConfiguration.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/ProxyAsyncConfiguration.java
index 93589de7852..8ec439db2e5 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/annotation/ProxyAsyncConfiguration.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/ProxyAsyncConfiguration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -42,13 +42,11 @@ public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {
@Bean(name=AnnotationConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public AsyncAnnotationBeanPostProcessor asyncAdvisor() {
- Assert.notNull(enableAsync, "@EnableAsync annotation metadata was not injected");
+ Assert.notNull(this.enableAsync, "@EnableAsync annotation metadata was not injected");
AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();
- @SuppressWarnings("unchecked")
- Class extends Annotation> customAsyncAnnotation =
- (Class extends Annotation>) enableAsync.get("annotation");
+ Class extends Annotation> customAsyncAnnotation = enableAsync.getClass("annotation");
if (customAsyncAnnotation != AnnotationUtils.getDefaultValue(EnableAsync.class, "annotation")) {
bpp.setAsyncAnnotationType(customAsyncAnnotation);
}
@@ -57,9 +55,8 @@ public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {
bpp.setExecutor(this.executor);
}
- bpp.setProxyTargetClass((Boolean) enableAsync.get("proxyTargetClass"));
-
- bpp.setOrder(((Integer) enableAsync.get("order")));
+ bpp.setProxyTargetClass(this.enableAsync.getBoolean("proxyTargetClass"));
+ bpp.setOrder(this.enableAsync.getNumber("order"));
return bpp;
}
diff --git a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ForkJoinPoolFactoryBean.java b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ForkJoinPoolFactoryBean.java
index 488e5008edd..873daf3f8b3 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ForkJoinPoolFactoryBean.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ForkJoinPoolFactoryBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@ import org.springframework.beans.factory.InitializingBean;
* (ideally on the VM bootstrap classpath).
*
* For details on the ForkJoinPool API and its use with RecursiveActions, see the
- * JDK 7 javadoc .
+ * JDK 7 javadoc .
*
*
jsr166.jar, containing java.util.concurrent updates for Java 6, can be obtained
* from the concurrency interest website .
diff --git a/spring-context/src/main/java/org/springframework/validation/AbstractBindingResult.java b/spring-context/src/main/java/org/springframework/validation/AbstractBindingResult.java
index 1cf65266562..7525a3dede9 100644
--- a/spring-context/src/main/java/org/springframework/validation/AbstractBindingResult.java
+++ b/spring-context/src/main/java/org/springframework/validation/AbstractBindingResult.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -120,13 +120,6 @@ public abstract class AbstractBindingResult extends AbstractErrors implements Bi
this.errors.addAll(errors.getAllErrors());
}
- /**
- * Resolve the given error code into message codes.
- * Calls the MessageCodesResolver with appropriate parameters.
- * @param errorCode the error code to resolve into message codes
- * @return the resolved message codes
- * @see #setMessageCodesResolver
- */
public String[] resolveMessageCodes(String errorCode) {
return getMessageCodesResolver().resolveMessageCodes(errorCode, getObjectName());
}
diff --git a/spring-context/src/main/java/org/springframework/validation/BindException.java b/spring-context/src/main/java/org/springframework/validation/BindException.java
index 8c401edaa1c..e7896996b3c 100644
--- a/spring-context/src/main/java/org/springframework/validation/BindException.java
+++ b/spring-context/src/main/java/org/springframework/validation/BindException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2008 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -220,6 +220,10 @@ public class BindException extends Exception implements BindingResult {
this.bindingResult.addError(error);
}
+ public String[] resolveMessageCodes(String errorCode) {
+ return this.bindingResult.resolveMessageCodes(errorCode);
+ }
+
public String[] resolveMessageCodes(String errorCode, String field) {
return this.bindingResult.resolveMessageCodes(errorCode, field);
}
diff --git a/spring-context/src/main/java/org/springframework/validation/BindingResult.java b/spring-context/src/main/java/org/springframework/validation/BindingResult.java
index 848c3e0292b..3069a42a17e 100644
--- a/spring-context/src/main/java/org/springframework/validation/BindingResult.java
+++ b/spring-context/src/main/java/org/springframework/validation/BindingResult.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2009 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -112,6 +112,14 @@ public interface BindingResult extends Errors {
*/
void addError(ObjectError error);
+ /**
+ * Resolve the given error code into message codes.
+ *
Calls the configured {@link MessageCodesResolver} with appropriate parameters.
+ * @param errorCode the error code to resolve into message codes
+ * @return the resolved message codes
+ */
+ String[] resolveMessageCodes(String errorCode);
+
/**
* Resolve the given error code into message codes for the given field.
*
Calls the configured {@link MessageCodesResolver} with appropriate parameters.
diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/LocalValidatorFactoryBean.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/LocalValidatorFactoryBean.java
index bc3bc6b2d12..a885fddb733 100644
--- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/LocalValidatorFactoryBean.java
+++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/LocalValidatorFactoryBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2010 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,7 +28,6 @@ import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorContext;
import javax.validation.ValidatorFactory;
-import javax.validation.spi.ValidationProvider;
import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
@@ -87,7 +86,7 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
* @see javax.validation.Validation#byDefaultProvider()
*/
@SuppressWarnings("rawtypes")
- public void setProviderClass(Class extends ValidationProvider> providerClass) {
+ public void setProviderClass(Class providerClass) {
this.providerClass = providerClass;
}
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 91fd75ab60e..82f1d913909 100644
--- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java
+++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -119,12 +119,11 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation.
// can do custom FieldError registration with invalid value from ConstraintViolation,
// as necessary for Hibernate Validator compatibility (non-indexed set path in field)
BindingResult bindingResult = (BindingResult) errors;
- String[] errorCodes = bindingResult.resolveMessageCodes(errorCode, field);
String nestedField = bindingResult.getNestedPath() + field;
- ObjectError error;
if ("".equals(nestedField)) {
- error = new ObjectError(
- errors.getObjectName(), errorCodes, errorArgs, violation.getMessage());
+ String[] errorCodes = bindingResult.resolveMessageCodes(errorCode);
+ bindingResult.addError(new ObjectError(
+ errors.getObjectName(), errorCodes, errorArgs, violation.getMessage()));
}
else {
Object invalidValue = violation.getInvalidValue();
@@ -132,11 +131,11 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation.
// bean constraint with property path: retrieve the actual property value
invalidValue = bindingResult.getRawFieldValue(field);
}
- error = new FieldError(
+ String[] errorCodes = bindingResult.resolveMessageCodes(errorCode, field);
+ bindingResult.addError(new FieldError(
errors.getObjectName(), nestedField, invalidValue, false,
- errorCodes, errorArgs, violation.getMessage());
+ errorCodes, errorArgs, violation.getMessage()));
}
- bindingResult.addError(error);
}
else {
// got no BindingResult - can only do standard rejectValue call
diff --git a/spring-context/src/test/java/org/springframework/context/annotation/AbstractCircularImportDetectionTests.java b/spring-context/src/test/java/org/springframework/context/annotation/AbstractCircularImportDetectionTests.java
index 1a02ed5297f..57d42c88c8e 100644
--- a/spring-context/src/test/java/org/springframework/context/annotation/AbstractCircularImportDetectionTests.java
+++ b/spring-context/src/test/java/org/springframework/context/annotation/AbstractCircularImportDetectionTests.java
@@ -42,7 +42,7 @@ public abstract class AbstractCircularImportDetectionTests {
public void simpleCircularImportIsDetected() throws Exception {
boolean threw = false;
try {
- newParser().parse(loadAsConfigurationSource(A.class), null);
+ newParser().parse(loadAsConfigurationSource(A.class), "A");
} catch (BeanDefinitionParsingException ex) {
assertTrue("Wrong message. Got: " + ex.getMessage(),
ex.getMessage().contains(
@@ -59,7 +59,7 @@ public abstract class AbstractCircularImportDetectionTests {
public void complexCircularImportIsDetected() throws Exception {
boolean threw = false;
try {
- newParser().parse(loadAsConfigurationSource(X.class), null);
+ newParser().parse(loadAsConfigurationSource(X.class), "X");
}
catch (BeanDefinitionParsingException ex) {
assertTrue("Wrong message. Got: " + ex.getMessage(),
diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ImportAwareTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ImportAwareTests.java
index 6d38318b216..180176452b5 100644
--- a/spring-context/src/test/java/org/springframework/context/annotation/ImportAwareTests.java
+++ b/spring-context/src/test/java/org/springframework/context/annotation/ImportAwareTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,18 +19,19 @@ package org.springframework.context.annotation;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.assertThat;
+import static org.springframework.context.annotation.MetadataUtils.attributesFor;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-import java.util.Map;
import org.junit.Test;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessor;
@@ -55,8 +56,8 @@ public class ImportAwareTests {
AnnotationMetadata importMetadata = importAwareConfig.importMetadata;
assertThat("import metadata was not injected", importMetadata, notNullValue());
assertThat(importMetadata.getClassName(), is(ImportingConfig.class.getName()));
- Map importAttribs = importMetadata.getAnnotationAttributes(Import.class.getName());
- Class>[] importedClasses = (Class>[])importAttribs.get("value");
+ AnnotationAttributes importAttribs = attributesFor(importMetadata, Import.class);
+ Class>[] importedClasses = importAttribs.getClassArray("value");
assertThat(importedClasses[0].getName(), is(ImportedConfig.class.getName()));
}
@@ -72,8 +73,8 @@ public class ImportAwareTests {
AnnotationMetadata importMetadata = importAwareConfig.importMetadata;
assertThat("import metadata was not injected", importMetadata, notNullValue());
assertThat(importMetadata.getClassName(), is(IndirectlyImportingConfig.class.getName()));
- Map enableAttribs = importMetadata.getAnnotationAttributes(EnableImportedConfig.class.getName());
- String foo = (String)enableAttribs.get("foo");
+ AnnotationAttributes enableAttribs = attributesFor(importMetadata, EnableImportedConfig.class);
+ String foo = enableAttribs.getString("foo");
assertThat(foo, is("xyz"));
}
@@ -129,7 +130,6 @@ public class ImportAwareTests {
}
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
- System.out.println("ImportAwareTests.BPP.setBeanFactory()");
}
}
}
diff --git a/spring-context/src/test/java/org/springframework/context/annotation/configuration/ImportTests.java b/spring-context/src/test/java/org/springframework/context/annotation/configuration/ImportTests.java
index 8756425e146..1866ea80405 100644
--- a/spring-context/src/test/java/org/springframework/context/annotation/configuration/ImportTests.java
+++ b/spring-context/src/test/java/org/springframework/context/annotation/configuration/ImportTests.java
@@ -25,6 +25,7 @@ import test.beans.TestBean;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
@@ -53,13 +54,6 @@ public class ImportTests {
assertThat(beanFactory.getBeanDefinitionCount(), equalTo(expectedCount));
}
- @Test
- public void testProcessImports() {
- int configClasses = 2;
- int beansInClasses = 2;
- assertBeanDefinitionCount((configClasses + beansInClasses), ConfigurationWithImportAnnotation.class);
- }
-
@Test
public void testProcessImportsWithAsm() {
int configClasses = 2;
@@ -315,4 +309,36 @@ public class ImportTests {
static class ConfigAnnotated { }
static class NonConfigAnnotated { }
+
+ // ------------------------------------------------------------------------
+
+ /**
+ * Test that values supplied to @Configuration(value="...") are propagated as the
+ * bean name for the configuration class even in the case of inclusion via @Import
+ * or in the case of automatic registration via nesting
+ */
+ @Test
+ public void reproSpr9023() {
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
+ ctx.register(B.class);
+ ctx.refresh();
+ System.out.println(ctx.getBeanFactory());
+ assertThat(ctx.getBeanNamesForType(B.class)[0], is("config-b"));
+ assertThat(ctx.getBeanNamesForType(A.class)[0], is("config-a"));
+ }
+
+ @Configuration("config-a")
+ static class A { }
+
+ @Configuration("config-b")
+ @Import(A.class)
+ static class B { }
+
+ @Test
+ public void testProcessImports() {
+ int configClasses = 2;
+ int beansInClasses = 2;
+ assertBeanDefinitionCount((configClasses + beansInClasses), ConfigurationWithImportAnnotation.class);
+ }
+
}
diff --git a/spring-context/src/test/java/org/springframework/context/annotation/configuration/spr9031/Spr9031Tests.java b/spring-context/src/test/java/org/springframework/context/annotation/configuration/spr9031/Spr9031Tests.java
new file mode 100644
index 00000000000..201918a4329
--- /dev/null
+++ b/spring-context/src/test/java/org/springframework/context/annotation/configuration/spr9031/Spr9031Tests.java
@@ -0,0 +1,79 @@
+/*
+ * 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.context.annotation.configuration.spr9031;
+
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.assertThat;
+
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.ComponentScan.Filter;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.context.annotation.configuration.spr9031.scanpackage.Spr9031Component;
+
+/**
+ * Unit tests cornering bug SPR-9031.
+ *
+ * @author Chris Beams
+ * @since 3.1.1
+ */
+public class Spr9031Tests {
+
+ /**
+ * Use of @Import to register LowLevelConfig results in ASM-based annotation
+ * processing.
+ */
+ @Test
+ public void withAsmAnnotationProcessing() {
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
+ ctx.register(HighLevelConfig.class);
+ ctx.refresh();
+ assertThat(ctx.getBean(LowLevelConfig.class).scanned, not(nullValue()));
+ }
+
+ /**
+ * Direct registration of LowLevelConfig results in reflection-based annotation
+ * processing.
+ */
+ @Test
+ public void withoutAsmAnnotationProcessing() {
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
+ ctx.register(LowLevelConfig.class);
+ ctx.refresh();
+ assertThat(ctx.getBean(LowLevelConfig.class).scanned, not(nullValue()));
+ }
+
+ @Configuration
+ @Import(LowLevelConfig.class)
+ static class HighLevelConfig {}
+
+ @Configuration
+ @ComponentScan(
+ basePackages = "org.springframework.context.annotation.configuration.spr9031.scanpackage",
+ includeFilters = { @Filter(MarkerAnnotation.class) })
+ static class LowLevelConfig {
+ // fails to wire when LowLevelConfig is processed with ASM because nested @Filter
+ // annotation is not parsed
+ @Autowired Spr9031Component scanned;
+ }
+
+ public @interface MarkerAnnotation {}
+}
diff --git a/spring-context/src/test/java/org/springframework/context/annotation/configuration/spr9031/scanpackage/Spr9031Component.java b/spring-context/src/test/java/org/springframework/context/annotation/configuration/spr9031/scanpackage/Spr9031Component.java
new file mode 100644
index 00000000000..1537b4cbd89
--- /dev/null
+++ b/spring-context/src/test/java/org/springframework/context/annotation/configuration/spr9031/scanpackage/Spr9031Component.java
@@ -0,0 +1,24 @@
+/*
+ * 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.context.annotation.configuration.spr9031.scanpackage;
+
+import org.springframework.context.annotation.configuration.spr9031.Spr9031Tests.MarkerAnnotation;
+
+@MarkerAnnotation
+public class Spr9031Component {
+
+}
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 4e69535108d..dbfe0b7d1a8 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2010 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -110,11 +110,22 @@ public class ValidatorFactoryTests {
assertEquals(2, result.getErrorCount());
FieldError fieldError = result.getFieldError("name");
assertEquals("name", fieldError.getField());
- System.out.println(Arrays.asList(fieldError.getCodes()));
+ List errorCodes = Arrays.asList(fieldError.getCodes());
+ assertEquals(4, errorCodes.size());
+ assertTrue(errorCodes.contains("NotNull.person.name"));
+ assertTrue(errorCodes.contains("NotNull.name"));
+ assertTrue(errorCodes.contains("NotNull.java.lang.String"));
+ assertTrue(errorCodes.contains("NotNull"));
System.out.println(fieldError.getDefaultMessage());
fieldError = result.getFieldError("address.street");
assertEquals("address.street", fieldError.getField());
- System.out.println(Arrays.asList(fieldError.getCodes()));
+ errorCodes = Arrays.asList(fieldError.getCodes());
+ assertEquals(5, errorCodes.size());
+ assertTrue(errorCodes.contains("NotNull.person.address.street"));
+ assertTrue(errorCodes.contains("NotNull.address.street"));
+ assertTrue(errorCodes.contains("NotNull.street"));
+ assertTrue(errorCodes.contains("NotNull.java.lang.String"));
+ assertTrue(errorCodes.contains("NotNull"));
System.out.println(fieldError.getDefaultMessage());
}
@@ -129,7 +140,10 @@ public class ValidatorFactoryTests {
validator.validate(person, result);
assertEquals(1, result.getErrorCount());
ObjectError globalError = result.getGlobalError();
- System.out.println(Arrays.asList(globalError.getCodes()));
+ List errorCodes = Arrays.asList(globalError.getCodes());
+ assertEquals(2, errorCodes.size());
+ assertTrue(errorCodes.contains("NameAddressValid.person"));
+ assertTrue(errorCodes.contains("NameAddressValid"));
System.out.println(globalError.getDefaultMessage());
}
diff --git a/spring-core/src/main/java/org/springframework/core/MethodParameter.java b/spring-core/src/main/java/org/springframework/core/MethodParameter.java
index 361bae7eedc..9c33cb25783 100644
--- a/spring-core/src/main/java/org/springframework/core/MethodParameter.java
+++ b/spring-core/src/main/java/org/springframework/core/MethodParameter.java
@@ -21,6 +21,7 @@ import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.HashMap;
@@ -233,6 +234,29 @@ public class MethodParameter {
return this.genericParameterType;
}
+ public Class> getNestedParameterType() {
+ if (this.nestingLevel > 1) {
+ Type type = getGenericParameterType();
+ if (type instanceof ParameterizedType) {
+ Integer index = getTypeIndexForCurrentLevel();
+ Type arg = ((ParameterizedType) type).getActualTypeArguments()[index != null ? index : 0];
+ if (arg instanceof Class) {
+ return (Class) arg;
+ }
+ else if (arg instanceof ParameterizedType) {
+ arg = ((ParameterizedType) arg).getRawType();
+ if (arg instanceof Class) {
+ return (Class) arg;
+ }
+ }
+ }
+ return Object.class;
+ }
+ else {
+ return getParameterType();
+ }
+ }
+
/**
* Return the annotations associated with the target method/constructor itself.
*/
diff --git a/spring-core/src/main/java/org/springframework/core/OrderComparator.java b/spring-core/src/main/java/org/springframework/core/OrderComparator.java
index 829ee4c275c..2cadb0a45bb 100644
--- a/spring-core/src/main/java/org/springframework/core/OrderComparator.java
+++ b/spring-core/src/main/java/org/springframework/core/OrderComparator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2009 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -40,7 +40,7 @@ public class OrderComparator implements Comparator {
/**
* Shared default instance of OrderComparator.
*/
- public static OrderComparator INSTANCE = new OrderComparator();
+ public static final OrderComparator INSTANCE = new OrderComparator();
public int compare(Object o1, Object o2) {
diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java
new file mode 100644
index 00000000000..1ea4ba3a470
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java
@@ -0,0 +1,132 @@
+/*
+ * 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.core.annotation;
+
+import static java.lang.String.format;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.springframework.util.Assert;
+
+/**
+ * {@link LinkedHashMap} subclass representing annotation attribute key/value pairs
+ * as read by Spring's reflection- or ASM-based {@link
+ * org.springframework.core.type.AnnotationMetadata AnnotationMetadata} implementations.
+ * Provides 'pseudo-reification' to avoid noisy Map generics in the calling code as well
+ * as convenience methods for looking up annotation attributes in a type-safe fashion.
+ *
+ * @author Chris Beams
+ * @since 3.1.1
+ */
+@SuppressWarnings("serial")
+public class AnnotationAttributes extends LinkedHashMap {
+
+ /**
+ * Create a new, empty {@link AnnotationAttributes} instance.
+ */
+ public AnnotationAttributes() {
+ }
+
+ /**
+ * Create a new, empty {@link AnnotationAttributes} instance with the given initial
+ * capacity to optimize performance.
+ * @param initialCapacity initial size of the underlying map
+ */
+ public AnnotationAttributes(int initialCapacity) {
+ super(initialCapacity);
+ }
+
+ /**
+ * Create a new {@link AnnotationAttributes} instance, wrapping the provided map
+ * and all its key/value pairs.
+ * @param map original source of annotation attribute key/value pairs to wrap
+ * @see #fromMap(Map)
+ */
+ public AnnotationAttributes(Map map) {
+ super(map);
+ }
+
+ /**
+ * Return an {@link AnnotationAttributes} instance based on the given map; if the map
+ * is already an {@code AnnotationAttributes} instance, it is casted and returned
+ * immediately without creating any new instance; otherwise create a new instance by
+ * wrapping the map with the {@link #AnnotationAttributes(Map)} constructor.
+ * @param map original source of annotation attribute key/value pairs
+ */
+ public static AnnotationAttributes fromMap(Map map) {
+ if (map == null) {
+ return null;
+ }
+
+ if (map instanceof AnnotationAttributes) {
+ return (AnnotationAttributes) map;
+ }
+
+ return new AnnotationAttributes(map);
+ }
+
+ public String getString(String attributeName) {
+ return doGet(attributeName, String.class);
+ }
+
+ public String[] getStringArray(String attributeName) {
+ return doGet(attributeName, String[].class);
+ }
+
+ public boolean getBoolean(String attributeName) {
+ return doGet(attributeName, Boolean.class);
+ }
+
+ @SuppressWarnings("unchecked")
+ public N getNumber(String attributeName) {
+ return (N) doGet(attributeName, Integer.class);
+ }
+
+ @SuppressWarnings("unchecked")
+ public > E getEnum(String attributeName) {
+ return (E) doGet(attributeName, Enum.class);
+ }
+
+ @SuppressWarnings("unchecked")
+ public Class extends T> getClass(String attributeName) {
+ return (Class)doGet(attributeName, Class.class);
+ }
+
+ public Class>[] getClassArray(String attributeName) {
+ return doGet(attributeName, Class[].class);
+ }
+
+ public AnnotationAttributes getAnnotation(String attributeName) {
+ return doGet(attributeName, AnnotationAttributes.class);
+ }
+
+ public AnnotationAttributes[] getAnnotationArray(String attributeName) {
+ return doGet(attributeName, AnnotationAttributes[].class);
+ }
+
+ @SuppressWarnings("unchecked")
+ private T doGet(String attributeName, Class expectedType) {
+ Assert.hasText(attributeName, "attributeName must not be null or empty");
+ Object value = this.get(attributeName);
+ Assert.notNull(value, format("Attribute '%s' not found", attributeName));
+ Assert.isAssignable(expectedType, value.getClass(),
+ format("Attribute '%s' is of type [%s], but [%s] was expected. Cause: ",
+ attributeName, value.getClass().getSimpleName(), expectedType.getSimpleName()));
+ return (T) value;
+ }
+}
\ No newline at end of file
diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java
index fa08d2b2f54..c8ddfe1fc02 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,7 +20,6 @@ import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
@@ -300,25 +299,58 @@ public abstract class AnnotationUtils {
}
/**
- * Retrieve the given annotation's attributes as a Map, preserving all attribute types as-is.
+ * Retrieve the given annotation's attributes as a Map, preserving all attribute types
+ * as-is.
+ * Note: As of Spring 3.1.1, the returned map is actually an
+ * {@link AnnotationAttributes} instance, however the Map signature of this method has
+ * been preserved for binary compatibility.
* @param annotation the annotation to retrieve the attributes for
* @return the Map of annotation attributes, with attribute names as keys and
* corresponding attribute values as values
*/
public static Map getAnnotationAttributes(Annotation annotation) {
- return getAnnotationAttributes(annotation, false);
+ return getAnnotationAttributes(annotation, false, false);
}
/**
- * Retrieve the given annotation's attributes as a Map.
+ * Retrieve the given annotation's attributes as a Map. Equivalent to calling
+ * {@link #getAnnotationAttributes(Annotation, boolean, boolean)} with
+ * the {@code nestedAnnotationsAsMap} parameter set to {@code false}.
+ * Note: As of Spring 3.1.1, the returned map is actually an
+ * {@link AnnotationAttributes} instance, however the Map signature of this method has
+ * been preserved for binary compatibility.
* @param annotation the annotation to retrieve the attributes for
- * @param classValuesAsString whether to turn Class references into Strings (for compatibility with
- * {@link org.springframework.core.type.AnnotationMetadata} or to preserve them as Class references
+ * @param classValuesAsString whether to turn Class references into Strings (for
+ * compatibility with {@link org.springframework.core.type.AnnotationMetadata} or to
+ * preserve them as Class references
* @return the Map of annotation attributes, with attribute names as keys and
* corresponding attribute values as values
*/
public static Map getAnnotationAttributes(Annotation annotation, boolean classValuesAsString) {
- Map attrs = new HashMap();
+ return getAnnotationAttributes(annotation, classValuesAsString, false);
+ }
+
+ /**
+ * Retrieve the given annotation's attributes as an {@link AnnotationAttributes}
+ * map structure. Implemented in Spring 3.1.1 to provide fully recursive annotation
+ * reading capabilities on par with that of the reflection-based
+ * {@link org.springframework.core.type.StandardAnnotationMetadata}.
+ * @param annotation the annotation to retrieve the attributes for
+ * @param classValuesAsString whether to turn Class references into Strings (for
+ * compatibility with {@link org.springframework.core.type.AnnotationMetadata} or to
+ * preserve them as Class references
+ * @param nestedAnnotationsAsMap whether to turn nested Annotation instances into
+ * {@link AnnotationAttributes} maps (for compatibility with
+ * {@link org.springframework.core.type.AnnotationMetadata} or to preserve them as
+ * Annotation instances
+ * @return the annotation attributes (a specialized Map) with attribute names as keys
+ * and corresponding attribute values as values
+ * @since 3.1.1
+ */
+ public static AnnotationAttributes getAnnotationAttributes(
+ Annotation annotation, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
+
+ AnnotationAttributes attrs = new AnnotationAttributes();
Method[] methods = annotation.annotationType().getDeclaredMethods();
for (Method method : methods) {
if (method.getParameterTypes().length == 0 && method.getReturnType() != void.class) {
@@ -337,7 +369,22 @@ public abstract class AnnotationUtils {
value = newValue;
}
}
- attrs.put(method.getName(), value);
+ if (nestedAnnotationsAsMap && value instanceof Annotation) {
+ attrs.put(method.getName(), getAnnotationAttributes(
+ (Annotation)value, classValuesAsString, nestedAnnotationsAsMap));
+ }
+ else if (nestedAnnotationsAsMap && value instanceof Annotation[]) {
+ Annotation[] realAnnotations = (Annotation[])value;
+ AnnotationAttributes[] mappedAnnotations = new AnnotationAttributes[realAnnotations.length];
+ for (int i = 0; i < realAnnotations.length; i++) {
+ mappedAnnotations[i] = getAnnotationAttributes(
+ realAnnotations[i], classValuesAsString, nestedAnnotationsAsMap);
+ }
+ attrs.put(method.getName(), mappedAnnotations);
+ }
+ else {
+ attrs.put(method.getName(), value);
+ }
}
catch (Exception ex) {
throw new IllegalStateException("Could not obtain annotation attribute values", ex);
diff --git a/spring-core/src/main/java/org/springframework/core/convert/Property.java b/spring-core/src/main/java/org/springframework/core/convert/Property.java
index 7cdf0bff801..09a1e6ada07 100644
--- a/spring-core/src/main/java/org/springframework/core/convert/Property.java
+++ b/spring-core/src/main/java/org/springframework/core/convert/Property.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package org.springframework.core.convert;
import java.lang.annotation.Annotation;
@@ -141,13 +142,20 @@ public final class Property {
private MethodParameter resolveMethodParameter() {
MethodParameter read = resolveReadMethodParameter();
MethodParameter write = resolveWriteMethodParameter();
- if (read == null && write == null) {
- throw new IllegalStateException("Property is neither readable nor writeable");
+ if (write == null) {
+ if (read == null) {
+ throw new IllegalStateException("Property is neither readable nor writeable");
+ }
+ return read;
}
- if (read != null && write != null && !write.getParameterType().isAssignableFrom(read.getParameterType())) {
- throw new IllegalStateException("Write parameter is not assignable from read parameter");
+ if (read != null) {
+ Class> readType = read.getParameterType();
+ Class> writeType = write.getParameterType();
+ if (!writeType.equals(readType) && writeType.isAssignableFrom(readType)) {
+ return read;
+ }
}
- return read != null ? read : write;
+ return write;
}
private MethodParameter resolveReadMethodParameter() {
diff --git a/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java b/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java
index ff277afa30d..f2ce7ed6280 100644
--- a/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java
+++ b/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package org.springframework.core.convert;
import java.lang.annotation.Annotation;
@@ -59,6 +60,7 @@ public class TypeDescriptor {
typeDescriptorCache.put(String.class, new TypeDescriptor(String.class));
}
+
private final Class> type;
private final TypeDescriptor elementTypeDescriptor;
@@ -69,6 +71,7 @@ public class TypeDescriptor {
private final Annotation[] annotations;
+
/**
* Create a new type descriptor from a {@link MethodParameter}.
* Use this constructor when a source or target conversion point is a constructor parameter, method parameter, or method return value.
@@ -96,6 +99,7 @@ public class TypeDescriptor {
this(new BeanPropertyDescriptor(property));
}
+
/**
* Create a new type descriptor from the given type.
* Use this to instruct the conversion system to convert an object to a specific target type, when no type location such as a method parameter or field is available to provide additional conversion context.
@@ -207,6 +211,7 @@ public class TypeDescriptor {
return (source != null ? valueOf(source.getClass()) : null);
}
+
/**
* The type of the backing class, method parameter, field, or property described by this TypeDescriptor.
* Returns primitive types as-is.
@@ -477,6 +482,7 @@ public class TypeDescriptor {
private TypeDescriptor(Class> type, TypeDescriptor elementTypeDescriptor, TypeDescriptor mapKeyTypeDescriptor,
TypeDescriptor mapValueTypeDescriptor, Annotation[] annotations) {
+
this.type = type;
this.elementTypeDescriptor = elementTypeDescriptor;
this.mapKeyTypeDescriptor = mapKeyTypeDescriptor;
@@ -538,15 +544,24 @@ public class TypeDescriptor {
return false;
}
TypeDescriptor other = (TypeDescriptor) obj;
- boolean annotatedTypeEquals = ObjectUtils.nullSafeEquals(getType(), other.getType()) && ObjectUtils.nullSafeEquals(getAnnotations(), other.getAnnotations());
- if (!annotatedTypeEquals) {
+ if (!ObjectUtils.nullSafeEquals(getType(), other.getType())) {
return false;
}
+ Annotation[] annotations = getAnnotations();
+ if (annotations.length != other.getAnnotations().length) {
+ return false;
+ }
+ for (Annotation ann : annotations) {
+ if (other.getAnnotation(ann.annotationType()) == null) {
+ return false;
+ }
+ }
if (isCollection() || isArray()) {
return ObjectUtils.nullSafeEquals(getElementTypeDescriptor(), other.getElementTypeDescriptor());
}
else if (isMap()) {
- return ObjectUtils.nullSafeEquals(getMapKeyTypeDescriptor(), other.getMapKeyTypeDescriptor()) && ObjectUtils.nullSafeEquals(getMapValueTypeDescriptor(), other.getMapValueTypeDescriptor());
+ return ObjectUtils.nullSafeEquals(getMapKeyTypeDescriptor(), other.getMapKeyTypeDescriptor()) &&
+ ObjectUtils.nullSafeEquals(getMapValueTypeDescriptor(), other.getMapValueTypeDescriptor());
}
else {
return true;
diff --git a/spring-core/src/main/java/org/springframework/core/io/AbstractResource.java b/spring-core/src/main/java/org/springframework/core/io/AbstractResource.java
index a6b06ab7bb6..2f6c8e7d7dd 100644
--- a/spring-core/src/main/java/org/springframework/core/io/AbstractResource.java
+++ b/spring-core/src/main/java/org/springframework/core/io/AbstractResource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -150,11 +150,11 @@ public abstract class AbstractResource implements Resource {
}
/**
- * This implementation always throws IllegalStateException,
- * assuming that the resource does not have a filename.
+ * This implementation always returns null,
+ * assuming that this resource type does not have a filename.
*/
public String getFilename() throws IllegalStateException {
- throw new IllegalStateException(getDescription() + " does not have a filename");
+ return null;
}
diff --git a/spring-core/src/main/java/org/springframework/core/io/Resource.java b/spring-core/src/main/java/org/springframework/core/io/Resource.java
index aafb1a9dab4..f90b13c7ed3 100644
--- a/spring-core/src/main/java/org/springframework/core/io/Resource.java
+++ b/spring-core/src/main/java/org/springframework/core/io/Resource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -116,8 +116,10 @@ public interface Resource extends InputStreamSource {
Resource createRelative(String relativePath) throws IOException;
/**
- * Return a filename for this resource, i.e. typically the last
+ * Determine a filename for this resource, i.e. typically the last
* part of the path: for example, "myfile.txt".
+ * Returns null if this type of resource does not
+ * have a filename.
*/
String getFilename();
diff --git a/spring-core/src/main/java/org/springframework/core/io/VfsResource.java b/spring-core/src/main/java/org/springframework/core/io/VfsResource.java
index e5450de45bf..c161858ad65 100644
--- a/spring-core/src/main/java/org/springframework/core/io/VfsResource.java
+++ b/spring-core/src/main/java/org/springframework/core/io/VfsResource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2010 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,7 +27,7 @@ import org.springframework.util.Assert;
/**
* VFS based {@link Resource} implementation.
- * Supports the corresponding VFS API versions on JBoss AS 5.x as well as 6.x.
+ * Supports the corresponding VFS API versions on JBoss AS 5.x as well as 6.x and 7.x.
*
* @author Ales Justin
* @author Juergen Hoeller
@@ -47,22 +47,21 @@ public class VfsResource extends AbstractResource {
}
- public boolean exists() {
- return VfsUtils.exists(this.resource);
- }
-
- public boolean isReadable() {
- return VfsUtils.isReadable(this.resource);
- }
-
- public long lastModified() throws IOException {
- return VfsUtils.getLastModified(this.resource);
- }
-
public InputStream getInputStream() throws IOException {
return VfsUtils.getInputStream(this.resource);
}
+ @Override
+ public boolean exists() {
+ return VfsUtils.exists(this.resource);
+ }
+
+ @Override
+ public boolean isReadable() {
+ return VfsUtils.isReadable(this.resource);
+ }
+
+ @Override
public URL getURL() throws IOException {
try {
return VfsUtils.getURL(this.resource);
@@ -72,6 +71,7 @@ public class VfsResource extends AbstractResource {
}
}
+ @Override
public URI getURI() throws IOException {
try {
return VfsUtils.getURI(this.resource);
@@ -81,10 +81,17 @@ public class VfsResource extends AbstractResource {
}
}
+ @Override
public File getFile() throws IOException {
return VfsUtils.getFile(this.resource);
}
+ @Override
+ public long lastModified() throws IOException {
+ return VfsUtils.getLastModified(this.resource);
+ }
+
+ @Override
public Resource createRelative(String relativePath) throws IOException {
if (!relativePath.startsWith(".") && relativePath.contains("/")) {
try {
@@ -98,6 +105,7 @@ public class VfsResource extends AbstractResource {
return new VfsResource(VfsUtils.getRelative(new URL(getURL(), relativePath)));
}
+ @Override
public String getFilename() {
return VfsUtils.getName(this.resource);
}
diff --git a/spring-core/src/main/java/org/springframework/core/io/support/PropertiesLoaderSupport.java b/spring-core/src/main/java/org/springframework/core/io/support/PropertiesLoaderSupport.java
index e4b4147a401..1b427611c4b 100644
--- a/spring-core/src/main/java/org/springframework/core/io/support/PropertiesLoaderSupport.java
+++ b/spring-core/src/main/java/org/springframework/core/io/support/PropertiesLoaderSupport.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2008 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,7 +23,7 @@ import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.springframework.core.io.AbstractFileResolvingResource;
+
import org.springframework.core.io.Resource;
import org.springframework.util.CollectionUtils;
import org.springframework.util.DefaultPropertiesPersister;
@@ -179,9 +179,7 @@ public abstract class PropertiesLoaderSupport {
InputStream is = null;
try {
is = location.getInputStream();
-
- String filename = (location instanceof AbstractFileResolvingResource) ?
- location.getFilename() : null;
+ String filename = location.getFilename();
if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) {
this.propertiesPersister.loadFromXml(props, is);
}
diff --git a/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java b/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java
index 5e413033bdf..1e5d00a9ca9 100644
--- a/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java
+++ b/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2009 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,24 +22,45 @@ import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
+import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils;
/**
* {@link AnnotationMetadata} implementation that uses standard reflection
- * to introspect a given Class.
+ * to introspect a given {@link Class}.
*
* @author Juergen Hoeller
* @author Mark Fisher
+ * @author Chris Beams
* @since 2.5
*/
public class StandardAnnotationMetadata extends StandardClassMetadata implements AnnotationMetadata {
+ private final boolean nestedAnnotationsAsMap;
+
+
/**
- * Create a new StandardAnnotationMetadata wrapper for the given Class.
+ * Create a new {@code StandardAnnotationMetadata} wrapper for the given Class.
* @param introspectedClass the Class to introspect
+ * @see #StandardAnnotationMetadata(Class, boolean)
*/
- public StandardAnnotationMetadata(Class introspectedClass) {
+ public StandardAnnotationMetadata(Class> introspectedClass) {
+ this(introspectedClass, false);
+ }
+
+ /**
+ * Create a new {@link StandardAnnotationMetadata} wrapper for the given Class,
+ * providing the option to return any nested annotations or annotation arrays in the
+ * form of {@link AnnotationAttributes} instead of actual {@link Annotation} instances.
+ * @param introspectedClass the Class to instrospect
+ * @param nestedAnnotationsAsMap return nested annotations and annotation arrays as
+ * {@link AnnotationAttributes} for compatibility with ASM-based
+ * {@link AnnotationMetadata} implementations
+ * @since 3.1.1
+ */
+ public StandardAnnotationMetadata(Class> introspectedClass, boolean nestedAnnotationsAsMap) {
super(introspectedClass);
+ this.nestedAnnotationsAsMap = nestedAnnotationsAsMap;
}
@@ -114,18 +135,20 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements
}
public Map getAnnotationAttributes(String annotationType) {
- return getAnnotationAttributes(annotationType, false);
+ return this.getAnnotationAttributes(annotationType, false);
}
public Map getAnnotationAttributes(String annotationType, boolean classValuesAsString) {
Annotation[] anns = getIntrospectedClass().getAnnotations();
for (Annotation ann : anns) {
if (ann.annotationType().getName().equals(annotationType)) {
- return AnnotationUtils.getAnnotationAttributes(ann, classValuesAsString);
+ return AnnotationUtils.getAnnotationAttributes(
+ ann, classValuesAsString, this.nestedAnnotationsAsMap);
}
for (Annotation metaAnn : ann.annotationType().getAnnotations()) {
if (metaAnn.annotationType().getName().equals(annotationType)) {
- return AnnotationUtils.getAnnotationAttributes(metaAnn, classValuesAsString);
+ return AnnotationUtils.getAnnotationAttributes(
+ metaAnn, classValuesAsString, this.nestedAnnotationsAsMap);
}
}
}
@@ -157,13 +180,13 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements
for (Method method : methods) {
for (Annotation ann : method.getAnnotations()) {
if (ann.annotationType().getName().equals(annotationType)) {
- annotatedMethods.add(new StandardMethodMetadata(method));
+ annotatedMethods.add(new StandardMethodMetadata(method, this.nestedAnnotationsAsMap));
break;
}
else {
for (Annotation metaAnn : ann.annotationType().getAnnotations()) {
if (metaAnn.annotationType().getName().equals(annotationType)) {
- annotatedMethods.add(new StandardMethodMetadata(method));
+ annotatedMethods.add(new StandardMethodMetadata(method, this.nestedAnnotationsAsMap));
break;
}
}
diff --git a/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java b/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java
index ead840cc21b..aca42b9d946 100644
--- a/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java
+++ b/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2009 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -37,14 +37,27 @@ public class StandardMethodMetadata implements MethodMetadata {
private final Method introspectedMethod;
+ private final boolean nestedAnnotationsAsMap;
+
/**
* Create a new StandardMethodMetadata wrapper for the given Method.
* @param introspectedMethod the Method to introspect
*/
public StandardMethodMetadata(Method introspectedMethod) {
+ this(introspectedMethod, false);
+ }
+
+ /**
+ * Create a new StandardMethodMetadata wrapper for the given Method.
+ * @param introspectedMethod the Method to introspect
+ * @param nestedAnnotationsAsMap
+ * @since 3.1.1
+ */
+ public StandardMethodMetadata(Method introspectedMethod, boolean nestedAnnotationsAsMap) {
Assert.notNull(introspectedMethod, "Method must not be null");
this.introspectedMethod = introspectedMethod;
+ this.nestedAnnotationsAsMap = nestedAnnotationsAsMap;
}
/**
@@ -94,11 +107,13 @@ public class StandardMethodMetadata implements MethodMetadata {
Annotation[] anns = this.introspectedMethod.getAnnotations();
for (Annotation ann : anns) {
if (ann.annotationType().getName().equals(annotationType)) {
- return AnnotationUtils.getAnnotationAttributes(ann, true);
+ return AnnotationUtils.getAnnotationAttributes(
+ ann, true, nestedAnnotationsAsMap);
}
for (Annotation metaAnn : ann.annotationType().getAnnotations()) {
if (metaAnn.annotationType().getName().equals(annotationType)) {
- return AnnotationUtils.getAnnotationAttributes(metaAnn, true);
+ return AnnotationUtils.getAnnotationAttributes(
+ metaAnn, true, this.nestedAnnotationsAsMap);
}
}
}
diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java
index 244808b3235..7f48ece4fd5 100644
--- a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java
+++ b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2009 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,131 +20,242 @@ import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
-import java.util.LinkedHashMap;
+import java.util.ArrayList;
import java.util.LinkedHashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
import org.springframework.asm.AnnotationVisitor;
import org.springframework.asm.Type;
-import org.springframework.asm.commons.EmptyVisitor;
+import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
+
/**
- * ASM visitor which looks for the annotations defined on a class or method.
- *
+ * @author Chris Beams
* @author Juergen Hoeller
- * @since 3.0
+ * @since 3.1.1
*/
-final class AnnotationAttributesReadingVisitor implements AnnotationVisitor {
+abstract class AbstractRecursiveAnnotationVisitor implements AnnotationVisitor {
- private final String annotationType;
+ protected final Log logger = LogFactory.getLog(this.getClass());
- private final Map> attributesMap;
+ protected final AnnotationAttributes attributes;
- private final Map> metaAnnotationMap;
-
- private final ClassLoader classLoader;
-
- private final Map localAttributes = new LinkedHashMap();
+ protected final ClassLoader classLoader;
- public AnnotationAttributesReadingVisitor(
- String annotationType, Map> attributesMap,
- Map> metaAnnotationMap, ClassLoader classLoader) {
-
- this.annotationType = annotationType;
- this.attributesMap = attributesMap;
- this.metaAnnotationMap = metaAnnotationMap;
+ public AbstractRecursiveAnnotationVisitor(ClassLoader classLoader, AnnotationAttributes attributes) {
this.classLoader = classLoader;
+ this.attributes = attributes;
}
- public void visit(String name, Object value) {
- this.localAttributes.put(name, value);
+ public void visit(String attributeName, Object attributeValue) {
+ this.attributes.put(attributeName, attributeValue);
}
- public void visitEnum(String name, String desc, String value) {
- Object valueToUse = value;
+ public AnnotationVisitor visitAnnotation(String attributeName, String asmTypeDescriptor) {
+ String annotationType = Type.getType(asmTypeDescriptor).getClassName();
+ AnnotationAttributes nestedAttributes = new AnnotationAttributes();
+ this.attributes.put(attributeName, nestedAttributes);
+ return new RecursiveAnnotationAttributesVisitor(annotationType, nestedAttributes, this.classLoader);
+ }
+
+ public AnnotationVisitor visitArray(String attributeName) {
+ return new RecursiveAnnotationArrayVisitor(attributeName, this.attributes, this.classLoader);
+ }
+
+ public void visitEnum(String attributeName, String asmTypeDescriptor, String attributeValue) {
+ Object valueToUse = attributeValue;
try {
- Class> enumType = this.classLoader.loadClass(Type.getType(desc).getClassName());
- Field enumConstant = ReflectionUtils.findField(enumType, value);
+ Class> enumType = this.classLoader.loadClass(Type.getType(asmTypeDescriptor).getClassName());
+ Field enumConstant = ReflectionUtils.findField(enumType, attributeValue);
if (enumConstant != null) {
valueToUse = enumConstant.get(null);
}
}
catch (Exception ex) {
- // Class not found - can't resolve class reference in annotation attribute.
+ logNonFatalException(ex);
}
- this.localAttributes.put(name, valueToUse);
+ this.attributes.put(attributeName, valueToUse);
}
- public AnnotationVisitor visitAnnotation(String name, String desc) {
- return new EmptyVisitor();
+
+ protected void logNonFatalException(Exception ex) {
+ this.logger.warn("Failed to classload type while reading annotation metadata. " +
+ "This is a non-fatal error, but certain annotation metadata may be " +
+ "unavailable.", ex);
+ }
+}
+
+
+/**
+ * @author Chris Beams
+ * @author Juergen Hoeller
+ * @since 3.1.1
+ */
+final class RecursiveAnnotationArrayVisitor extends AbstractRecursiveAnnotationVisitor {
+
+ private final String attributeName;
+
+ private final List allNestedAttributes = new ArrayList();
+
+
+ public RecursiveAnnotationArrayVisitor(
+ String attributeName, AnnotationAttributes attributes, ClassLoader classLoader) {
+ super(classLoader, attributes);
+ this.attributeName = attributeName;
}
- public AnnotationVisitor visitArray(final String attrName) {
- return new AnnotationVisitor() {
- public void visit(String name, Object value) {
- Object newValue = value;
- Object existingValue = localAttributes.get(attrName);
- if (existingValue != null) {
- newValue = ObjectUtils.addObjectToArray((Object[]) existingValue, newValue);
- }
- else {
- Object[] newArray = (Object[]) Array.newInstance(newValue.getClass(), 1);
- newArray[0] = newValue;
- newValue = newArray;
- }
- localAttributes.put(attrName, newValue);
- }
- public void visitEnum(String name, String desc, String value) {
- }
- public AnnotationVisitor visitAnnotation(String name, String desc) {
- return new EmptyVisitor();
- }
- public AnnotationVisitor visitArray(String name) {
- return new EmptyVisitor();
- }
- public void visitEnd() {
- }
- };
+ @Override
+ public void visit(String attributeName, Object attributeValue) {
+ Object newValue = attributeValue;
+ Object existingValue = this.attributes.get(this.attributeName);
+ if (existingValue != null) {
+ newValue = ObjectUtils.addObjectToArray((Object[]) existingValue, newValue);
+ }
+ else {
+ Object[] newArray = (Object[]) Array.newInstance(newValue.getClass(), 1);
+ newArray[0] = newValue;
+ newValue = newArray;
+ }
+ this.attributes.put(this.attributeName, newValue);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String attributeName, String asmTypeDescriptor) {
+ String annotationType = Type.getType(asmTypeDescriptor).getClassName();
+ AnnotationAttributes nestedAttributes = new AnnotationAttributes();
+ this.allNestedAttributes.add(nestedAttributes);
+ return new RecursiveAnnotationAttributesVisitor(annotationType, nestedAttributes, this.classLoader);
}
public void visitEnd() {
- this.attributesMap.put(this.annotationType, this.localAttributes);
+ if (!this.allNestedAttributes.isEmpty()) {
+ this.attributes.put(this.attributeName, this.allNestedAttributes.toArray(
+ new AnnotationAttributes[this.allNestedAttributes.size()]));
+ }
+ }
+}
+
+
+/**
+ * @author Chris Beams
+ * @author Juergen Hoeller
+ * @since 3.1.1
+ */
+class RecursiveAnnotationAttributesVisitor extends AbstractRecursiveAnnotationVisitor {
+
+ private final String annotationType;
+
+
+ public RecursiveAnnotationAttributesVisitor(
+ String annotationType, AnnotationAttributes attributes, ClassLoader classLoader) {
+ super(classLoader, attributes);
+ this.annotationType = annotationType;
+ }
+
+
+ public final void visitEnd() {
try {
Class> annotationClass = this.classLoader.loadClass(this.annotationType);
- // Check declared default values of attributes in the annotation type.
- Method[] annotationAttributes = annotationClass.getMethods();
- for (Method annotationAttribute : annotationAttributes) {
- String attributeName = annotationAttribute.getName();
- Object defaultValue = annotationAttribute.getDefaultValue();
- if (defaultValue != null && !this.localAttributes.containsKey(attributeName)) {
- this.localAttributes.put(attributeName, defaultValue);
- }
- }
- // Register annotations that the annotation type is annotated with.
- Set metaAnnotationTypeNames = new LinkedHashSet();
- for (Annotation metaAnnotation : annotationClass.getAnnotations()) {
- metaAnnotationTypeNames.add(metaAnnotation.annotationType().getName());
- if (!this.attributesMap.containsKey(metaAnnotation.annotationType().getName())) {
- this.attributesMap.put(metaAnnotation.annotationType().getName(),
- AnnotationUtils.getAnnotationAttributes(metaAnnotation, true));
- }
- for (Annotation metaMetaAnnotation : metaAnnotation.annotationType().getAnnotations()) {
- metaAnnotationTypeNames.add(metaMetaAnnotation.annotationType().getName());
- }
- }
- if (this.metaAnnotationMap != null) {
- this.metaAnnotationMap.put(this.annotationType, metaAnnotationTypeNames);
- }
+ this.doVisitEnd(annotationClass);
}
catch (ClassNotFoundException ex) {
- // Class not found - can't determine meta-annotations.
+ logNonFatalException(ex);
}
}
+ protected void doVisitEnd(Class> annotationClass) {
+ registerDefaultValues(annotationClass);
+ }
+
+ private void registerDefaultValues(Class> annotationClass) {
+ // Check declared default values of attributes in the annotation type.
+ Method[] annotationAttributes = annotationClass.getMethods();
+ for (Method annotationAttribute : annotationAttributes) {
+ String attributeName = annotationAttribute.getName();
+ Object defaultValue = annotationAttribute.getDefaultValue();
+ if (defaultValue != null && !this.attributes.containsKey(attributeName)) {
+ if (defaultValue instanceof Annotation) {
+ defaultValue = AnnotationAttributes.fromMap(
+ AnnotationUtils.getAnnotationAttributes((Annotation)defaultValue, false, true));
+ }
+ else if (defaultValue instanceof Annotation[]) {
+ Annotation[] realAnnotations = (Annotation[]) defaultValue;
+ AnnotationAttributes[] mappedAnnotations = new AnnotationAttributes[realAnnotations.length];
+ for (int i = 0; i < realAnnotations.length; i++) {
+ mappedAnnotations[i] = AnnotationAttributes.fromMap(
+ AnnotationUtils.getAnnotationAttributes(realAnnotations[i], false, true));
+ }
+ defaultValue = mappedAnnotations;
+ }
+ this.attributes.put(attributeName, defaultValue);
+ }
+ }
+ }
}
+
+
+/**
+ * ASM visitor which looks for the annotations defined on a class or method, including
+ * tracking meta-annotations.
+ *
+ * As of Spring 3.1.1, this visitor is fully recursive, taking into account any nested
+ * annotations or nested annotation arrays. These annotations are in turn read into
+ * {@link AnnotationAttributes} map structures.
+ *
+ * @author Juergen Hoeller
+ * @author Chris Beams
+ * @since 3.0
+ */
+final class AnnotationAttributesReadingVisitor extends RecursiveAnnotationAttributesVisitor {
+
+ private final String annotationType;
+
+ private final Map attributesMap;
+
+ private final Map> metaAnnotationMap;
+
+
+ public AnnotationAttributesReadingVisitor(
+ String annotationType, Map attributesMap,
+ Map> metaAnnotationMap, ClassLoader classLoader) {
+
+ super(annotationType, new AnnotationAttributes(), classLoader);
+ this.annotationType = annotationType;
+ this.attributesMap = attributesMap;
+ this.metaAnnotationMap = metaAnnotationMap;
+ }
+
+ @Override
+ public void doVisitEnd(Class> annotationClass) {
+ super.doVisitEnd(annotationClass);
+ this.attributesMap.put(this.annotationType, this.attributes);
+ registerMetaAnnotations(annotationClass);
+ }
+
+ private void registerMetaAnnotations(Class> annotationClass) {
+ // Register annotations that the annotation type is annotated with.
+ Set metaAnnotationTypeNames = new LinkedHashSet();
+ for (Annotation metaAnnotation : annotationClass.getAnnotations()) {
+ metaAnnotationTypeNames.add(metaAnnotation.annotationType().getName());
+ if (!this.attributesMap.containsKey(metaAnnotation.annotationType().getName())) {
+ this.attributesMap.put(metaAnnotation.annotationType().getName(),
+ AnnotationUtils.getAnnotationAttributes(metaAnnotation, true, true));
+ }
+ for (Annotation metaMetaAnnotation : metaAnnotation.annotationType().getAnnotations()) {
+ metaAnnotationTypeNames.add(metaMetaAnnotation.annotationType().getName());
+ }
+ }
+ if (this.metaAnnotationMap != null) {
+ this.metaAnnotationMap.put(annotationClass.getName(), metaAnnotationTypeNames);
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java
index cf76c807c91..97a4897f815 100644
--- a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java
+++ b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@ import java.util.Set;
import org.springframework.asm.AnnotationVisitor;
import org.springframework.asm.MethodVisitor;
import org.springframework.asm.Type;
+import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.MethodMetadata;
import org.springframework.util.CollectionUtils;
@@ -50,7 +51,7 @@ final class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor
private final Map> metaAnnotationMap = new LinkedHashMap>(4);
- private final Map> attributeMap = new LinkedHashMap>(4);
+ private final Map attributeMap = new LinkedHashMap(4);
private final MultiValueMap methodMetadataMap = new LinkedMultiValueMap();
@@ -99,20 +100,41 @@ final class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor
return this.attributeMap.containsKey(annotationType);
}
- public Map getAnnotationAttributes(String annotationType) {
+ public AnnotationAttributes getAnnotationAttributes(String annotationType) {
return getAnnotationAttributes(annotationType, false);
}
- public Map getAnnotationAttributes(String annotationType, boolean classValuesAsString) {
- Map raw = this.attributeMap.get(annotationType);
- if (raw == null) {
+ public AnnotationAttributes getAnnotationAttributes(String annotationType, boolean classValuesAsString) {
+ return getAnnotationAttributes(annotationType, classValuesAsString, false);
+ }
+
+ public AnnotationAttributes getAnnotationAttributes(
+ String annotationType, boolean classValuesAsString, boolean nestedAttributesAsMap) {
+
+ AnnotationAttributes raw = this.attributeMap.get(annotationType);
+ return convertClassValues(raw, classValuesAsString, nestedAttributesAsMap);
+ }
+
+ private AnnotationAttributes convertClassValues(
+ AnnotationAttributes original, boolean classValuesAsString, boolean nestedAttributesAsMap) {
+
+ if (original == null) {
return null;
}
- Map result = new LinkedHashMap(raw.size());
- for (Map.Entry entry : raw.entrySet()) {
+ AnnotationAttributes result = new AnnotationAttributes(original.size());
+ for (Map.Entry entry : original.entrySet()) {
try {
Object value = entry.getValue();
- if (value instanceof Type) {
+ if (value instanceof AnnotationAttributes) {
+ value = convertClassValues((AnnotationAttributes)value, classValuesAsString, nestedAttributesAsMap);
+ }
+ else if (value instanceof AnnotationAttributes[]) {
+ AnnotationAttributes[] values = (AnnotationAttributes[])value;
+ for (int i = 0; i < values.length; i++) {
+ values[i] = convertClassValues(values[i], classValuesAsString, nestedAttributesAsMap);
+ }
+ }
+ else if (value instanceof Type) {
value = (classValuesAsString ? ((Type) value).getClassName() :
this.classLoader.loadClass(((Type) value).getClassName()));
}
@@ -127,10 +149,10 @@ final class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor
}
else if (classValuesAsString) {
if (value instanceof Class) {
- value = ((Class) value).getName();
+ value = ((Class>) value).getName();
}
else if (value instanceof Class[]) {
- Class[] clazzArray = (Class[]) value;
+ Class>[] clazzArray = (Class[]) value;
String[] newValue = new String[clazzArray.length];
for (int i = 0; i < clazzArray.length; i++) {
newValue[i] = clazzArray[i].getName();
diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java
index 1bb40cbbee8..43476425aad 100644
--- a/spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java
+++ b/spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2009 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@ import org.springframework.asm.MethodAdapter;
import org.springframework.asm.Opcodes;
import org.springframework.asm.Type;
import org.springframework.asm.commons.EmptyVisitor;
+import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.MethodMetadata;
import org.springframework.util.MultiValueMap;
@@ -35,6 +36,7 @@ import org.springframework.util.MultiValueMap;
* @author Juergen Hoeller
* @author Mark Pollack
* @author Costin Leau
+ * @author Chris Beams
* @since 3.0
*/
final class MethodMetadataReadingVisitor extends MethodAdapter implements MethodMetadata {
@@ -49,7 +51,7 @@ final class MethodMetadataReadingVisitor extends MethodAdapter implements Method
private final MultiValueMap methodMetadataMap;
- private final Map> attributeMap = new LinkedHashMap>(2);
+ private final Map attributeMap = new LinkedHashMap(2);
public MethodMetadataReadingVisitor(String name, int access, String declaringClassName, ClassLoader classLoader,
MultiValueMap methodMetadataMap) {
@@ -88,7 +90,7 @@ final class MethodMetadataReadingVisitor extends MethodAdapter implements Method
return this.attributeMap.containsKey(annotationType);
}
- public Map getAnnotationAttributes(String annotationType) {
+ public AnnotationAttributes getAnnotationAttributes(String annotationType) {
return this.attributeMap.get(annotationType);
}
diff --git a/spring-core/src/main/java/org/springframework/util/ClassUtils.java b/spring-core/src/main/java/org/springframework/util/ClassUtils.java
index db0524c25cb..cbd6bae5801 100644
--- a/spring-core/src/main/java/org/springframework/util/ClassUtils.java
+++ b/spring-core/src/main/java/org/springframework/util/ClassUtils.java
@@ -22,6 +22,7 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
+import java.security.AccessControlException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@@ -712,6 +713,9 @@ public abstract class ClassUtils {
* Call {@link org.springframework.core.BridgeMethodResolver#findBridgedMethod}
* if bridge method resolution is desirable (e.g. for obtaining metadata from
* the original method definition).
+ * NOTE: Since Spring 3.1.1, if java security settings disallow reflective
+ * access (e.g. calls to {@code Class#getDeclaredMethods} etc, this implementation
+ * will fall back to returning the originally provided method.
* @param method the method to be invoked, which may come from an interface
* @param targetClass the target class for the current invocation.
* May be null or may not even implement the method.
@@ -722,7 +726,12 @@ public abstract class ClassUtils {
Method specificMethod = null;
if (method != null && isOverridable(method, targetClass) &&
targetClass != null && !targetClass.equals(method.getDeclaringClass())) {
- specificMethod = ReflectionUtils.findMethod(targetClass, method.getName(), method.getParameterTypes());
+ try {
+ specificMethod = ReflectionUtils.findMethod(targetClass, method.getName(), method.getParameterTypes());
+ } catch (AccessControlException ex) {
+ // security settings are disallowing reflective access; leave
+ // 'specificMethod' null and fall back to 'method' below
+ }
}
return (specificMethod != null ? specificMethod : method);
}
diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationAttributesTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationAttributesTests.java
new file mode 100644
index 00000000000..487b3399216
--- /dev/null
+++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationAttributesTests.java
@@ -0,0 +1,110 @@
+/*
+ * 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.core.annotation;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link AnnotationAttributes}.
+ *
+ * @author Chris Beams
+ * @since 3.1.1
+ */
+public class AnnotationAttributesTests {
+
+ enum Color { RED, WHITE, BLUE }
+
+ @Test
+ public void testTypeSafeAttributeAccess() {
+ AnnotationAttributes a = new AnnotationAttributes();
+ a.put("name", "dave");
+ a.put("names", new String[] { "dave", "frank", "hal" });
+ a.put("bool1", true);
+ a.put("bool2", false);
+ a.put("color", Color.RED);
+ a.put("clazz", Integer.class);
+ a.put("classes", new Class>[] { Number.class, Short.class, Integer.class });
+ a.put("number", 42);
+ a.put("numbers", new int[] { 42, 43 });
+ AnnotationAttributes anno = new AnnotationAttributes();
+ anno.put("value", 10);
+ anno.put("name", "algernon");
+ a.put("anno", anno);
+ a.put("annoArray", new AnnotationAttributes[] { anno });
+
+ assertThat(a.getString("name"), equalTo("dave"));
+ assertThat(a.getStringArray("names"), equalTo(new String[] { "dave", "frank", "hal" }));
+ assertThat(a.getBoolean("bool1"), equalTo(true));
+ assertThat(a.getBoolean("bool2"), equalTo(false));
+ assertThat(a.getEnum("color"), equalTo(Color.RED));
+ assertTrue(a.getClass("clazz").equals(Integer.class));
+ assertThat(a.getClassArray("classes"), equalTo(new Class[] { Number.class, Short.class, Integer.class }));
+ assertThat(a.getNumber("number"), equalTo(42));
+ assertThat(a.getAnnotation("anno").getNumber("value"), equalTo(10));
+ assertThat(a.getAnnotationArray("annoArray")[0].getString("name"), equalTo("algernon"));
+ }
+
+ @Test
+ public void getEnum_emptyAttributeName() {
+ AnnotationAttributes a = new AnnotationAttributes();
+ a.put("color", "RED");
+ try {
+ a.getEnum("");
+ fail();
+ } catch (IllegalArgumentException ex) {
+ assertThat(ex.getMessage(), equalTo("attributeName must not be null or empty"));
+ }
+ try {
+ a.getEnum(null);
+ fail();
+ } catch (IllegalArgumentException ex) {
+ assertThat(ex.getMessage(), equalTo("attributeName must not be null or empty"));
+ }
+ }
+
+ @Test
+ public void getEnum_notFound() {
+ AnnotationAttributes a = new AnnotationAttributes();
+ a.put("color", "RED");
+ try {
+ a.getEnum("colour");
+ fail();
+ } catch (IllegalArgumentException ex) {
+ assertThat(ex.getMessage(), equalTo("Attribute 'colour' not found"));
+ }
+ }
+
+ @Test
+ public void getEnum_typeMismatch() {
+ AnnotationAttributes a = new AnnotationAttributes();
+ a.put("color", "RED");
+ try {
+ a.getEnum("color");
+ fail();
+ } catch (IllegalArgumentException ex) {
+ String expected =
+ "Attribute 'color' is of type [String], but [Enum] was expected";
+ assertThat(ex.getMessage().substring(0, expected.length()), equalTo(expected));
+ }
+ }
+
+}
diff --git a/spring-core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java b/spring-core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java
index 1b8fcbdb2f9..b9cd47bf170 100644
--- a/spring-core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java
+++ b/spring-core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2009 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,98 +16,203 @@
package org.springframework.core.type;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
import java.io.IOException;
import java.io.Serializable;
+import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-import java.util.Map;
import java.util.Set;
-import junit.framework.TestCase;
+import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import org.springframework.stereotype.Component;
/**
+ * Unit tests demonstrating that the reflection-based {@link StandardAnnotationMetadata}
+ * and ASM-based {@code AnnotationMetadataReadingVisitor} produce identical output.
+ *
* @author Juergen Hoeller
+ * @author Chris Beams
*/
-public class AnnotationMetadataTests extends TestCase {
+public class AnnotationMetadataTests {
+ @Test
public void testStandardAnnotationMetadata() throws IOException {
- StandardAnnotationMetadata annInfo = new StandardAnnotationMetadata(AnnotatedComponent.class);
- doTestAnnotationInfo(annInfo);
- doTestMethodAnnotationInfo(annInfo);
+ AnnotationMetadata metadata = new StandardAnnotationMetadata(AnnotatedComponent.class, true);
+ doTestAnnotationInfo(metadata);
+ doTestMethodAnnotationInfo(metadata);
}
+ @Test
public void testAsmAnnotationMetadata() throws IOException {
MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory();
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(AnnotatedComponent.class.getName());
- doTestAnnotationInfo(metadataReader.getAnnotationMetadata());
- doTestMethodAnnotationInfo(metadataReader.getAnnotationMetadata());
+ AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
+ doTestAnnotationInfo(metadata);
+ doTestMethodAnnotationInfo(metadata);
+ }
+
+ /**
+ * In order to preserve backward-compatibility, {@link StandardAnnotationMetadata}
+ * defaults to return nested annotations and annotation arrays as actual
+ * Annotation instances. It is recommended for compatibility with ASM-based
+ * AnnotationMetadata implementations to set the 'nestedAnnotationsAsMap' flag to
+ * 'true' as is done in the main test above.
+ */
+ @Test
+ public void testStandardAnnotationMetadata_nestedAnnotationsAsMap_false() throws IOException {
+ AnnotationMetadata metadata = new StandardAnnotationMetadata(AnnotatedComponent.class);
+
+ AnnotationAttributes specialAttrs = (AnnotationAttributes) metadata.getAnnotationAttributes(SpecialAttr.class.getName());
+ Annotation[] nestedAnnoArray = (Annotation[])specialAttrs.get("nestedAnnoArray");
+ assertThat(nestedAnnoArray[0], instanceOf(NestedAnno.class));
}
private void doTestAnnotationInfo(AnnotationMetadata metadata) {
- assertEquals(AnnotatedComponent.class.getName(), metadata.getClassName());
- assertFalse(metadata.isInterface());
- assertFalse(metadata.isAbstract());
- assertTrue(metadata.isConcrete());
- assertTrue(metadata.hasSuperClass());
- assertEquals(Object.class.getName(), metadata.getSuperClassName());
- assertEquals(1, metadata.getInterfaceNames().length);
- assertEquals(Serializable.class.getName(), metadata.getInterfaceNames()[0]);
+ assertThat(metadata.getClassName(), is(AnnotatedComponent.class.getName()));
+ assertThat(metadata.isInterface(), is(false));
+ assertThat(metadata.isAbstract(), is(false));
+ assertThat(metadata.isConcrete(), is(true));
+ assertThat(metadata.hasSuperClass(), is(true));
+ assertThat(metadata.getSuperClassName(), is(Object.class.getName()));
+ assertThat(metadata.getInterfaceNames().length, is(1));
+ assertThat(metadata.getInterfaceNames()[0], is(Serializable.class.getName()));
- assertTrue(metadata.hasAnnotation(Component.class.getName()));
- assertTrue(metadata.hasAnnotation(Scope.class.getName()));
- assertTrue(metadata.hasAnnotation(SpecialAttr.class.getName()));
- assertEquals(3, metadata.getAnnotationTypes().size());
- assertTrue(metadata.getAnnotationTypes().contains(Component.class.getName()));
- assertTrue(metadata.getAnnotationTypes().contains(Scope.class.getName()));
- assertTrue(metadata.getAnnotationTypes().contains(SpecialAttr.class.getName()));
+ assertThat(metadata.hasAnnotation(Component.class.getName()), is(true));
+ assertThat(metadata.hasAnnotation(Scope.class.getName()), is(true));
+ assertThat(metadata.hasAnnotation(SpecialAttr.class.getName()), is(true));
+ assertThat(metadata.getAnnotationTypes().size(), is(3));
+ assertThat(metadata.getAnnotationTypes().contains(Component.class.getName()), is(true));
+ assertThat(metadata.getAnnotationTypes().contains(Scope.class.getName()), is(true));
+ assertThat(metadata.getAnnotationTypes().contains(SpecialAttr.class.getName()), is(true));
- Map compAttrs = metadata.getAnnotationAttributes(Component.class.getName());
- assertEquals(1, compAttrs.size());
- assertEquals("myName", compAttrs.get("value"));
- Map scopeAttrs = metadata.getAnnotationAttributes(Scope.class.getName());
- assertEquals(1, scopeAttrs.size());
- assertEquals("myScope", scopeAttrs.get("value"));
- Map specialAttrs = metadata.getAnnotationAttributes(SpecialAttr.class.getName());
- assertEquals(2, specialAttrs.size());
- assertEquals(String.class, specialAttrs.get("clazz"));
- assertEquals(Thread.State.NEW, specialAttrs.get("state"));
- Map specialAttrsString = metadata.getAnnotationAttributes(SpecialAttr.class.getName(), true);
- assertEquals(String.class.getName(), specialAttrsString .get("clazz"));
- assertEquals(Thread.State.NEW, specialAttrsString.get("state"));
+ AnnotationAttributes compAttrs = (AnnotationAttributes) metadata.getAnnotationAttributes(Component.class.getName());
+ assertThat(compAttrs.size(), is(1));
+ assertThat(compAttrs.getString("value"), is("myName"));
+ AnnotationAttributes scopeAttrs = (AnnotationAttributes) metadata.getAnnotationAttributes(Scope.class.getName());
+ assertThat(scopeAttrs.size(), is(1));
+ assertThat(scopeAttrs.getString("value"), is("myScope"));
+ { // perform tests with classValuesAsString = false (the default)
+ AnnotationAttributes specialAttrs = (AnnotationAttributes) metadata.getAnnotationAttributes(SpecialAttr.class.getName());
+ assertThat(specialAttrs.size(), is(6));
+ assertTrue(String.class.isAssignableFrom(specialAttrs.getClass("clazz")));
+ assertTrue(specialAttrs.getEnum("state").equals(Thread.State.NEW));
+
+ AnnotationAttributes nestedAnno = specialAttrs.getAnnotation("nestedAnno");
+ assertThat("na", is(nestedAnno.getString("value")));
+ assertTrue(nestedAnno.getEnum("anEnum").equals(SomeEnum.LABEL1));
+ assertArrayEquals(new Class[]{String.class}, (Class[])nestedAnno.get("classArray"));
+
+ AnnotationAttributes[] nestedAnnoArray = specialAttrs.getAnnotationArray("nestedAnnoArray");
+ assertThat(nestedAnnoArray.length, is(2));
+ assertThat(nestedAnnoArray[0].getString("value"), is("default"));
+ assertTrue(nestedAnnoArray[0].getEnum("anEnum").equals(SomeEnum.DEFAULT));
+ assertArrayEquals(new Class[]{Void.class}, (Class[])nestedAnnoArray[0].get("classArray"));
+ assertThat(nestedAnnoArray[1].getString("value"), is("na1"));
+ assertTrue(nestedAnnoArray[1].getEnum("anEnum").equals(SomeEnum.LABEL2));
+ assertArrayEquals(new Class[]{Number.class}, (Class[])nestedAnnoArray[1].get("classArray"));
+ assertArrayEquals(new Class[]{Number.class}, nestedAnnoArray[1].getClassArray("classArray"));
+
+ AnnotationAttributes optional = specialAttrs.getAnnotation("optional");
+ assertThat(optional.getString("value"), is("optional"));
+ assertTrue(optional.getEnum("anEnum").equals(SomeEnum.DEFAULT));
+ assertArrayEquals(new Class[]{Void.class}, (Class[])optional.get("classArray"));
+ assertArrayEquals(new Class[]{Void.class}, optional.getClassArray("classArray"));
+
+ AnnotationAttributes[] optionalArray = specialAttrs.getAnnotationArray("optionalArray");
+ assertThat(optionalArray.length, is(1));
+ assertThat(optionalArray[0].getString("value"), is("optional"));
+ assertTrue(optionalArray[0].getEnum("anEnum").equals(SomeEnum.DEFAULT));
+ assertArrayEquals(new Class[]{Void.class}, (Class[])optionalArray[0].get("classArray"));
+ assertArrayEquals(new Class[]{Void.class}, optionalArray[0].getClassArray("classArray"));
+ }
+ { // perform tests with classValuesAsString = true
+ AnnotationAttributes specialAttrs = (AnnotationAttributes) metadata.getAnnotationAttributes(SpecialAttr.class.getName(), true);
+ assertThat(specialAttrs.size(), is(6));
+ assertThat(specialAttrs.get("clazz"), is((Object)String.class.getName()));
+ assertThat(specialAttrs.getString("clazz"), is(String.class.getName()));
+
+ AnnotationAttributes nestedAnno = specialAttrs.getAnnotation("nestedAnno");
+ assertArrayEquals(new String[]{String.class.getName()}, (String[])nestedAnno.getStringArray("classArray"));
+ assertArrayEquals(new String[]{String.class.getName()}, nestedAnno.getStringArray("classArray"));
+
+ AnnotationAttributes[] nestedAnnoArray = specialAttrs.getAnnotationArray("nestedAnnoArray");
+ assertArrayEquals(new String[]{Void.class.getName()}, (String[])nestedAnnoArray[0].get("classArray"));
+ assertArrayEquals(new String[]{Void.class.getName()}, nestedAnnoArray[0].getStringArray("classArray"));
+ assertArrayEquals(new String[]{Number.class.getName()}, (String[])nestedAnnoArray[1].get("classArray"));
+ assertArrayEquals(new String[]{Number.class.getName()}, nestedAnnoArray[1].getStringArray("classArray"));
+
+ AnnotationAttributes optional = specialAttrs.getAnnotation("optional");
+ assertArrayEquals(new String[]{Void.class.getName()}, (String[])optional.get("classArray"));
+ assertArrayEquals(new String[]{Void.class.getName()}, optional.getStringArray("classArray"));
+
+ AnnotationAttributes[] optionalArray = specialAttrs.getAnnotationArray("optionalArray");
+ assertArrayEquals(new String[]{Void.class.getName()}, (String[])optionalArray[0].get("classArray"));
+ assertArrayEquals(new String[]{Void.class.getName()}, optionalArray[0].getStringArray("classArray"));
+ }
}
private void doTestMethodAnnotationInfo(AnnotationMetadata classMetadata) {
Set methods = classMetadata.getAnnotatedMethods(Autowired.class.getName());
- assertEquals(1, methods.size());
+ assertThat(methods.size(), is(1));
for (MethodMetadata methodMetadata : methods) {
- assertTrue(methodMetadata.isAnnotated(Autowired.class.getName()));
+ assertThat(methodMetadata.isAnnotated(Autowired.class.getName()), is(true));
}
-
}
+ public static enum SomeEnum {
+ LABEL1, LABEL2, DEFAULT;
+ }
+
+ @Target({})
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface NestedAnno {
+ String value() default "default";
+ SomeEnum anEnum() default SomeEnum.DEFAULT;
+ Class>[] classArray() default Void.class;
+ }
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SpecialAttr {
- Class clazz();
+ Class> clazz();
Thread.State state();
+
+ NestedAnno nestedAnno();
+
+ NestedAnno[] nestedAnnoArray();
+
+ NestedAnno optional() default @NestedAnno(value="optional", anEnum=SomeEnum.DEFAULT, classArray=Void.class);
+
+ NestedAnno[] optionalArray() default {@NestedAnno(value="optional", anEnum=SomeEnum.DEFAULT, classArray=Void.class)};
}
@Component("myName")
@Scope("myScope")
- @SpecialAttr(clazz = String.class, state = Thread.State.NEW)
+ @SpecialAttr(clazz = String.class, state = Thread.State.NEW,
+ nestedAnno = @NestedAnno(value = "na", anEnum = SomeEnum.LABEL1, classArray = {String.class}),
+ nestedAnnoArray = {
+ @NestedAnno,
+ @NestedAnno(value = "na1", anEnum = SomeEnum.LABEL2, classArray = {Number.class})
+ })
+ @SuppressWarnings({"serial", "unused"})
private static class AnnotatedComponent implements Serializable {
@Autowired
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 d40daa3cbae..33d1f510b17 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-2010 the original author or authors.
+ * Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -38,27 +38,30 @@ import org.springframework.expression.spel.SpelMessage;
import org.springframework.util.CollectionUtils;
/**
- * A method resolver that uses reflection to locate the method that should be invoked.
+ * Reflection-based {@link MethodResolver} used by default in
+ * {@link StandardEvaluationContext} unless explicit method resolvers have been specified.
*
* @author Andy Clement
* @author Juergen Hoeller
+ * @author Chris Beams
* @since 3.0
+ * @see StandardEvaluationContext#addMethodResolver(MethodResolver)
*/
public class ReflectiveMethodResolver implements MethodResolver {
private static Method[] NO_METHODS = new Method[0];
private Map, MethodFilter> filters = null;
-
+
// Using distance will ensure a more accurate match is discovered,
// more closely following the Java rules.
private boolean useDistance = false;
-
+
public ReflectiveMethodResolver() {
-
+
}
-
+
/**
* This constructors allows the ReflectiveMethodResolver to be configured such that it will
* use a distance computation to check which is the better of two close matches (when there
@@ -71,7 +74,7 @@ public class ReflectiveMethodResolver implements MethodResolver {
public ReflectiveMethodResolver(boolean useDistance) {
this.useDistance = useDistance;
}
-
+
/**
* Locate a method on a type. There are three kinds of match that might occur:
*
@@ -87,15 +90,15 @@ public class ReflectiveMethodResolver implements MethodResolver {
try {
TypeConverter typeConverter = context.getTypeConverter();
Class> type = (targetObject instanceof Class ? (Class>) targetObject : targetObject.getClass());
- Method[] methods = type.getMethods();
-
+ Method[] methods = getMethods(type);
+
// 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 methodsForFiltering = new ArrayList();
+ for (Method method: methods) {
+ methodsForFiltering.add(method);
+ }
List methodsFiltered = filter.filter(methodsForFiltering);
if (CollectionUtils.isEmpty(methodsFiltered)) {
methods = NO_METHODS;
@@ -124,7 +127,7 @@ public class ReflectiveMethodResolver implements MethodResolver {
continue;
}
if (method.getName().equals(name)) {
- Class[] paramTypes = method.getParameterTypes();
+ Class>[] paramTypes = method.getParameterTypes();
List paramDescriptors = new ArrayList(paramTypes.length);
for (int i = 0; i < paramTypes.length; i++) {
paramDescriptors.add(new TypeDescriptor(new MethodParameter(method, i)));
@@ -194,4 +197,16 @@ public class ReflectiveMethodResolver implements MethodResolver {
}
}
+ /**
+ * Return the set of methods for this type. The default implementation returns the
+ * result of Class#getMethods for the given {@code type}, but subclasses may override
+ * in order to alter the results, e.g. specifying static methods declared elsewhere.
+ *
+ * @param type the class for which to return the methods
+ * @since 3.1.1
+ */
+ protected Method[] getMethods(Class> type) {
+ return type.getMethods();
+ }
+
}
diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SpringEL300Tests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SpringEL300Tests.java
index c947e27e025..0fa4d1fbd1d 100644
--- a/spring-expression/src/test/java/org/springframework/expression/spel/SpringEL300Tests.java
+++ b/spring-expression/src/test/java/org/springframework/expression/spel/SpringEL300Tests.java
@@ -17,8 +17,10 @@
package org.springframework.expression.spel;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
import java.lang.reflect.Field;
+import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
@@ -38,6 +40,7 @@ import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.MethodExecutor;
+import org.springframework.expression.MethodResolver;
import org.springframework.expression.ParserContext;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.TypedValue;
@@ -50,12 +53,12 @@ import org.springframework.expression.spel.support.StandardTypeLocator;
/**
* Tests based on Jiras up to the release of Spring 3.0.0
- *
+ *
* @author Andy Clement
* @author Clark Duplichien
*/
public class SpringEL300Tests extends ExpressionTestCase {
-
+
@Test
public void testNPE_SPR5661() {
evaluate("joinThreeStrings('a',null,'c')", "anullc", String.class);
@@ -66,7 +69,7 @@ public class SpringEL300Tests extends ExpressionTestCase {
public void testSWF1086() {
evaluate("printDouble(T(java.math.BigDecimal).valueOf(14.35))", "anullc", String.class);
}
-
+
@Test
public void testDoubleCoercion() {
evaluate("printDouble(14.35)", "14.35", String.class);
@@ -92,15 +95,15 @@ public class SpringEL300Tests extends ExpressionTestCase {
// success
}
eContext.setTypeLocator(new MyTypeLocator());
-
+
// varargs
expr = new SpelExpressionParser().parseRaw("tryToInvokeWithNull3(null,'a','b')");
Assert.assertEquals("ab",expr.getValue(eContext));
-
+
// varargs 2 - null is packed into the varargs
expr = new SpelExpressionParser().parseRaw("tryToInvokeWithNull3(12,'a',null,'c')");
Assert.assertEquals("anullc",expr.getValue(eContext));
-
+
// check we can find the ctor ok
expr = new SpelExpressionParser().parseRaw("new Spr5899Class().toString()");
Assert.assertEquals("instance",expr.getValue(eContext));
@@ -116,7 +119,7 @@ public class SpringEL300Tests extends ExpressionTestCase {
expr = new SpelExpressionParser().parseRaw("new Spr5899Class(null,'a', null, 'b').toString()");
Assert.assertEquals("instance",expr.getValue(eContext));
}
-
+
static class MyTypeLocator extends StandardTypeLocator {
public Class> findType(String typename) throws EvaluationException {
@@ -132,12 +135,12 @@ public class SpringEL300Tests extends ExpressionTestCase {
static class Spr5899Class {
public Spr5899Class() {}
- public Spr5899Class(Integer i) { }
- public Spr5899Class(Integer i, String... s) { }
-
+ public Spr5899Class(Integer i) { }
+ public Spr5899Class(Integer i, String... s) { }
+
public Integer tryToInvokeWithNull(Integer value) { return value; }
public Integer tryToInvokeWithNull2(int i) { return new Integer(i); }
- public String tryToInvokeWithNull3(Integer value,String... strings) {
+ public String tryToInvokeWithNull3(Integer value,String... strings) {
StringBuilder sb = new StringBuilder();
for (int i=0;i m = new HashMap();
m.put("foo", "bar");
StandardEvaluationContext eContext = new StandardEvaluationContext(m); // root is a map instance
eContext.addPropertyAccessor(new MapAccessor());
Expression expr = new SpelExpressionParser().parseRaw("['foo']");
Assert.assertEquals("bar", expr.getValue(eContext));
}
-
+
@Test
public void testSPR5847() throws Exception {
StandardEvaluationContext eContext = new StandardEvaluationContext(new TestProperties());
String name = null;
Expression expr = null;
-
+
expr = new SpelExpressionParser().parseRaw("jdbcProperties['username']");
name = expr.getValue(eContext,String.class);
Assert.assertEquals("Dave",name);
-
+
expr = new SpelExpressionParser().parseRaw("jdbcProperties[username]");
name = expr.getValue(eContext,String.class);
Assert.assertEquals("Dave",name);
@@ -209,7 +211,7 @@ public class SpringEL300Tests extends ExpressionTestCase {
eContext.addPropertyAccessor(new MapAccessor());
name = expr.getValue(eContext,String.class);
Assert.assertEquals("Dave",name);
-
+
// --- dotted property names
// lookup foo on the root, then bar on that, then use that as the key into jdbcProperties
@@ -222,9 +224,9 @@ public class SpringEL300Tests extends ExpressionTestCase {
expr = new SpelExpressionParser().parseRaw("jdbcProperties['foo.bar']");
eContext.addPropertyAccessor(new MapAccessor());
name = expr.getValue(eContext,String.class);
- Assert.assertEquals("Elephant",name);
+ Assert.assertEquals("Elephant",name);
}
-
+
static class TestProperties {
public Properties jdbcProperties = new Properties();
public Properties foo = new Properties();
@@ -239,11 +241,11 @@ public class SpringEL300Tests extends ExpressionTestCase {
static class MapAccessor implements PropertyAccessor {
public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
- return (((Map) target).containsKey(name));
+ return (((Map,?>) target).containsKey(name));
}
public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
- return new TypedValue(((Map) target).get(name));
+ return new TypedValue(((Map,?>) target).get(name));
}
public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException {
@@ -252,22 +254,22 @@ public class SpringEL300Tests extends ExpressionTestCase {
@SuppressWarnings("unchecked")
public void write(EvaluationContext context, Object target, String name, Object newValue) throws AccessException {
- ((Map) target).put(name, newValue);
+ ((Map) target).put(name, newValue);
}
- public Class[] getSpecificTargetClasses() {
+ public Class>[] getSpecificTargetClasses() {
return new Class[] {Map.class};
}
-
+
}
-
+
@Test
public void testNPE_SPR5673() throws Exception {
ParserContext hashes = TemplateExpressionParsingTests.HASH_DELIMITED_PARSER_CONTEXT;
ParserContext dollars = TemplateExpressionParsingTests.DEFAULT_TEMPLATE_PARSER_CONTEXT;
-
+
checkTemplateParsing("abc${'def'} ghi","abcdef ghi");
-
+
checkTemplateParsingError("abc${ {}( 'abc'","Missing closing ')' for '(' at position 8");
checkTemplateParsingError("abc${ {}[ 'abc'","Missing closing ']' for '[' at position 8");
checkTemplateParsingError("abc${ {}{ 'abc'","Missing closing '}' for '{' at position 8");
@@ -278,7 +280,7 @@ public class SpringEL300Tests extends ExpressionTestCase {
checkTemplateParsingError("abc${ ] }","Found closing ']' at position 6 without an opening '['");
checkTemplateParsingError("abc${ } }","No expression defined within delimiter '${}' at character 3");
checkTemplateParsingError("abc$[ } ]",DOLLARSQUARE_TEMPLATE_PARSER_CONTEXT,"Found closing '}' at position 6 without an opening '{'");
-
+
checkTemplateParsing("abc ${\"def''g}hi\"} jkl","abc def'g}hi jkl");
checkTemplateParsing("abc ${'def''g}hi'} jkl","abc def'g}hi jkl");
checkTemplateParsing("}","}");
@@ -295,7 +297,7 @@ public class SpringEL300Tests extends ExpressionTestCase {
checkTemplateParsing("Hello ${'inner literal that''s got {[(])]}an escaped quote in it'} World","Hello inner literal that's got {[(])]}an escaped quote in it World");
checkTemplateParsingError("Hello ${","No ending suffix '}' for expression starting at character 6: ${");
}
-
+
@Test
public void testAccessingNullPropertyViaReflection_SPR5663() throws AccessException {
PropertyAccessor propertyAccessor = new ReflectivePropertyAccessor();
@@ -315,33 +317,33 @@ public class SpringEL300Tests extends ExpressionTestCase {
// success
}
}
-
+
@Test
public void testNestedProperties_SPR6923() {
StandardEvaluationContext eContext = new StandardEvaluationContext(new Foo());
String name = null;
Expression expr = null;
-
+
expr = new SpelExpressionParser().parseRaw("resource.resource.server");
name = expr.getValue(eContext,String.class);
Assert.assertEquals("abc",name);
}
-
+
static class Foo {
public ResourceSummary resource = new ResourceSummary();
}
-
+
static class ResourceSummary {
ResourceSummary() {
this.resource = new Resource();
}
private final Resource resource;
public Resource getResource() {
- return resource;
- }
+ return resource;
+ }
}
-
+
static class Resource {
public String getServer() {
return "abc";
@@ -374,9 +376,8 @@ public class SpringEL300Tests extends ExpressionTestCase {
name = expr.getValue(eContext,String.class); // will be using the cached accessor this time
Assert.assertEquals("hello",name);
}
-
+
/** $ related identifiers */
- @SuppressWarnings("unchecked")
@Test
public void testDollarPrefixedIdentifier_SPR7100() {
Holder h = new Holder();
@@ -390,7 +391,7 @@ public class SpringEL300Tests extends ExpressionTestCase {
h.map.put("$_$","tribble");
String name = null;
Expression expr = null;
-
+
expr = new SpelExpressionParser().parseRaw("map.$foo");
name = expr.getValue(eContext,String.class);
Assert.assertEquals("wibble",name);
@@ -430,8 +431,11 @@ public class SpringEL300Tests extends ExpressionTestCase {
name = expr.getValue(eContext,String.class); // will be using the cached accessor this time
Assert.assertEquals("wobble",name);
}
-
- /** Should be accessing (setting) Goo.wibble field because 'bar' variable evaluates to "wibble" */
+
+ /**
+ * Should be accessing (setting) Goo.wibble field because 'bar' variable evaluates to
+ * "wibble"
+ */
@Test
public void testIndexingAsAPropertyAccess_SPR6968_4() {
Goo g = Goo.instance;
@@ -458,7 +462,7 @@ public class SpringEL300Tests extends ExpressionTestCase {
expr.getValue(eContext,String.class); // will be using the cached accessor this time
Assert.assertEquals("world",g.value);
}
-
+
@Test
public void testDollars() {
StandardEvaluationContext eContext = new StandardEvaluationContext(new XX());
@@ -467,7 +471,7 @@ public class SpringEL300Tests extends ExpressionTestCase {
eContext.setVariable("file_name","$foo");
Assert.assertEquals("wibble",expr.getValue(eContext,String.class));
}
-
+
@Test
public void testDollars2() {
StandardEvaluationContext eContext = new StandardEvaluationContext(new XX());
@@ -476,12 +480,12 @@ public class SpringEL300Tests extends ExpressionTestCase {
eContext.setVariable("file_name","$foo");
Assert.assertEquals("wibble",expr.getValue(eContext,String.class));
}
-
+
static class XX {
public Map m;
-
+
public String floo ="bar";
-
+
public XX() {
m = new HashMap();
m.put("$foo","wibble");
@@ -490,34 +494,34 @@ public class SpringEL300Tests extends ExpressionTestCase {
}
static class Goo {
-
+
public static Goo instance = new Goo();
public String bar = "key";
public String value = null;
public String wibble = "wobble";
-
+
public String getKey() {
return "hello";
}
-
+
public void setKey(String s) {
value = s;
}
-
+
}
-
+
static class Holder {
-
- public Map map = new HashMap();
+
+ public Map map = new HashMap();
}
-
+
// ---
private void checkTemplateParsing(String expression, String expectedValue) throws Exception {
checkTemplateParsing(expression,TemplateExpressionParsingTests.DEFAULT_TEMPLATE_PARSER_CONTEXT, expectedValue);
}
-
+
private void checkTemplateParsing(String expression, ParserContext context, String expectedValue) throws Exception {
SpelExpressionParser parser = new SpelExpressionParser();
Expression expr = parser.parseExpression(expression,context);
@@ -527,7 +531,7 @@ public class SpringEL300Tests extends ExpressionTestCase {
private void checkTemplateParsingError(String expression,String expectedMessage) throws Exception {
checkTemplateParsingError(expression, TemplateExpressionParsingTests.DEFAULT_TEMPLATE_PARSER_CONTEXT,expectedMessage);
}
-
+
private void checkTemplateParsingError(String expression,ParserContext context, String expectedMessage) throws Exception {
SpelExpressionParser parser = new SpelExpressionParser();
try {
@@ -540,7 +544,7 @@ public class SpringEL300Tests extends ExpressionTestCase {
Assert.assertEquals(expectedMessage,e.getMessage());
}
}
-
+
private static final ParserContext DOLLARSQUARE_TEMPLATE_PARSER_CONTEXT = new ParserContext() {
public String getExpressionPrefix() {
return "$[";
@@ -552,28 +556,13 @@ public class SpringEL300Tests extends ExpressionTestCase {
return true;
}
};
-
-// @Test
-// public void testFails() {
-//
-// StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
-// evaluationContext.setVariable("target", new Foo2());
-// for (int i = 0; i < 300000; i++) {
-// evaluationContext.addPropertyAccessor(new MapAccessor());
-// ExpressionParser parser = new SpelExpressionParser();
-// Expression expression = parser.parseExpression("#target.execute(payload)");
-// Message message = new Message();
-// message.setPayload(i+"");
-// expression.getValue(evaluationContext, message);
-// }
-// }
-
+
static class Foo2 {
public void execute(String str){
System.out.println("Value: " + str);
}
}
-
+
static class Message{
private String payload;
@@ -587,7 +576,7 @@ public class SpringEL300Tests extends ExpressionTestCase {
}
// bean resolver tests
-
+
@Test
public void beanResolution() {
StandardEvaluationContext eContext = new StandardEvaluationContext(new XX());
@@ -601,7 +590,7 @@ public class SpringEL300Tests extends ExpressionTestCase {
Assert.assertEquals(SpelMessage.NO_BEAN_RESOLVER_REGISTERED,see.getMessageCode());
Assert.assertEquals("foo",see.getInserts()[0]);
}
-
+
eContext.setBeanResolver(new MyBeanResolver());
// bean exists
@@ -622,11 +611,11 @@ public class SpringEL300Tests extends ExpressionTestCase {
Assert.assertTrue(see.getCause() instanceof AccessException);
Assert.assertTrue(((AccessException)see.getCause()).getMessage().startsWith("DONT"));
}
-
+
// bean exists
expr = new SpelExpressionParser().parseRaw("@'foo.bar'");
Assert.assertEquals("trouble",expr.getValue(eContext,String.class));
-
+
// bean exists
try {
expr = new SpelExpressionParser().parseRaw("@378");
@@ -635,7 +624,7 @@ public class SpringEL300Tests extends ExpressionTestCase {
Assert.assertEquals(SpelMessage.INVALID_BEAN_REFERENCE,spe.getMessageCode());
}
}
-
+
static class MyBeanResolver implements BeanResolver {
public Object resolve(EvaluationContext context, String beanname) throws AccessException {
if (beanname.equals("foo")) {
@@ -648,14 +637,14 @@ public class SpringEL300Tests extends ExpressionTestCase {
return null;
}
}
-
+
// end bean resolver tests
@Test
public void elvis_SPR7209_1() {
StandardEvaluationContext eContext = new StandardEvaluationContext(new XX());
Expression expr = null;
-
+
// Different parts of elvis expression are null
expr = new SpelExpressionParser().parseRaw("(?:'default')");
Assert.assertEquals("default", expr.getValue());
@@ -696,68 +685,67 @@ public class SpringEL300Tests extends ExpressionTestCase {
expr = new SpelExpressionParser().parseRaw("''?:'default'");
Assert.assertEquals("default", expr.getValue());
}
-
- @Test
- @SuppressWarnings("unchecked")
- public void testMapOfMap_SPR7244() throws Exception {
- Map map = new LinkedHashMap();
- map.put("uri", "http:");
- Map nameMap = new LinkedHashMap();
- nameMap.put("givenName", "Arthur");
- map.put("value", nameMap);
-
- StandardEvaluationContext ctx = new StandardEvaluationContext(map);
- ExpressionParser parser = new SpelExpressionParser();
- String el1 = "#root['value'].get('givenName')";
- Expression exp = parser.parseExpression(el1);
- Object evaluated = exp.getValue(ctx);
- Assert.assertEquals("Arthur", evaluated);
- String el2 = "#root['value']['givenName']";
- exp = parser.parseExpression(el2);
- evaluated = exp.getValue(ctx);
- Assert.assertEquals("Arthur",evaluated);
- }
-
+ @Test
+ public void testMapOfMap_SPR7244() throws Exception {
+ Map map = new LinkedHashMap();
+ map.put("uri", "http:");
+ Map nameMap = new LinkedHashMap();
+ nameMap.put("givenName", "Arthur");
+ map.put("value", nameMap);
+
+ StandardEvaluationContext ctx = new StandardEvaluationContext(map);
+ ExpressionParser parser = new SpelExpressionParser();
+ String el1 = "#root['value'].get('givenName')";
+ Expression exp = parser.parseExpression(el1);
+ Object evaluated = exp.getValue(ctx);
+ Assert.assertEquals("Arthur", evaluated);
+
+ String el2 = "#root['value']['givenName']";
+ exp = parser.parseExpression(el2);
+ evaluated = exp.getValue(ctx);
+ Assert.assertEquals("Arthur",evaluated);
+ }
+
@Test
public void testProjectionTypeDescriptors_1() throws Exception {
- StandardEvaluationContext ctx = new StandardEvaluationContext(new C());
- SpelExpressionParser parser = new SpelExpressionParser();
- String el1 = "ls.![#this.equals('abc')]";
- SpelExpression exp = parser.parseRaw(el1);
- List value = (List)exp.getValue(ctx);
- // value is list containing [true,false]
- Assert.assertEquals(Boolean.class,value.get(0).getClass());
- TypeDescriptor evaluated = exp.getValueTypeDescriptor(ctx);
- Assert.assertEquals(null, evaluated.getElementTypeDescriptor());
+ StandardEvaluationContext ctx = new StandardEvaluationContext(new C());
+ SpelExpressionParser parser = new SpelExpressionParser();
+ String el1 = "ls.![#this.equals('abc')]";
+ SpelExpression exp = parser.parseRaw(el1);
+ List> value = (List>)exp.getValue(ctx);
+ // value is list containing [true,false]
+ Assert.assertEquals(Boolean.class,value.get(0).getClass());
+ TypeDescriptor evaluated = exp.getValueTypeDescriptor(ctx);
+ Assert.assertEquals(null, evaluated.getElementTypeDescriptor());
}
-
+
@Test
public void testProjectionTypeDescriptors_2() throws Exception {
- StandardEvaluationContext ctx = new StandardEvaluationContext(new C());
- SpelExpressionParser parser = new SpelExpressionParser();
- String el1 = "as.![#this.equals('abc')]";
- SpelExpression exp = parser.parseRaw(el1);
- Object[] value = (Object[])exp.getValue(ctx);
- // value is array containing [true,false]
- Assert.assertEquals(Boolean.class,value[0].getClass());
- TypeDescriptor evaluated = exp.getValueTypeDescriptor(ctx);
- Assert.assertEquals(Boolean.class, evaluated.getElementTypeDescriptor().getType());
+ StandardEvaluationContext ctx = new StandardEvaluationContext(new C());
+ SpelExpressionParser parser = new SpelExpressionParser();
+ String el1 = "as.![#this.equals('abc')]";
+ SpelExpression exp = parser.parseRaw(el1);
+ Object[] value = (Object[])exp.getValue(ctx);
+ // value is array containing [true,false]
+ Assert.assertEquals(Boolean.class,value[0].getClass());
+ TypeDescriptor evaluated = exp.getValueTypeDescriptor(ctx);
+ Assert.assertEquals(Boolean.class, evaluated.getElementTypeDescriptor().getType());
}
-
+
@Test
public void testProjectionTypeDescriptors_3() throws Exception {
- StandardEvaluationContext ctx = new StandardEvaluationContext(new C());
- SpelExpressionParser parser = new SpelExpressionParser();
- String el1 = "ms.![key.equals('abc')]";
- SpelExpression exp = parser.parseRaw(el1);
- List value = (List)exp.getValue(ctx);
- // value is list containing [true,false]
- Assert.assertEquals(Boolean.class,value.get(0).getClass());
- TypeDescriptor evaluated = exp.getValueTypeDescriptor(ctx);
- Assert.assertEquals(null, evaluated.getElementTypeDescriptor());
+ StandardEvaluationContext ctx = new StandardEvaluationContext(new C());
+ SpelExpressionParser parser = new SpelExpressionParser();
+ String el1 = "ms.![key.equals('abc')]";
+ SpelExpression exp = parser.parseRaw(el1);
+ List> value = (List>)exp.getValue(ctx);
+ // value is list containing [true,false]
+ Assert.assertEquals(Boolean.class,value.get(0).getClass());
+ TypeDescriptor evaluated = exp.getValueTypeDescriptor(ctx);
+ Assert.assertEquals(null, evaluated.getElementTypeDescriptor());
}
-
+
static class C {
public List ls;
public String[] as;
@@ -772,19 +760,19 @@ public class SpringEL300Tests extends ExpressionTestCase {
ms.put("def","pqr");
}
}
-
+
static class D {
public String a;
-
+
private D(String s) {
a=s;
}
-
+
public String toString() {
return "D("+a+")";
}
}
-
+
@Test
public void testGreaterThanWithNulls_SPR7840() throws Exception {
List list = new ArrayList();
@@ -794,30 +782,31 @@ public class SpringEL300Tests extends ExpressionTestCase {
list.add(new D("ccc"));
list.add(new D(null));
list.add(new D("zzz"));
-
- StandardEvaluationContext ctx = new StandardEvaluationContext(list);
- SpelExpressionParser parser = new SpelExpressionParser();
- String el1 = "#root.?[a < 'hhh']";
- SpelExpression exp = parser.parseRaw(el1);
- Object value = exp.getValue(ctx);
- assertEquals("[D(aaa), D(bbb), D(null), D(ccc), D(null)]",value.toString());
+ StandardEvaluationContext ctx = new StandardEvaluationContext(list);
+ SpelExpressionParser parser = new SpelExpressionParser();
- String el2 = "#root.?[a > 'hhh']";
- SpelExpression exp2 = parser.parseRaw(el2);
- Object value2 = exp2.getValue(ctx);
- assertEquals("[D(zzz)]",value2.toString());
-
- // trim out the nulls first
- String el3 = "#root.?[a!=null].?[a < 'hhh']";
- SpelExpression exp3 = parser.parseRaw(el3);
- Object value3 = exp3.getValue(ctx);
- assertEquals("[D(aaa), D(bbb), D(ccc)]",value3.toString());
+ String el1 = "#root.?[a < 'hhh']";
+ SpelExpression exp = parser.parseRaw(el1);
+ Object value = exp.getValue(ctx);
+ assertEquals("[D(aaa), D(bbb), D(null), D(ccc), D(null)]",value.toString());
+
+ String el2 = "#root.?[a > 'hhh']";
+ SpelExpression exp2 = parser.parseRaw(el2);
+ Object value2 = exp2.getValue(ctx);
+ assertEquals("[D(zzz)]",value2.toString());
+
+ // trim out the nulls first
+ String el3 = "#root.?[a!=null].?[a < 'hhh']";
+ SpelExpression exp3 = parser.parseRaw(el3);
+ Object value3 = exp3.getValue(ctx);
+ assertEquals("[D(aaa), D(bbb), D(ccc)]",value3.toString());
}
/**
- * Test whether {@link ReflectiveMethodResolver} follows Java Method Invocation Conversion order. And more precisely
- * that widening reference conversion is 'higher' than a unboxing conversion.
+ * Test whether {@link ReflectiveMethodResolver} follows Java Method Invocation
+ * Conversion order. And more precisely that widening reference conversion is 'higher'
+ * than a unboxing conversion.
*/
@Test
public void testConversionPriority_8224() throws Exception {
@@ -904,16 +893,16 @@ public class SpringEL300Tests extends ExpressionTestCase {
}
-
+
@Test
public void varargsAndPrimitives_SPR8174() throws Exception {
EvaluationContext emptyEvalContext = new StandardEvaluationContext();
List args = new ArrayList();
-
+
args.add(TypeDescriptor.forObject(34L));
ReflectionUtil ru = new ReflectionUtil();
MethodExecutor me = new ReflectiveMethodResolver().resolve(emptyEvalContext,ru,"methodToCall",args);
-
+
args.set(0,TypeDescriptor.forObject(23));
me = new ReflectiveMethodResolver().resolve(emptyEvalContext,ru,"foo",args);
me.execute(emptyEvalContext, ru, 45);
@@ -941,127 +930,126 @@ public class SpringEL300Tests extends ExpressionTestCase {
args.set(0,TypeDescriptor.forObject((byte)23));
me = new ReflectiveMethodResolver().resolve(emptyEvalContext,ru,"foo",args);
me.execute(emptyEvalContext, ru, (byte)23);
-
+
args.set(0,TypeDescriptor.forObject(true));
me = new ReflectiveMethodResolver().resolve(emptyEvalContext,ru,"foo",args);
me.execute(emptyEvalContext, ru, true);
-
+
// trickier:
args.set(0,TypeDescriptor.forObject(12));
args.add(TypeDescriptor.forObject(23f));
me = new ReflectiveMethodResolver().resolve(emptyEvalContext,ru,"bar",args);
me.execute(emptyEvalContext, ru, 12,23f);
-
+
}
-
-
-
+
+
+
public class ReflectionUtil {
- public Object methodToCall(T param) {
- System.out.println(param+" "+param.getClass());
- return "Object methodToCall(T param)";
- }
-
- public void foo(int... array) {
- if (array.length==0) {
- throw new RuntimeException();
- }
- }
- public void foo(float...array) {
- if (array.length==0) {
- throw new RuntimeException();
- }
- }
- public void foo(double...array) {
- if (array.length==0) {
- throw new RuntimeException();
- }
- }
- public void foo(short...array) {
- if (array.length==0) {
- throw new RuntimeException();
- }
- }
- public void foo(long...array) {
- if (array.length==0) {
- throw new RuntimeException();
- }
- }
- public void foo(boolean...array) {
- if (array.length==0) {
- throw new RuntimeException();
- }
- }
- public void foo(char...array) {
- if (array.length==0) {
- throw new RuntimeException();
- }
- }
- public void foo(byte...array) {
- if (array.length==0) {
- throw new RuntimeException();
- }
- }
-
- public void bar(int... array) {
- if (array.length==0) {
- throw new RuntimeException();
- }
- }
- }
-
+ public Object methodToCall(T param) {
+ System.out.println(param+" "+param.getClass());
+ return "Object methodToCall(T param)";
+ }
+
+ public void foo(int... array) {
+ if (array.length==0) {
+ throw new RuntimeException();
+ }
+ }
+ public void foo(float...array) {
+ if (array.length==0) {
+ throw new RuntimeException();
+ }
+ }
+ public void foo(double...array) {
+ if (array.length==0) {
+ throw new RuntimeException();
+ }
+ }
+ public void foo(short...array) {
+ if (array.length==0) {
+ throw new RuntimeException();
+ }
+ }
+ public void foo(long...array) {
+ if (array.length==0) {
+ throw new RuntimeException();
+ }
+ }
+ public void foo(boolean...array) {
+ if (array.length==0) {
+ throw new RuntimeException();
+ }
+ }
+ public void foo(char...array) {
+ if (array.length==0) {
+ throw new RuntimeException();
+ }
+ }
+ public void foo(byte...array) {
+ if (array.length==0) {
+ throw new RuntimeException();
+ }
+ }
+
+ public void bar(int... array) {
+ if (array.length==0) {
+ throw new RuntimeException();
+ }
+ }
+ }
+
@Test
public void testReservedWords_8228() throws Exception {
// "DIV","EQ","GE","GT","LE","LT","MOD","NE","NOT"
- @SuppressWarnings("unused")
- class Reserver {
- public Reserver getReserver() {
- return this;
- }
- public String NE = "abc";
- public String ne = "def";
-
- public int DIV = 1;
- public int div = 3;
-
- public Map m = new HashMap();
-
- @SuppressWarnings("unchecked")
+ @SuppressWarnings("unused")
+ class Reserver {
+ public Reserver getReserver() {
+ return this;
+ }
+ public String NE = "abc";
+ public String ne = "def";
+
+ public int DIV = 1;
+ public int div = 3;
+
+ public Map m = new HashMap();
+
Reserver() {
- m.put("NE","xyz");
- }
- }
- StandardEvaluationContext ctx = new StandardEvaluationContext(new Reserver());
- SpelExpressionParser parser = new SpelExpressionParser();
- String ex = "getReserver().NE";
- SpelExpression exp = null;
- exp = parser.parseRaw(ex);
- String value = (String)exp.getValue(ctx);
- Assert.assertEquals("abc",value);
+ m.put("NE","xyz");
+ }
+ }
+ StandardEvaluationContext ctx = new StandardEvaluationContext(new Reserver());
+ SpelExpressionParser parser = new SpelExpressionParser();
+ String ex = "getReserver().NE";
+ SpelExpression exp = null;
+ exp = parser.parseRaw(ex);
+ String value = (String)exp.getValue(ctx);
+ Assert.assertEquals("abc",value);
- ex = "getReserver().ne";
- exp = parser.parseRaw(ex);
- value = (String)exp.getValue(ctx);
- Assert.assertEquals("def",value);
+ ex = "getReserver().ne";
+ exp = parser.parseRaw(ex);
+ value = (String)exp.getValue(ctx);
+ Assert.assertEquals("def",value);
- ex = "getReserver().m[NE]";
- exp = parser.parseRaw(ex);
- value = (String)exp.getValue(ctx);
- Assert.assertEquals("xyz",value);
-
- ex = "getReserver().DIV";
- exp = parser.parseRaw(ex);
- Assert.assertEquals(1,exp.getValue(ctx));
+ ex = "getReserver().m[NE]";
+ exp = parser.parseRaw(ex);
+ value = (String)exp.getValue(ctx);
+ Assert.assertEquals("xyz",value);
- ex = "getReserver().div";
- exp = parser.parseRaw(ex);
- Assert.assertEquals(3,exp.getValue(ctx));
-
- exp = parser.parseRaw("NE");
- Assert.assertEquals("abc",exp.getValue(ctx));
+ ex = "getReserver().DIV";
+ exp = parser.parseRaw(ex);
+ Assert.assertEquals(1,exp.getValue(ctx));
+
+ ex = "getReserver().div";
+ exp = parser.parseRaw(ex);
+ Assert.assertEquals(3,exp.getValue(ctx));
+
+ exp = parser.parseRaw("NE");
+ Assert.assertEquals("abc",exp.getValue(ctx));
}
-
+
/**
* We add property accessors in the order:
* First, Second, Third, Fourth.
@@ -1071,39 +1059,24 @@ public class SpringEL300Tests extends ExpressionTestCase {
@Test
public void testPropertyAccessorOrder_8211() {
ExpressionParser expressionParser = new SpelExpressionParser();
- StandardEvaluationContext evaluationContext =
+ StandardEvaluationContext evaluationContext =
new StandardEvaluationContext(new ContextObject());
-
+
evaluationContext.addPropertyAccessor(new TestPropertyAccessor("firstContext"));
evaluationContext.addPropertyAccessor(new TestPropertyAccessor("secondContext"));
evaluationContext.addPropertyAccessor(new TestPropertyAccessor("thirdContext"));
evaluationContext.addPropertyAccessor(new TestPropertyAccessor("fourthContext"));
-
- assertEquals("first",
+
+ assertEquals("first",
expressionParser.parseExpression("shouldBeFirst").getValue(evaluationContext));
- assertEquals("second",
+ assertEquals("second",
expressionParser.parseExpression("shouldBeSecond").getValue(evaluationContext));
- assertEquals("third",
+ assertEquals("third",
expressionParser.parseExpression("shouldBeThird").getValue(evaluationContext));
- assertEquals("fourth",
+ assertEquals("fourth",
expressionParser.parseExpression("shouldBeFourth").getValue(evaluationContext));
}
-
- // test not quite complete, it doesn't check that the list of resolvers at the end of
- // PropertyOrFieldReference.getPropertyAccessorsToTry() doesn't contain duplicates, which
- // is what it is trying to test by having a property accessor that returns specific
- // classes Integer and Number
-// @Test
-// public void testPropertyAccessorOrder_8211_2() {
-// ExpressionParser expressionParser = new SpelExpressionParser();
-// StandardEvaluationContext evaluationContext =
-// new StandardEvaluationContext(new Integer(42));
-//
-// evaluationContext.addPropertyAccessor(new TestPropertyAccessor2());
-//
-// assertEquals("42", expressionParser.parseExpression("x").getValue(evaluationContext));
-// }
-
+
class TestPropertyAccessor implements PropertyAccessor {
private String mapName;
public TestPropertyAccessor(String mapName) {
@@ -1139,38 +1112,13 @@ public class SpringEL300Tests extends ExpressionTestCase {
}
}
-
-// class TestPropertyAccessor2 implements PropertyAccessor {
-//
-// public Class[] getSpecificTargetClasses() {
-// return new Class[]{Integer.class,Number.class};
-// }
-//
-// public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
-// return true;
-// }
-//
-// public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
-// return new TypedValue(target.toString(),TypeDescriptor.valueOf(String.class));
-// }
-//
-// public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException {
-// return false;
-// }
-//
-// public void write(EvaluationContext context, Object target, String name, Object newValue)
-// throws AccessException {
-// throw new UnsupportedOperationException();
-// }
-//
-// }
class ContextObject {
public Map firstContext = new HashMap();
public Map secondContext = new HashMap();
public Map thirdContext = new HashMap();
public Map fourthContext = new HashMap();
-
+
public ContextObject() {
firstContext.put("shouldBeFirst", "first");
secondContext.put("shouldBeFirst", "second");
@@ -1180,10 +1128,10 @@ public class SpringEL300Tests extends ExpressionTestCase {
secondContext.put("shouldBeSecond", "second");
thirdContext.put("shouldBeSecond", "third");
fourthContext.put("shouldBeSecond", "fourth");
-
+
thirdContext.put("shouldBeThird", "third");
fourthContext.put("shouldBeThird", "fourth");
-
+
fourthContext.put("shouldBeFourth", "fourth");
}
public Map getFirstContext() {return firstContext;}
@@ -1192,6 +1140,41 @@ public class SpringEL300Tests extends ExpressionTestCase {
public Map getFourthContext() {return fourthContext;}
}
+ /**
+ * Test the ability to subclass the ReflectiveMethodResolver and change how it
+ * determines the set of methods for a type.
+ */
+ @Test
+ public void testCustomStaticFunctions_SPR9038() {
+ try {
+ ExpressionParser parser = new SpelExpressionParser();
+ StandardEvaluationContext context = new StandardEvaluationContext();
+ List methodResolvers = new ArrayList();
+ methodResolvers.add(new ReflectiveMethodResolver() {
+ @Override
+ protected Method[] getMethods(Class> type) {
+ try {
+ return new Method[] {
+ Integer.class.getDeclaredMethod("parseInt", new Class[] {
+ String.class, Integer.TYPE }) };
+ } catch (NoSuchMethodException e1) {
+ return new Method[0];
+ }
+ }
+ });
+
+ context.setMethodResolvers(methodResolvers);
+ org.springframework.expression.Expression expression =
+ parser.parseExpression("parseInt('-FF', 16)");
+
+ Integer result = expression.getValue(context, "", Integer.class);
+ assertEquals("Equal assertion failed: ", -255, result.intValue());
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail("Unexpected exception: "+e.toString());
+ }
+ }
+
}
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/GenericTableMetaDataProvider.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/GenericTableMetaDataProvider.java
index fc160ec8b2c..1a4e28772fc 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/GenericTableMetaDataProvider.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/GenericTableMetaDataProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -181,8 +181,13 @@ public class GenericTableMetaDataProvider implements TableMetaDataProvider {
setGeneratedKeysColumnNameArraySupported(false);
}
else {
- logger.debug("GeneratedKeysColumnNameArray is supported for " + databaseProductName);
- setGeneratedKeysColumnNameArraySupported(true);
+ if (isGetGeneratedKeysSupported()) {
+ logger.debug("GeneratedKeysColumnNameArray is supported for " + databaseProductName);
+ setGeneratedKeysColumnNameArraySupported(true);
+ }
+ else {
+ setGeneratedKeysColumnNameArraySupported(false);
+ }
}
}
catch (SQLException se) {
@@ -307,7 +312,7 @@ public class GenericTableMetaDataProvider implements TableMetaDataProvider {
tmd.setTableName(tables.getString("TABLE_NAME"));
tmd.setType(tables.getString("TABLE_TYPE"));
if (tmd.getSchemaName() == null) {
- tableMeta.put(userName.toUpperCase(), tmd);
+ tableMeta.put(userName != null ? userName.toUpperCase() : "", tmd);
}
else {
tableMeta.put(tmd.getSchemaName().toUpperCase(), tmd);
@@ -335,7 +340,7 @@ public class GenericTableMetaDataProvider implements TableMetaDataProvider {
if (schemaName == null) {
tmd = tableMeta.get(getDefaultSchema());
if (tmd == null) {
- tmd = tableMeta.get(userName.toUpperCase());
+ tmd = tableMeta.get(userName != null ? userName.toUpperCase() : "");
}
if (tmd == null) {
tmd = tableMeta.get("PUBLIC");
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterUtils.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterUtils.java
index 1ab1f86bff7..ccc668502c5 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterUtils.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -85,12 +85,18 @@ public abstract class NamedParameterUtils {
int escapes = 0;
int i = 0;
while (i < statement.length) {
- int skipToPosition = skipCommentsAndQuotes(statement, i);
- if (i != skipToPosition) {
- if (skipToPosition >= statement.length) {
+ int skipToPosition = i;
+ while (i < statement.length) {
+ skipToPosition = skipCommentsAndQuotes(statement, i);
+ if (i == skipToPosition) {
break;
}
- i = skipToPosition;
+ else {
+ i = skipToPosition;
+ }
+ }
+ if (i >= statement.length) {
+ break;
}
char c = statement[i];
if (c == ':' || c == '&') {
@@ -252,6 +258,9 @@ public abstract class NamedParameterUtils {
actualSql.append(originalSql.substring(lastIndex, startIndex));
if (paramSource != null && paramSource.hasValue(paramName)) {
Object value = paramSource.getValue(paramName);
+ if (value instanceof SqlParameterValue) {
+ value = ((SqlParameterValue) value).getValue();
+ }
if (value instanceof Collection) {
Iterator entryIter = ((Collection) value).iterator();
int k = 0;
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ResourceDatabasePopulator.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ResourceDatabasePopulator.java
index 3da2b77fd72..b33a9d008a0 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ResourceDatabasePopulator.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ResourceDatabasePopulator.java
@@ -181,9 +181,10 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
for (String statement : statements) {
lineNumber++;
try {
- int rowsAffected = stmt.executeUpdate(statement);
+ stmt.execute(statement);
+ int rowsAffected = stmt.getUpdateCount();
if (logger.isDebugEnabled()) {
- logger.debug(rowsAffected + " rows affected by SQL: " + statement);
+ logger.debug(rowsAffected + " returned as updateCount for SQL: " + statement);
}
}
catch (SQLException ex) {
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/CustomSQLExceptionTranslatorRegistrar.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/CustomSQLExceptionTranslatorRegistrar.java
new file mode 100644
index 00000000000..e9806b8c0bc
--- /dev/null
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/CustomSQLExceptionTranslatorRegistrar.java
@@ -0,0 +1,68 @@
+/*
+ * 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.jdbc.support;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.beans.factory.InitializingBean;
+
+/**
+ * Registry for registering custom {@link org.springframework.jdbc.support.SQLExceptionTranslator}.
+ *
+ * @author Thomas Risberg
+ * @since 3.1
+ */
+public class CustomSQLExceptionTranslatorRegistrar implements InitializingBean {
+
+ private static final Log logger = LogFactory.getLog(CustomSQLExceptionTranslatorRegistrar.class);
+
+ /**
+ * Map registry to hold custom translators specific databases.
+ * Key is the database product name as defined in the
+ * {@link org.springframework.jdbc.support.SQLErrorCodesFactory}.
+ */
+ private final Map sqlExceptionTranslators =
+ new HashMap();
+
+ /**
+ * Setter for a Map of translators where the key must be the database name as defined in the
+ * sql-error-codes.xml file. This method is used when this registry is used in an application context.
+ * Note that any existing translators will remain unless there is a match in the database name at which
+ * point the new translator will replace the existing one.
+ *
+ * @param sqlExceptionTranslators
+ */
+ public void setSqlExceptionTranslators(Map sqlExceptionTranslators) {
+ this.sqlExceptionTranslators.putAll(sqlExceptionTranslators);
+ }
+
+ public void afterPropertiesSet() throws Exception {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Registering custom SQL exception translators for database(s): " +
+ sqlExceptionTranslators.keySet());
+ }
+ for (String dbName : sqlExceptionTranslators.keySet()) {
+ CustomSQLExceptionTranslatorRegistry.getInstance()
+ .registerSqlExceptionTranslator(dbName, sqlExceptionTranslators.get(dbName));
+ }
+ }
+}
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/CustomSQLExceptionTranslatorRegistry.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/CustomSQLExceptionTranslatorRegistry.java
new file mode 100644
index 00000000000..89db6b89023
--- /dev/null
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/CustomSQLExceptionTranslatorRegistry.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.support;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.WeakHashMap;
+import javax.sql.DataSource;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+import org.springframework.util.Assert;
+import org.springframework.util.PatternMatchUtils;
+
+/**
+ * Registry for custom {@link org.springframework.jdbc.support.SQLExceptionTranslator} instances associated with
+ * specific databases allowing for overriding translation based on values contained in the configuration file
+ * named "sql-error-codes.xml".
+ *
+ * @author Thomas Risberg
+ * @since 3.1
+ * @see SQLErrorCodesFactory
+ */
+public class CustomSQLExceptionTranslatorRegistry {
+
+ private static final Log logger = LogFactory.getLog(CustomSQLExceptionTranslatorRegistry.class);
+
+ /**
+ * Map registry to hold custom translators specific databases.
+ * Key is the database product name as defined in the
+ * {@link org.springframework.jdbc.support.SQLErrorCodesFactory}.
+ */
+ private final Map sqlExceptionTranslatorRegistry =
+ new HashMap();
+
+
+ /**
+ * Keep track of a single instance so we can return it to classes that request it.
+ */
+ private static final CustomSQLExceptionTranslatorRegistry instance = new CustomSQLExceptionTranslatorRegistry();
+
+
+ /**
+ * Return the singleton instance.
+ */
+ public static CustomSQLExceptionTranslatorRegistry getInstance() {
+ return instance;
+ }
+
+
+ /**
+ * Create a new instance of the {@link org.springframework.jdbc.support.CustomSQLExceptionTranslatorRegistry} class.
+ * Not public to enforce Singleton design pattern.
+ */
+ private CustomSQLExceptionTranslatorRegistry() {
+ }
+
+ /**
+ * Register a new custom translator for the specified database name.
+ *
+ * @param dbName the database name
+ * @param sqlExceptionTranslator the custom translator
+ */
+ public void registerSqlExceptionTranslator(String dbName, SQLExceptionTranslator sqlExceptionTranslator) {
+ SQLExceptionTranslator replaced = sqlExceptionTranslatorRegistry.put(dbName, sqlExceptionTranslator);
+ if (replaced != null) {
+ logger.warn("Replacing custom translator '" + replaced +
+ "' for database " + dbName +
+ " with '" + sqlExceptionTranslator + "'");
+ }
+ else {
+ logger.info("Adding custom translator '" + sqlExceptionTranslator.getClass().getSimpleName() +
+ "' for database " + dbName);
+ }
+ }
+
+ public SQLExceptionTranslator findSqlExceptionTranslatorForDatabase(String dbName) {
+ return sqlExceptionTranslatorRegistry.get(dbName);
+ }
+
+}
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodes.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodes.java
index c7d49126ab2..8dde372beca 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodes.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodes.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2008 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -104,6 +104,10 @@ public class SQLErrorCodes {
return customSqlExceptionTranslator;
}
+ public void setCustomSqlExceptionTranslator(SQLExceptionTranslator customSqlExceptionTranslator) {
+ this.customSqlExceptionTranslator = customSqlExceptionTranslator;
+ }
+
public void setCustomSqlExceptionTranslatorClass(Class customSqlExceptionTranslatorClass) {
if (customSqlExceptionTranslatorClass != null) {
try {
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodesFactory.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodesFactory.java
index 4b984e7f577..bdaf16c0e1d 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodesFactory.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodesFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2010 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
package org.springframework.jdbc.support;
import java.util.Collections;
+import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
import javax.sql.DataSource;
@@ -130,7 +131,7 @@ public class SQLErrorCodesFactory {
logger.warn("Error loading SQL error codes from config file", ex);
errorCodes = Collections.emptyMap();
}
-
+
this.errorCodesMap = errorCodes;
}
@@ -159,7 +160,7 @@ public class SQLErrorCodesFactory {
*/
public SQLErrorCodes getErrorCodes(String dbName) {
Assert.notNull(dbName, "Database product name must not be null");
-
+
SQLErrorCodes sec = this.errorCodesMap.get(dbName);
if (sec == null) {
for (SQLErrorCodes candidate : this.errorCodesMap.values()) {
@@ -170,6 +171,7 @@ public class SQLErrorCodesFactory {
}
}
if (sec != null) {
+ checkSqlExceptionTranslatorRegistry(dbName, sec);
if (logger.isDebugEnabled()) {
logger.debug("SQL error codes for '" + dbName + "' found");
}
@@ -246,4 +248,24 @@ public class SQLErrorCodesFactory {
}
}
+ private void checkSqlExceptionTranslatorRegistry(String dbName, SQLErrorCodes dbCodes) {
+ // Check the custom sql exception translator registry for any entries
+ SQLExceptionTranslator customTranslator =
+ CustomSQLExceptionTranslatorRegistry.getInstance().findSqlExceptionTranslatorForDatabase(dbName);
+ if (customTranslator != null) {
+ if (dbCodes.getCustomSqlExceptionTranslator() != null) {
+ logger.warn("Overriding already defined custom translator '" +
+ dbCodes.getCustomSqlExceptionTranslator().getClass().getSimpleName() +
+ " with '" + customTranslator.getClass().getSimpleName() +
+ "' found in the CustomSQLExceptionTranslatorRegistry for database " + dbName);
+ }
+ else {
+ logger.info("Using custom translator '" + customTranslator.getClass().getSimpleName() +
+ "' found in the CustomSQLExceptionTranslatorRegistry for database " + dbName);
+ }
+ dbCodes.setCustomSqlExceptionTranslator(customTranslator);
+ }
+
+ }
+
}
diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterUtilsTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterUtilsTests.java
index ed4c54e0ee8..48875c60f07 100644
--- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterUtilsTests.java
+++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterUtilsTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -268,4 +268,30 @@ public class NamedParameterUtilsTests {
assertEquals(expectedSql, newSql);
}
+ /*
+ * SPR-8280
+ */
+ @Test
+ public void parseSqlStatementWithQuotedSingleQuote() {
+ String sql = "SELECT ':foo'':doo', :xxx FROM DUAL";
+ ParsedSql psql = NamedParameterUtils.parseSqlStatement(sql);
+ assertEquals(1, psql.getTotalParameterCount());
+ assertEquals("xxx", psql.getParameterNames().get(0));
+ }
+
+ @Test
+ public void parseSqlStatementWithQuotesAndCommentBefore() {
+ String sql = "SELECT /*:doo*/':foo', :xxx FROM DUAL";
+ ParsedSql psql = NamedParameterUtils.parseSqlStatement(sql);
+ assertEquals(1, psql.getTotalParameterCount());
+ assertEquals("xxx", psql.getParameterNames().get(0));
+ }
+
+ @Test
+ public void parseSqlStatementWithQuotesAndCommentAfter() {
+ String sql2 = "SELECT ':foo'/*:doo*/, :xxx FROM DUAL";
+ ParsedSql psql2 = NamedParameterUtils.parseSqlStatement(sql2);
+ assertEquals(1, psql2.getTotalParameterCount());
+ assertEquals("xxx", psql2.getParameterNames().get(0));
+ }
}
diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/DatabasePopulatorTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/DatabasePopulatorTests.java
index 8154fa526ca..04d59698cce 100644
--- a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/DatabasePopulatorTests.java
+++ b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/DatabasePopulatorTests.java
@@ -20,8 +20,6 @@ import static org.junit.Assert.assertEquals;
import java.sql.Connection;
-import javax.sql.DataSource;
-
import org.junit.After;
import org.junit.Test;
import org.springframework.core.io.ClassRelativeResourceLoader;
@@ -49,7 +47,7 @@ public class DatabasePopulatorTests {
assertEquals(name, jdbcTemplate.queryForObject("select NAME from T_TEST", String.class));
}
- private void assertUsersDatabaseCreated(DataSource db) {
+ private void assertUsersDatabaseCreated() {
assertEquals("Sam", jdbcTemplate.queryForObject("select first_name from users where last_name = 'Brannen'",
String.class));
}
@@ -191,7 +189,22 @@ public class DatabasePopulatorTests {
connection.close();
}
- assertUsersDatabaseCreated(db);
+ assertUsersDatabaseCreated();
+ }
+
+ @Test
+ public void testBuildWithSelectStatements() throws Exception {
+ databasePopulator.addScript(resourceLoader.getResource("db-schema.sql"));
+ databasePopulator.addScript(resourceLoader.getResource("db-test-data-select.sql"));
+ Connection connection = db.getConnection();
+ try {
+ databasePopulator.populate(connection);
+ } finally {
+ connection.close();
+ }
+
+ assertEquals(1, jdbcTemplate.queryForInt("select COUNT(NAME) from T_TEST where NAME='Keith'"));
+ assertEquals(1, jdbcTemplate.queryForInt("select COUNT(NAME) from T_TEST where NAME='Dave'"));
}
}
diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/support/CustomSQLExceptionTranslatorRegistrarTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/support/CustomSQLExceptionTranslatorRegistrarTests.java
new file mode 100644
index 00000000000..21ed920479e
--- /dev/null
+++ b/spring-jdbc/src/test/java/org/springframework/jdbc/support/CustomSQLExceptionTranslatorRegistrarTests.java
@@ -0,0 +1,67 @@
+/*
+ * 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.jdbc.support;
+
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.TransientDataAccessResourceException;
+import org.springframework.jdbc.BadSqlGrammarException;
+
+import static org.junit.Assert.*;
+
+/**
+ * Tests for custom translator.
+ *
+ * @author Thomas Risberg
+ */
+public class CustomSQLExceptionTranslatorRegistrarTests {
+
+ @Before
+ public void setUp() {
+ new ClassPathXmlApplicationContext("test-custom-translators-context.xml",
+ CustomSQLExceptionTranslatorRegistrarTests.class);
+ }
+
+ @Test
+ public void testCustomErrorCodeTranslation() {
+
+ SQLErrorCodes codes = SQLErrorCodesFactory.getInstance().getErrorCodes("H2");
+ SQLErrorCodeSQLExceptionTranslator sext = new SQLErrorCodeSQLExceptionTranslator();
+ sext.setSqlErrorCodes(codes);
+
+ DataAccessException exFor4200 = sext.doTranslate("", "", new SQLException("Ouch", "42000", 42000));
+ assertNotNull("Should have been translated", exFor4200);
+ assertTrue("Should have been instance of BadSqlGrammarException",
+ BadSqlGrammarException.class.isAssignableFrom(exFor4200.getClass()));
+
+ DataAccessException exFor2 = sext.doTranslate("", "", new SQLException("Ouch", "42000", 2));
+ assertNotNull("Should have been translated", exFor2);
+ assertTrue("Should have been instance of TransientDataAccessResourceException",
+ TransientDataAccessResourceException.class.isAssignableFrom(exFor2.getClass()));
+
+ DataAccessException exFor3 = sext.doTranslate("", "", new SQLException("Ouch", "42000", 3));
+ assertNull("Should not have been translated", exFor3);
+ }
+
+}
diff --git a/spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/init/db-test-data-select.sql b/spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/init/db-test-data-select.sql
new file mode 100644
index 00000000000..a000cb1b96a
--- /dev/null
+++ b/spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/init/db-test-data-select.sql
@@ -0,0 +1,3 @@
+insert into T_TEST (NAME) values ('Keith');
+insert into T_TEST (NAME) values ('Dave');
+select NAME from T_TEST where NAME = 'Keith';
diff --git a/spring-jdbc/src/test/resources/org/springframework/jdbc/support/test-custom-translators-context.xml b/spring-jdbc/src/test/resources/org/springframework/jdbc/support/test-custom-translators-context.xml
new file mode 100644
index 00000000000..df3a6258dab
--- /dev/null
+++ b/spring-jdbc/src/test/resources/org/springframework/jdbc/support/test-custom-translators-context.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/package-info.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/package-info.java
index b15057a340e..ce3b14842b7 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/package-info.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/package-info.java
@@ -2,15 +2,15 @@
/**
*
* Package providing integration of
- * Hibernate3
+ * Hibernate 3.x
* with Spring concepts.
- *
+ *
*
Contains SessionFactory helper classes, a template plus callback
* for Hibernate access, and an implementation of Spring's transaction SPI
* for local Hibernate transactions.
- *
+ *
*
This package supports Hibernate 3.x only.
- * See the org.springframework.orm.hibernate package for Hibernate 2.1 support.
+ * See the org.springframework.orm.hibernate4 package for Hibernate 4.x support.
*
*/
package org.springframework.orm.hibernate3;
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate4/package-info.java b/spring-orm/src/main/java/org/springframework/orm/hibernate4/package-info.java
index 86f12a5b38f..ef73ff78f93 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate4/package-info.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate4/package-info.java
@@ -2,15 +2,15 @@
/**
*
* Package providing integration of
- * Hibernate 4.0
+ * Hibernate 4.x
* with Spring concepts.
- *
+ *
*
Contains an implementation of Spring's transaction SPI for local Hibernate transactions.
- * This package is intentionally rather minimal, relying on native Hibernate builder APIs
- * for building a SessionFactory (for example in an @Bean method in a @Configuration class).
+ * This package is intentionally rather minimal, with no template classes or the like,
+ * in order to follow native Hibernate recommendations as closely as possible.
*
*
This package supports Hibernate 4.x only.
- * See the org.springframework.orm.hibernate3 package for Hibernate 3.x support.
+ * See the org.springframework.orm.hibernate3 package for Hibernate 3.x support.
*
*/
package org.springframework.orm.hibernate4;
diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java b/spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java
index 9196b8dee4f..02963e2fda0 100644
--- a/spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java
+++ b/spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -306,7 +306,7 @@ public abstract class EntityManagerFactoryUtils {
return new JpaObjectRetrievalFailureException((EntityNotFoundException) ex);
}
if (ex instanceof NoResultException) {
- return new EmptyResultDataAccessException(ex.getMessage(), 1);
+ return new EmptyResultDataAccessException(ex.getMessage(), 1, ex);
}
if (ex instanceof NonUniqueResultException) {
return new IncorrectResultSizeDataAccessException(ex.getMessage(), 1, ex);
diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.java b/spring-orm/src/main/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.java
index 6774691c979..da80e700099 100644
--- a/spring-orm/src/main/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.java
+++ b/spring-orm/src/main/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -123,6 +123,17 @@ public class LocalContainerEntityManagerFactoryBean extends AbstractEntityManage
this.internalPersistenceUnitManager.setPersistenceXmlLocation(persistenceXmlLocation);
}
+ /**
+ * Uses the specified persistence unit name as the name of the default
+ * persistence unit, if applicable.
+ *
NOTE: Only applied if no external PersistenceUnitManager specified.
+ */
+ @Override
+ public void setPersistenceUnitName(String persistenceUnitName) {
+ super.setPersistenceUnitName(persistenceUnitName);
+ this.internalPersistenceUnitManager.setDefaultPersistenceUnitName(persistenceUnitName);
+ }
+
/**
* Set whether to use Spring-based scanning for entity classes in the classpath
* instead of using JPA's standard scanning of jar files with persistence.xml
diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/DefaultPersistenceUnitManager.java b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/DefaultPersistenceUnitManager.java
index 36d446b81e1..807b8ae2d7f 100644
--- a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/DefaultPersistenceUnitManager.java
+++ b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/DefaultPersistenceUnitManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -55,6 +55,7 @@ import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;
import org.springframework.jdbc.datasource.lookup.MapDataSourceLookup;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
+import org.springframework.util.ResourceUtils;
/**
* Default implementation of the {@link PersistenceUnitManager} interface.
@@ -328,7 +329,7 @@ public class DefaultPersistenceUnitManager
/**
* Prepare the PersistenceUnitInfos according to the configuration
* of this manager: scanning for persistence.xml files,
- * parsing all matching files, configurating and post-processing them.
+ * parsing all matching files, configuring and post-processing them.
*
PersistenceUnitInfos cannot be obtained before this preparation
* method has been invoked.
* @see #obtainDefaultPersistenceUnitInfo()
@@ -404,6 +405,12 @@ public class DefaultPersistenceUnitManager
String className = reader.getClassMetadata().getClassName();
if (matchesFilter(reader, readerFactory)) {
scannedUnit.addManagedClassName(className);
+ if (scannedUnit.getPersistenceUnitRootUrl() == null) {
+ URL url = resource.getURL();
+ if (ResourceUtils.isJarURL(url)) {
+ scannedUnit.setPersistenceUnitRootUrl(ResourceUtils.extractJarFileURL(url));
+ }
+ }
}
}
}
diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceUnitReader.java b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceUnitReader.java
index f8d474ea18f..d2460ec94c1 100644
--- a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceUnitReader.java
+++ b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceUnitReader.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2010 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -43,7 +43,7 @@ import org.springframework.util.xml.DomUtils;
import org.springframework.util.xml.SimpleSaxErrorHandler;
/**
- * Internal helper class for reading persistence.xml files.
+ * Internal helper class for reading JPA-compliant persistence.xml files.
*
* @author Costin Leau
* @author Juergen Hoeller
@@ -227,7 +227,7 @@ class PersistenceUnitReader {
/**
* Parse the unit info DOM element.
*/
- protected SpringPersistenceUnitInfo parsePersistenceUnitInfo(Element persistenceUnit, String version) throws IOException { // JC: Changed
+ protected SpringPersistenceUnitInfo parsePersistenceUnitInfo(Element persistenceUnit, String version) throws IOException {
SpringPersistenceUnitInfo unitInfo = new SpringPersistenceUnitInfo();
// set JPA version (1.0 or 2.0)
diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/eclipselink/eclipselink-manager.xml b/spring-orm/src/test/java/org/springframework/orm/jpa/eclipselink/eclipselink-manager.xml
index 625605e8278..a2e4f06ab81 100644
--- a/spring-orm/src/test/java/org/springframework/orm/jpa/eclipselink/eclipselink-manager.xml
+++ b/spring-orm/src/test/java/org/springframework/orm/jpa/eclipselink/eclipselink-manager.xml
@@ -5,6 +5,10 @@
+
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DefaultProfileAnnotationConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DefaultProfileAnnotationConfigTests.java
new file mode 100644
index 00000000000..dd0717bf105
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DefaultProfileAnnotationConfigTests.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.profile.importresource;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.springframework.beans.Employee;
+import org.springframework.beans.Pet;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.support.AnnotationConfigContextLoader;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Juergen Hoeller
+ * @since 3.1
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(classes = DefaultProfileConfig.class, loader = AnnotationConfigContextLoader.class)
+public class DefaultProfileAnnotationConfigTests {
+
+ @Autowired
+ protected Pet pet;
+
+ @Autowired(required = false)
+ protected Employee employee;
+
+
+ @Test
+ public void pet() {
+ assertNotNull(pet);
+ assertEquals("Fido", pet.getName());
+ }
+
+ @Test
+ public void employee() {
+ assertNull("employee bean should not be created for the default profile", employee);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DefaultProfileConfig.java b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DefaultProfileConfig.java
new file mode 100644
index 00000000000..a4af38435a4
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DefaultProfileConfig.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.profile.importresource;
+
+import org.springframework.beans.Pet;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.ImportResource;
+
+/**
+ * @author Juergen Hoeller
+ * @since 3.1
+ */
+@Configuration
+@ImportResource("org/springframework/test/context/junit4/profile/importresource/import.xml")
+public class DefaultProfileConfig {
+
+ @Bean
+ public Pet pet() {
+ return new Pet("Fido");
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DevProfileAnnotationConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DevProfileAnnotationConfigTests.java
new file mode 100644
index 00000000000..9e6ff731697
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DevProfileAnnotationConfigTests.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.profile.importresource;
+
+import org.junit.Test;
+
+import org.springframework.test.context.ActiveProfiles;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Juergen Hoeller
+ * @since 3.1
+ */
+@ActiveProfiles("dev")
+public class DevProfileAnnotationConfigTests extends DefaultProfileAnnotationConfigTests {
+
+ @Test
+ @Override
+ public void employee() {
+ assertNotNull("employee bean should be loaded for the 'dev' profile", employee);
+ assertEquals("John Smith", employee.getName());
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/import.xml b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/import.xml
new file mode 100644
index 00000000000..8427a674a3d
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/import.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/spring-transaction/src/main/java/org/springframework/dao/QueryTimeoutException.java b/spring-transaction/src/main/java/org/springframework/dao/QueryTimeoutException.java
new file mode 100644
index 00000000000..5df9cb3c3f5
--- /dev/null
+++ b/spring-transaction/src/main/java/org/springframework/dao/QueryTimeoutException.java
@@ -0,0 +1,49 @@
+/*
+ * 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.dao;
+
+/**
+ * Exception to be thrown on a query timeout. This could have different causes depending on
+ * the database API in use but most likely thrown after the database interrupts or stops
+ * the processing of a query before it has completed.
+ *
+ * This exception can be thrown by user code trapping the native database exception or
+ * by exception translation.
+ *
+ * @author Thomas Risberg
+ * @since 3.1
+ */
+public class QueryTimeoutException extends TransientDataAccessException {
+
+ /**
+ * Constructor for QueryTimeoutException.
+ * @param msg the detail message
+ */
+ public QueryTimeoutException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * Constructor for QueryTimeoutException.
+ * @param msg the detail message
+ * @param cause the root cause from the data access API in use
+ */
+ public QueryTimeoutException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+
+}
diff --git a/spring-tx/src/main/java/org/springframework/dao/EmptyResultDataAccessException.java b/spring-tx/src/main/java/org/springframework/dao/EmptyResultDataAccessException.java
index f10e84e3d98..dfbb86d54bc 100644
--- a/spring-tx/src/main/java/org/springframework/dao/EmptyResultDataAccessException.java
+++ b/spring-tx/src/main/java/org/springframework/dao/EmptyResultDataAccessException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2006 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -43,4 +43,14 @@ public class EmptyResultDataAccessException extends IncorrectResultSizeDataAcces
super(msg, expectedSize, 0);
}
+ /**
+ * Constructor for EmptyResultDataAccessException.
+ * @param msg the detail message
+ * @param expectedSize the expected result size
+ * @param ex the wrapped exception
+ */
+ public EmptyResultDataAccessException(String msg, int expectedSize, Throwable ex) {
+ super(msg, expectedSize, 0, ex);
+ }
+
}
diff --git a/spring-tx/src/main/java/org/springframework/dao/IncorrectResultSizeDataAccessException.java b/spring-tx/src/main/java/org/springframework/dao/IncorrectResultSizeDataAccessException.java
index f2af289b972..98f8379b332 100644
--- a/spring-tx/src/main/java/org/springframework/dao/IncorrectResultSizeDataAccessException.java
+++ b/spring-tx/src/main/java/org/springframework/dao/IncorrectResultSizeDataAccessException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2006 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -68,8 +68,8 @@ public class IncorrectResultSizeDataAccessException extends DataRetrievalFailure
/**
* Constructor for IncorrectResultSizeDataAccessException.
* @param msg the detail message
- * @param ex the wrapped exception
* @param expectedSize the expected result size
+ * @param ex the wrapped exception
*/
public IncorrectResultSizeDataAccessException(String msg, int expectedSize, Throwable ex) {
super(msg, ex);
@@ -89,19 +89,32 @@ public class IncorrectResultSizeDataAccessException extends DataRetrievalFailure
this.actualSize = actualSize;
}
+ /**
+ * Constructor for IncorrectResultSizeDataAccessException.
+ * @param msg the detail message
+ * @param expectedSize the expected result size
+ * @param actualSize the actual result size (or -1 if unknown)
+ * @param ex the wrapped exception
+ */
+ public IncorrectResultSizeDataAccessException(String msg, int expectedSize, int actualSize, Throwable ex) {
+ super(msg, ex);
+ this.expectedSize = expectedSize;
+ this.actualSize = actualSize;
+ }
+
/**
* Return the expected result size.
*/
public int getExpectedSize() {
- return expectedSize;
+ return this.expectedSize;
}
/**
* Return the actual result size (or -1 if unknown).
*/
public int getActualSize() {
- return actualSize;
+ return this.actualSize;
}
}
diff --git a/spring-tx/src/main/java/org/springframework/transaction/annotation/AbstractTransactionManagementConfiguration.java b/spring-tx/src/main/java/org/springframework/transaction/annotation/AbstractTransactionManagementConfiguration.java
index 9ef8ad23f24..d5ea077155f 100644
--- a/spring-tx/src/main/java/org/springframework/transaction/annotation/AbstractTransactionManagementConfiguration.java
+++ b/spring-tx/src/main/java/org/springframework/transaction/annotation/AbstractTransactionManagementConfiguration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,11 +17,11 @@
package org.springframework.transaction.annotation;
import java.util.Collection;
-import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportAware;
+import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.util.Assert;
@@ -37,11 +37,12 @@ import org.springframework.util.Assert;
@Configuration
public abstract class AbstractTransactionManagementConfiguration implements ImportAware {
- protected Map enableTx;
+ protected AnnotationAttributes enableTx;
protected PlatformTransactionManager txManager;
public void setImportMetadata(AnnotationMetadata importMetadata) {
- this.enableTx = importMetadata.getAnnotationAttributes(EnableTransactionManagement.class.getName(), false);
+ this.enableTx = AnnotationAttributes.fromMap(
+ importMetadata.getAnnotationAttributes(EnableTransactionManagement.class.getName(), false));
Assert.notNull(this.enableTx,
"@EnableTransactionManagement is not present on importing class " +
importMetadata.getClassName());
diff --git a/spring-tx/src/main/java/org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.java b/spring-tx/src/main/java/org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.java
index 48a6436ace1..c1602479b4e 100644
--- a/spring-tx/src/main/java/org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.java
+++ b/spring-tx/src/main/java/org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -44,7 +44,7 @@ public class ProxyTransactionManagementConfiguration extends AbstractTransaction
new BeanFactoryTransactionAttributeSourceAdvisor();
advisor.setTransactionAttributeSource(transactionAttributeSource());
advisor.setAdvice(transactionInterceptor());
- advisor.setOrder(((Integer)this.enableTx.get("order")));
+ advisor.setOrder(this.enableTx.getNumber("order"));
return advisor;
}
diff --git a/spring-web/src/main/java/org/springframework/http/MediaType.java b/spring-web/src/main/java/org/springframework/http/MediaType.java
index d467e1feaeb..6d0f75fe4f5 100644
--- a/spring-web/src/main/java/org/springframework/http/MediaType.java
+++ b/spring-web/src/main/java/org/springframework/http/MediaType.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -450,6 +450,14 @@ public class MediaType implements Comparable {
return this.parameters.get(name);
}
+ /**
+ * Return all generic parameter values.
+ * @return a read-only map, possibly empty, never null
+ */
+ public Map getParameters() {
+ return parameters;
+ }
+
/**
* Indicate whether this {@code MediaType} includes the given media type.
* For instance, {@code text/*} includes {@code text/plain} and {@code text/html}, and {@code application/*+xml}
diff --git a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java
index e93367b6515..5dd0dbd4249 100644
--- a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java
+++ b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,15 +25,19 @@ import java.io.Writer;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
+import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Enumeration;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
import org.springframework.util.Assert;
/**
@@ -90,14 +94,31 @@ public class ServletServerHttpRequest implements ServerHttpRequest {
public HttpHeaders getHeaders() {
if (this.headers == null) {
this.headers = new HttpHeaders();
- for (Enumeration headerNames = this.servletRequest.getHeaderNames(); headerNames.hasMoreElements();) {
+ for (Enumeration> headerNames = this.servletRequest.getHeaderNames(); headerNames.hasMoreElements();) {
String headerName = (String) headerNames.nextElement();
- for (Enumeration headerValues = this.servletRequest.getHeaders(headerName);
+ for (Enumeration> headerValues = this.servletRequest.getHeaders(headerName);
headerValues.hasMoreElements();) {
String headerValue = (String) headerValues.nextElement();
this.headers.add(headerName, headerValue);
}
}
+ // HttpServletRequest exposes some headers as properties: we should include those if not already present
+ if (this.headers.getContentType() == null && this.servletRequest.getContentType() != null) {
+ MediaType contentType = MediaType.parseMediaType(this.servletRequest.getContentType());
+ this.headers.setContentType(contentType);
+ }
+ if (this.headers.getContentType() != null && this.headers.getContentType().getCharSet() == null &&
+ this.servletRequest.getCharacterEncoding() != null) {
+ MediaType oldContentType = this.headers.getContentType();
+ Charset charSet = Charset.forName(this.servletRequest.getCharacterEncoding());
+ Map params = new HashMap(oldContentType.getParameters());
+ params.put("charset", charSet.toString());
+ MediaType newContentType = new MediaType(oldContentType.getType(), oldContentType.getSubtype(), params);
+ this.headers.setContentType(newContentType);
+ }
+ if (this.headers.getContentLength() == -1 && this.servletRequest.getContentLength() != -1) {
+ this.headers.setContentLength(this.servletRequest.getContentLength());
+ }
}
return this.headers;
}
diff --git a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java
index 9752c4259d8..985085e51e9 100644
--- a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java
+++ b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -83,6 +83,14 @@ public class ServletServerHttpResponse implements ServerHttpResponse {
this.servletResponse.addHeader(headerName, headerValue);
}
}
+ // HttpServletResponse exposes some headers as properties: we should include those if not already present
+ if (this.servletResponse.getContentType() == null && this.headers.getContentType() != null) {
+ this.servletResponse.setContentType(this.headers.getContentType().toString());
+ }
+ if (this.servletResponse.getCharacterEncoding() == null && this.headers.getContentType() != null &&
+ this.headers.getContentType().getCharSet() != null) {
+ this.servletResponse.setCharacterEncoding(this.headers.getContentType().getCharSet().name());
+ }
this.headersWritten = true;
}
}
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java
index 7a4ff06b2e5..00ad724649d 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,17 +24,18 @@ import java.lang.annotation.Target;
/**
* Annotation for mapping web requests onto specific handler classes and/or
- * handler methods. Provides consistent style between Servlet and Portlet
+ * handler methods. Provides a consistent style between Servlet and Portlet
* environments, with the semantics adapting to the concrete environment.
*
- * NOTE: Method-level mappings are only allowed to narrow the mapping
- * expressed at the class level (if any). In the Servlet case, an HTTP path needs to
- * uniquely map onto one specific handler bean (not spread across multiple handler beans);
- * the remaining mapping parameters and conditions are effectively assertions only.
- * In the Portlet case, a portlet mode in combination with specific parameter conditions
- * needs to uniquely map onto one specific handler bean, with all conditions evaluated
- * for mapping purposes. It is strongly recommended to co-locate related handler methods
- * into the same bean and therefore keep the mappings simple and intuitive.
+ *
NOTE: The set of features supported for Servlets is a superset
+ * of the set of features supported for Portlets. The places where this applies
+ * are marked with the label "Servlet-only" in this source file. For Servlet
+ * environments there are some further distinctions depending on whether an
+ * application is configured with {@literal "@MVC 3.0"} or
+ * {@literal "@MVC 3.1"} support classes. The places where this applies are
+ * marked with {@literal "@MVC 3.1-only"} in this source file. For more
+ * details see the note on the new support classes added in Spring MVC 3.1
+ * further below.
*
*
Handler methods which are annotated with this annotation are allowed
* to have very flexible signatures. They may have arguments of the following
@@ -71,8 +72,8 @@ import java.lang.annotation.Target;
*
{@link java.io.OutputStream} / {@link java.io.Writer} for generating
* the response's content. This will be the raw OutputStream/Writer as
* exposed by the Servlet/Portlet API.
- * {@link PathVariable @PathVariable} annotated parameters for access to
- * URI template values (i.e. /hotels/{hotel}). Variable values will be
+ * {@link PathVariable @PathVariable} annotated parameters (Servlet-only)
+ * for access to URI template values (i.e. /hotels/{hotel}). Variable values will be
* converted to the declared method argument type. By default, the URI template
* will match against the regular expression {@code [^\.]*} (i.e. any character
* other than period), but this can be changed by specifying another regular
@@ -90,30 +91,43 @@ import java.lang.annotation.Target;
* {@link org.springframework.util.MultiValueMap MultiValueMap<String, String>}, or
* {@link org.springframework.http.HttpHeaders HttpHeaders} method parameter to
* gain access to all request headers.
- * {@link RequestBody @RequestBody} annotated parameters for access to
- * the Servlet request HTTP contents. The request stream will be
- * converted to the declared method argument type using
+ * {@link RequestBody @RequestBody} annotated parameters (Servlet-only)
+ * for access to the Servlet request HTTP contents. The request stream will be
+ * converted to the declared method argument type using
* {@linkplain org.springframework.http.converter.HttpMessageConverter message
- * converters}. Such parameters may optionally be annotated with {@code @Valid}.
- * {@link RequestPart @RequestPart} annotated parameters for access to the content
+ * converters}. Such parameters may optionally be annotated with {@code @Valid}
+ * but do not support access to validation results through a
+ * {@link org.springframework.validation.Errors} /
+ * {@link org.springframework.validation.BindingResult} argument.
+ * Instead a {@link org.springframework.web.servlet.mvc.method.annotation.MethodArgumentNotValidException}
+ * exception is raised.
+ * {@link RequestPart @RequestPart} annotated parameters
+ * (Servlet-only, {@literal @MVC 3.1-only})
+ * for access to the content
* of a part of "multipart/form-data" request. The request part stream will be
- * converted to the declared method argument type using
+ * converted to the declared method argument type using
* {@linkplain org.springframework.http.converter.HttpMessageConverter message
- * converters}. Such parameters may optionally be annotated with {@code @Valid}.
+ * converters}. Such parameters may optionally be annotated with {@code @Valid}
+ * but do not support access to validation results through a
+ * {@link org.springframework.validation.Errors} /
+ * {@link org.springframework.validation.BindingResult} argument.
+ * Instead a {@link org.springframework.web.servlet.mvc.method.annotation.MethodArgumentNotValidException}
+ * exception is raised.
* {@link org.springframework.http.HttpEntity HttpEntity<?>} parameters
- * for access to the Servlet request HTTP headers and contents. The request stream will be
- * converted to the entity body using
+ * (Servlet-only) for access to the Servlet request HTTP headers and contents.
+ * The request stream will be converted to the entity body using
* {@linkplain org.springframework.http.converter.HttpMessageConverter message
* converters}.
* {@link java.util.Map} / {@link org.springframework.ui.Model} /
* {@link org.springframework.ui.ModelMap} for enriching the implicit model
* that will be exposed to the web view.
- * {@link org.springframework.web.servlet.mvc.support.RedirectAttributes}
- * to specify the exact set of attributes to use in case of a redirect
- * and also to add flash attributes (attributes stored temporarily on the
- * server-side to make them available to the request after the redirect).
- * {@code RedirectAttributes} is used instead of the implicit model if the
- * method returns a "redirect:" prefixed view name or {@code RedirectView}.
+ * {@link org.springframework.web.servlet.mvc.support.RedirectAttributes}
+ * (Servlet-only, {@literal @MVC 3.1-only}) to specify the exact set of attributes
+ * to use in case of a redirect and also to add flash attributes (attributes
+ * stored temporarily on the server-side to make them available to the request
+ * after the redirect). {@code RedirectAttributes} is used instead of the
+ * implicit model if the method returns a "redirect:" prefixed view name or
+ * {@code RedirectView}.
* Command/form objects to bind parameters to: as bean properties or fields,
* with customizable type conversion, depending on {@link InitBinder} methods
* and/or the HandlerAdapter configuration - see the "webBindingInitializer"
@@ -130,9 +144,10 @@ import java.lang.annotation.Target;
* for marking form processing as complete (triggering the cleanup of session
* attributes that have been indicated by the {@link SessionAttributes} annotation
* at the handler type level).
- * {@link org.springframework.web.util.UriComponentsBuilder} a builder for
- * preparing a URL relative to the current request's host, port, scheme, context
- * path, and the literal part of the servlet mapping.
+ * {@link org.springframework.web.util.UriComponentsBuilder}
+ * (Servlet-only, {@literal @MVC 3.1-only})
+ * for preparing a URL relative to the current request's host, port, scheme,
+ * context path, and the literal part of the servlet mapping.
*
*
* The following return types are supported for handler methods:
@@ -160,15 +175,15 @@ import java.lang.annotation.Target;
* The handler method may also programmatically enrich the model by
* declaring a {@link org.springframework.ui.ModelMap} argument
* (see above).
- *
{@link ResponseBody @ResponseBody} annotated methods for access to
- * the Servlet response HTTP contents. The return value will be converted
- * to the response stream using
+ * {@link ResponseBody @ResponseBody} annotated methods (Servlet-only)
+ * for access to the Servlet response HTTP contents. The return value will
+ * be converted to the response stream using
* {@linkplain org.springframework.http.converter.HttpMessageConverter message
* converters}.
* A {@link org.springframework.http.HttpEntity HttpEntity<?>} or
* {@link org.springframework.http.ResponseEntity ResponseEntity<?>} object
- * to access to the Servlet response HTTP headers and contents. The entity body will
- * be converted to the response stream using
+ * (Servlet-only) to access to the Servlet response HTTP headers and contents.
+ * The entity body will be converted to the response stream using
* {@linkplain org.springframework.http.converter.HttpMessageConverter message
* converters}.
* void if the method handles the response itself (by
@@ -187,16 +202,24 @@ import java.lang.annotation.Target;
* {@link ModelAttribute} annotated reference data accessor methods.
*
*
- * NOTE: @RequestMapping will only be processed if a
- * corresponding HandlerMapping (for type level annotations)
- * and/or HandlerAdapter (for method level annotations) is
- * present in the dispatcher. This is the case by default in both
- * DispatcherServlet and DispatcherPortlet.
+ *
NOTE: @RequestMapping will only be processed if an
+ * an appropriate HandlerMapping-HandlerAdapter pair
+ * is configured. This is the case by default in both the
+ * DispatcherServlet and the DispatcherPortlet.
* However, if you are defining custom HandlerMappings or
- * HandlerAdapters, then you need to make sure that a
- * corresponding custom RequestMappingHandlerMethodMapping
- * and/or RequestMappingHandlerMethodAdapter is defined as well
- * - provided that you intend to use @RequestMapping.
+ * HandlerAdapters, then you need to add
+ * DefaultAnnotationHandlerMapping and
+ * AnnotationMethodHandlerAdapter to your configuration..
+ *
+ *
NOTE: Spring 3.1 introduced a new set of support classes for
+ * @RequestMapping methods in Servlet environments called
+ * RequestMappingHandlerMapping and
+ * RequestMappingHandlerAdapter. They are recommended for use and
+ * even required to take advantage of new features in Spring MVC 3.1 (search
+ * {@literal "@MVC 3.1-only"} in this source file) and going forward.
+ * The new support classes are enabled by default from the MVC namespace and
+ * with use of the MVC Java config (@EnableWebMvc) but must be
+ * configured explicitly if using neither.
*
*
NOTE: When using controller interfaces (e.g. for AOP proxying),
* make sure to consistently put all your mapping annotations - such as
@@ -234,20 +257,6 @@ public @interface RequestMapping {
*
Supported at the type level as well as at the method level!
* When used at the type level, all method-level mappings inherit
* this primary mapping, narrowing it for a specific handler method.
- *
In case of Servlet-based handler methods, the method names are
- * taken into account for narrowing if no path was specified explicitly,
- * according to the specified
- * {@link org.springframework.web.servlet.mvc.multiaction.MethodNameResolver}
- * (by default an
- * {@link org.springframework.web.servlet.mvc.multiaction.InternalPathMethodNameResolver}).
- * Note that this only applies in case of ambiguous annotation mappings
- * that do not specify a path mapping explicitly. In other words,
- * the method name is only used for narrowing among a set of matching
- * methods; it does not constitute a primary path mapping itself.
- *
If you have a single default method (without explicit path mapping),
- * then all requests without a more specific mapped method found will
- * be dispatched to it. If you have multiple such default methods, then
- * the method name will be taken into account for choosing between them.
*/
String[] value() default {};
@@ -309,7 +318,7 @@ public @interface RequestMapping {
* and against PortletRequest properties in a Portlet 2.0 environment.
* @see org.springframework.http.MediaType
*/
- String[] headers() default {};
+ String[] headers() default {};
/**
* The consumable media types of the mapped request, narrowing the primary mapping.
diff --git a/spring-web/src/main/java/org/springframework/web/context/ContextLoader.java b/spring-web/src/main/java/org/springframework/web/context/ContextLoader.java
index 1f32562deb9..79c3d8744ca 100644
--- a/spring-web/src/main/java/org/springframework/web/context/ContextLoader.java
+++ b/spring-web/src/main/java/org/springframework/web/context/ContextLoader.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,11 +23,11 @@ import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
+
import javax.servlet.ServletContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.access.BeanFactoryLocator;
import org.springframework.beans.factory.access.BeanFactoryReference;
@@ -44,6 +44,7 @@ import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
+import org.springframework.web.context.support.WebApplicationContextUtils;
/**
* Performs the actual initialization work for the root application context.
@@ -463,11 +464,18 @@ public class ContextLoader {
* @see ApplicationContextInitializer#initialize(ConfigurableApplicationContext)
*/
protected void customizeContext(ServletContext servletContext, ConfigurableWebApplicationContext applicationContext) {
+ List>> initializerClasses =
+ determineContextInitializerClasses(servletContext);
+
+ if (initializerClasses.size() == 0) {
+ // no ApplicationContextInitializers have been declared -> nothing to do
+ return;
+ }
+
ArrayList> initializerInstances =
new ArrayList>();
- for (Class> initializerClass :
- determineContextInitializerClasses(servletContext)) {
+ for (Class> initializerClass : initializerClasses) {
Class> contextClass = applicationContext.getClass();
Class> initializerContextClass =
GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class);
@@ -480,6 +488,13 @@ public class ContextLoader {
Collections.sort(initializerInstances, new AnnotationAwareOrderComparator());
+ // eagerly attempt to initialize servlet property sources in case initializers
+ // below depend on accessing context-params via the Environment API. Note that
+ // depending on application context implementation, this initialization will be
+ // attempted again during context refresh.
+ WebApplicationContextUtils.initServletPropertySources(
+ applicationContext.getEnvironment().getPropertySources(), servletContext);
+
for (ApplicationContextInitializer initializer : initializerInstances) {
initializer.initialize(applicationContext);
}
diff --git a/spring-web/src/main/java/org/springframework/web/context/support/ServletContextResource.java b/spring-web/src/main/java/org/springframework/web/context/support/ServletContextResource.java
index 99a028b2dc2..af73e49a436 100644
--- a/spring-web/src/main/java/org/springframework/web/context/support/ServletContextResource.java
+++ b/spring-web/src/main/java/org/springframework/web/context/support/ServletContextResource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -107,6 +107,28 @@ public class ServletContextResource extends AbstractFileResolvingResource implem
}
}
+ /**
+ * This implementation delegates to ServletContext.getResourceAsStream,
+ * which returns null in case of a non-readable resource (e.g. a directory).
+ * @see javax.servlet.ServletContext#getResourceAsStream(String)
+ */
+ @Override
+ public boolean isReadable() {
+ InputStream is = this.servletContext.getResourceAsStream(this.path);
+ if (is != null) {
+ try {
+ is.close();
+ }
+ catch (IOException ex) {
+ // ignore
+ }
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+
/**
* This implementation delegates to ServletContext.getResourceAsStream,
* but throws a FileNotFoundException if no resource found.
diff --git a/spring-web/src/main/java/org/springframework/web/context/support/WebApplicationContextUtils.java b/spring-web/src/main/java/org/springframework/web/context/support/WebApplicationContextUtils.java
index eb607d1c318..9b9426d112f 100644
--- a/spring-web/src/main/java/org/springframework/web/context/support/WebApplicationContextUtils.java
+++ b/spring-web/src/main/java/org/springframework/web/context/support/WebApplicationContextUtils.java
@@ -16,6 +16,9 @@
package org.springframework.web.context.support;
+import static org.springframework.web.context.support.StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME;
+import static org.springframework.web.context.support.StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME;
+
import java.io.Serializable;
import java.util.Collections;
import java.util.Enumeration;
@@ -32,6 +35,7 @@ import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.env.MutablePropertySources;
+import org.springframework.core.env.PropertySource.StubPropertySource;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.web.context.ConfigurableWebApplicationContext;
@@ -247,13 +251,15 @@ public abstract class WebApplicationContextUtils {
public static void initServletPropertySources(
MutablePropertySources propertySources, ServletContext servletContext, ServletConfig servletConfig) {
Assert.notNull(propertySources, "propertySources must not be null");
- if(servletContext != null && propertySources.contains(StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME)) {
- propertySources.replace(StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME,
- new ServletContextPropertySource(StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME, servletContext));
+ if(servletContext != null &&
+ propertySources.contains(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME) &&
+ propertySources.get(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME) instanceof StubPropertySource) {
+ propertySources.replace(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME, new ServletContextPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME, servletContext));
}
- if(servletConfig != null && propertySources.contains(StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME)) {
- propertySources.replace(StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME,
- new ServletConfigPropertySource(StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME, servletConfig));
+ if(servletConfig != null &&
+ propertySources.contains(SERVLET_CONFIG_PROPERTY_SOURCE_NAME) &&
+ propertySources.get(SERVLET_CONFIG_PROPERTY_SOURCE_NAME) instanceof StubPropertySource) {
+ propertySources.replace(SERVLET_CONFIG_PROPERTY_SOURCE_NAME, new ServletConfigPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME, servletConfig));
}
}
diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolver.java b/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolver.java
index c659d699d29..0fb76c9bfb4 100644
--- a/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolver.java
+++ b/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolver.java
@@ -179,17 +179,10 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod
}
private void assertIsMultipartRequest(HttpServletRequest request) {
- if (!isMultipartRequest(request)) {
- throw new MultipartException("The current request is not a multipart request.");
- }
- }
-
- private boolean isMultipartRequest(HttpServletRequest request) {
- if (!"post".equals(request.getMethod().toLowerCase())) {
- return false;
- }
String contentType = request.getContentType();
- return (contentType != null && contentType.toLowerCase().startsWith("multipart/"));
+ if (contentType == null || !contentType.toLowerCase().startsWith("multipart/")) {
+ throw new MultipartException("The current request is not a multipart request");
+ }
}
private boolean isMultipartFileCollection(MethodParameter parameter) {
diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/SessionAttributesHandler.java b/spring-web/src/main/java/org/springframework/web/method/annotation/SessionAttributesHandler.java
index fd7b4e42cf6..5e6080db13e 100644
--- a/spring-web/src/main/java/org/springframework/web/method/annotation/SessionAttributesHandler.java
+++ b/spring-web/src/main/java/org/springframework/web/method/annotation/SessionAttributesHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,16 +31,16 @@ import org.springframework.web.bind.support.SessionStatus;
import org.springframework.web.context.request.WebRequest;
/**
- * Manages controller-specific session attributes declared via
- * {@link SessionAttributes @SessionAttributes}. Actual storage is
- * performed via {@link SessionAttributeStore}.
- *
- * When a controller annotated with {@code @SessionAttributes} adds
- * attributes to its model, those attributes are checked against names and
- * types specified via {@code @SessionAttributes}. Matching model attributes
- * are saved in the HTTP session and remain there until the controller calls
+ * Manages controller-specific session attributes declared via
+ * {@link SessionAttributes @SessionAttributes}. Actual storage is
+ * delegated to a {@link SessionAttributeStore} instance.
+ *
+ *
When a controller annotated with {@code @SessionAttributes} adds
+ * attributes to its model, those attributes are checked against names and
+ * types specified via {@code @SessionAttributes}. Matching model attributes
+ * are saved in the HTTP session and remain there until the controller calls
* {@link SessionStatus#setComplete()}.
- *
+ *
* @author Rossen Stoyanchev
* @since 3.1
*/
@@ -50,51 +50,53 @@ public class SessionAttributesHandler {
private final Set> attributeTypes = new HashSet>();
- private final Set resolvedAttributeNames = Collections.synchronizedSet(new HashSet(4));
+ private final Set knownAttributeNames = Collections.synchronizedSet(new HashSet(4));
private final SessionAttributeStore sessionAttributeStore;
/**
- * Creates a new instance for a controller type. Session attribute names/types
- * are extracted from a type-level {@code @SessionAttributes} if found.
+ * Create a new instance for a controller type. Session attribute names and
+ * types are extracted from the {@code @SessionAttributes} annotation, if
+ * present, on the given type.
* @param handlerType the controller type
* @param sessionAttributeStore used for session access
*/
public SessionAttributesHandler(Class> handlerType, SessionAttributeStore sessionAttributeStore) {
Assert.notNull(sessionAttributeStore, "SessionAttributeStore may not be null.");
this.sessionAttributeStore = sessionAttributeStore;
-
+
SessionAttributes annotation = AnnotationUtils.findAnnotation(handlerType, SessionAttributes.class);
if (annotation != null) {
- this.attributeNames.addAll(Arrays.asList(annotation.value()));
+ this.attributeNames.addAll(Arrays.asList(annotation.value()));
this.attributeTypes.addAll(Arrays.>asList(annotation.types()));
- }
+ }
+
+ this.knownAttributeNames.addAll(this.attributeNames);
}
/**
- * Whether the controller represented by this instance has declared session
- * attribute names or types of interest via {@link SessionAttributes}.
+ * Whether the controller represented by this instance has declared any
+ * session attributes through an {@link SessionAttributes} annotation.
*/
public boolean hasSessionAttributes() {
- return ((this.attributeNames.size() > 0) || (this.attributeTypes.size() > 0));
+ return ((this.attributeNames.size() > 0) || (this.attributeTypes.size() > 0));
}
-
+
/**
- * Whether the attribute name and/or type match those specified in the
- * controller's {@code @SessionAttributes} annotation.
- *
+ * Whether the attribute name or type match the names and types specified
+ * via {@code @SessionAttributes} in underlying controller.
+ *
* Attributes successfully resolved through this method are "remembered"
- * and used in {@link #retrieveAttributes(WebRequest)} and
- * {@link #cleanupAttributes(WebRequest)}. In other words, retrieval and
- * cleanup only affect attributes previously resolved through here.
- *
- * @param attributeName the attribute name to check; must not be null
- * @param attributeType the type for the attribute; or {@code null}
+ * and subsequently used in {@link #retrieveAttributes(WebRequest)} and
+ * {@link #cleanupAttributes(WebRequest)}.
+ *
+ * @param attributeName the attribute name to check, never {@code null}
+ * @param attributeType the type for the attribute, possibly {@code null}
*/
public boolean isHandlerSessionAttribute(String attributeName, Class> attributeType) {
Assert.notNull(attributeName, "Attribute name must not be null");
if (this.attributeNames.contains(attributeName) || this.attributeTypes.contains(attributeType)) {
- this.resolvedAttributeNames.add(attributeName);
+ this.knownAttributeNames.add(attributeName);
return true;
}
else {
@@ -103,8 +105,8 @@ public class SessionAttributesHandler {
}
/**
- * Stores a subset of the given attributes in the session. Attributes not
- * declared as session attributes via {@code @SessionAttributes} are ignored.
+ * Store a subset of the given attributes in the session. Attributes not
+ * declared as session attributes via {@code @SessionAttributes} are ignored.
* @param request the current request
* @param attributes candidate attributes for session storage
*/
@@ -112,23 +114,23 @@ public class SessionAttributesHandler {
for (String name : attributes.keySet()) {
Object value = attributes.get(name);
Class> attrType = (value != null) ? value.getClass() : null;
-
+
if (isHandlerSessionAttribute(name, attrType)) {
this.sessionAttributeStore.storeAttribute(request, name, value);
}
}
}
-
+
/**
- * Retrieve "known" attributes from the session -- i.e. attributes listed
- * in {@code @SessionAttributes} and previously stored in the in the model
- * at least once.
+ * Retrieve "known" attributes from the session, i.e. attributes listed
+ * by name in {@code @SessionAttributes} or attributes previously stored
+ * in the model that matched by type.
* @param request the current request
- * @return a map with handler session attributes; possibly empty.
+ * @return a map with handler session attributes, possibly empty
*/
public Map retrieveAttributes(WebRequest request) {
Map attributes = new HashMap();
- for (String name : this.resolvedAttributeNames) {
+ for (String name : this.knownAttributeNames) {
Object value = this.sessionAttributeStore.retrieveAttribute(request, name);
if (value != null) {
attributes.put(name, value);
@@ -138,13 +140,13 @@ public class SessionAttributesHandler {
}
/**
- * Cleans "known" attributes from the session - i.e. attributes listed
- * in {@code @SessionAttributes} and previously stored in the in the model
- * at least once.
+ * Remove "known" attributes from the session, i.e. attributes listed
+ * by name in {@code @SessionAttributes} or attributes previously stored
+ * in the model that matched by type.
* @param request the current request
*/
public void cleanupAttributes(WebRequest request) {
- for (String attributeName : this.resolvedAttributeNames) {
+ for (String attributeName : this.knownAttributeNames) {
this.sessionAttributeStore.cleanupAttribute(request, attributeName);
}
}
@@ -158,5 +160,5 @@ public class SessionAttributesHandler {
Object retrieveAttribute(WebRequest request, String attributeName) {
return this.sessionAttributeStore.retrieveAttribute(request, attributeName);
}
-
+
}
\ No newline at end of file
diff --git a/spring-web/src/main/java/org/springframework/web/util/UriComponents.java b/spring-web/src/main/java/org/springframework/web/util/UriComponents.java
index a5b4ec74b9c..54cbcef437e 100644
--- a/spring-web/src/main/java/org/springframework/web/util/UriComponents.java
+++ b/spring-web/src/main/java/org/springframework/web/util/UriComponents.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -286,7 +286,7 @@ public final class UriComponents {
if (source == null) {
return null;
}
-
+
Assert.hasLength(encoding, "'encoding' must not be empty");
byte[] bytes = encodeBytes(source.getBytes(encoding), type);
@@ -406,7 +406,7 @@ public final class UriComponents {
private UriComponents expandInternal(UriTemplateVariables uriVariables) {
Assert.state(!encoded, "Cannot expand an already encoded UriComponents object");
-
+
String expandedScheme = expandUriComponent(this.scheme, uriVariables);
String expandedUserInfo = expandUriComponent(this.userInfo, uriVariables);
String expandedHost = expandUriComponent(this.host, uriVariables);
@@ -458,6 +458,16 @@ public final class UriComponents {
return variableValue != null ? variableValue.toString() : "";
}
+ /**
+ * Normalize the path removing sequences like "path/..".
+ * @see StringUtils#cleanPath(String)
+ */
+ public UriComponents normalize() {
+ String normalizedPath = StringUtils.cleanPath(getPath());
+ return new UriComponents(scheme, userInfo, host, this.port, new FullPathComponent(normalizedPath),
+ queryParams, fragment, encoded, false);
+ }
+
// other functionality
/**
@@ -930,7 +940,7 @@ public final class UriComponents {
}
}
-
+
/**
* Represents an empty path.
@@ -992,6 +1002,9 @@ public final class UriComponents {
}
public Object getValue(String name) {
+ if (!this.uriVariables.containsKey(name)) {
+ throw new IllegalArgumentException("Map has no value for '" + name + "'");
+ }
return this.uriVariables.get(name);
}
}
@@ -1000,6 +1013,7 @@ public final class UriComponents {
* URI template variables backed by a variable argument array.
*/
private static class VarArgsTemplateVariables implements UriTemplateVariables {
+
private final Iterator valueIterator;
public VarArgsTemplateVariables(Object... uriVariableValues) {
@@ -1008,7 +1022,7 @@ public final class UriComponents {
public Object getValue(String name) {
if (!valueIterator.hasNext()) {
- throw new IllegalArgumentException("Not enough variable values available to expand [" + name + "]");
+ throw new IllegalArgumentException("Not enough variable values available to expand '" + name + "'");
}
return valueIterator.next();
}
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 f4ed1f252e1..c95d620256e 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-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,7 @@ package org.springframework.web.util;
import java.net.URI;
import java.util.ArrayList;
-import java.util.Collections;
+import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
@@ -80,7 +80,7 @@ public class UriComponentsBuilder {
"^" + HTTP_PATTERN + "(//(" + USERINFO_PATTERN + "@)?" + HOST_PATTERN + "(:" + PORT_PATTERN + ")?" + ")?" +
PATH_PATTERN + "(\\?" + LAST_PATTERN + ")?");
-
+
private String scheme;
private String userInfo;
@@ -223,10 +223,10 @@ public class UriComponentsBuilder {
}
/**
- * Builds a {@code UriComponents} instance and replaces URI template variables
- * with the values from a map. This is a shortcut method, which combines
- * calls to {@link #build()} and then {@link UriComponents#expand(Map)}.
- *
+ * Builds a {@code UriComponents} instance and replaces URI template variables
+ * with the values from a map. This is a shortcut method, which combines
+ * calls to {@link #build()} and then {@link UriComponents#expand(Map)}.
+ *
* @param uriVariables the map of URI variables
* @return the URI components with expanded values
*/
@@ -235,17 +235,17 @@ public class UriComponentsBuilder {
}
/**
- * Builds a {@code UriComponents} instance and replaces URI template variables
- * with the values from an array. This is a shortcut method, which combines
- * calls to {@link #build()} and then {@link UriComponents#expand(Object...)}.
- *
+ * Builds a {@code UriComponents} instance and replaces URI template variables
+ * with the values from an array. This is a shortcut method, which combines
+ * calls to {@link #build()} and then {@link UriComponents#expand(Object...)}.
+ *
* @param uriVariableValues URI variable values
* @return the URI components with expanded values
*/
public UriComponents buildAndExpand(Object... uriVariableValues) {
return build(false).expand(uriVariableValues);
}
-
+
// URI components methods
/**
@@ -347,8 +347,8 @@ public class UriComponentsBuilder {
}
/**
- * Sets the path of this builder overriding all existing path and path segment values.
- *
+ * Sets the path of this builder overriding all existing path and path segment values.
+ *
* @param path the URI path; a {@code null} value results in an empty path.
* @return this UriComponentsBuilder
*/
@@ -394,7 +394,7 @@ public class UriComponentsBuilder {
/**
* Sets the query of this builder overriding all existing query parameters.
- *
+ *
* @param query the query string; a {@code null} value removes all query parameters.
* @return this UriComponentsBuilder
*/
@@ -430,7 +430,7 @@ public class UriComponentsBuilder {
/**
* Sets the query parameter values overriding all existing query values for the same parameter.
* If no values are given, the query parameter is removed.
- *
+ *
* @param name the query parameter name
* @param values the query parameter values
* @return this UriComponentsBuilder
@@ -443,7 +443,7 @@ public class UriComponentsBuilder {
}
return this;
}
-
+
/**
* Sets the URI fragment. The given fragment may contain URI template variables, and may also be {@code null} to clear
* the fragment of this builder.
@@ -509,7 +509,17 @@ public class UriComponentsBuilder {
private final List pathSegments = new ArrayList();
private PathSegmentComponentBuilder(String... pathSegments) {
- Collections.addAll(this.pathSegments, pathSegments);
+ this.pathSegments.addAll(removeEmptyPathSegments(pathSegments));
+ }
+
+ private Collection removeEmptyPathSegments(String... pathSegments) {
+ List result = new ArrayList();
+ for (String segment : pathSegments) {
+ if (StringUtils.hasText(segment)) {
+ result.add(segment);
+ }
+ }
+ return result;
}
public UriComponents.PathComponent build() {
@@ -523,7 +533,7 @@ public class UriComponentsBuilder {
}
public PathComponentBuilder appendPathSegments(String... pathSegments) {
- Collections.addAll(this.pathSegments, pathSegments);
+ this.pathSegments.addAll(removeEmptyPathSegments(pathSegments));
return this;
}
}
diff --git a/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpRequestTests.java b/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpRequestTests.java
index 30fd57b6b79..1a2ec75e574 100644
--- a/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpRequestTests.java
+++ b/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpRequestTests.java
@@ -1,11 +1,11 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -17,6 +17,7 @@
package org.springframework.http.server;
import java.net.URI;
+import java.nio.charset.Charset;
import java.util.List;
import org.junit.Before;
@@ -24,6 +25,7 @@ import org.junit.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.util.FileCopyUtils;
@@ -67,6 +69,8 @@ public class ServletServerHttpRequestTests {
mockRequest.addHeader(headerName, headerValue1);
String headerValue2 = "value2";
mockRequest.addHeader(headerName, headerValue2);
+ mockRequest.setContentType("text/plain");
+ mockRequest.setCharacterEncoding("UTF-8");
HttpHeaders headers = request.getHeaders();
assertNotNull("No HttpHeaders returned", headers);
@@ -75,6 +79,8 @@ public class ServletServerHttpRequestTests {
assertEquals("Invalid header values returned", 2, headerValues.size());
assertTrue("Invalid header values returned", headerValues.contains(headerValue1));
assertTrue("Invalid header values returned", headerValues.contains(headerValue2));
+ assertEquals("Invalid Content-Type", new MediaType("text", "plain", Charset.forName("UTF-8")),
+ headers.getContentType());
}
@Test
diff --git a/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpResponseTests.java b/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpResponseTests.java
index bad9ccdb840..fd9392f108d 100644
--- a/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpResponseTests.java
+++ b/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpResponseTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2009 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,16 +16,19 @@
package org.springframework.http.server;
+import java.nio.charset.Charset;
import java.util.List;
-import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
-import org.springframework.mock.web.MockHttpServletResponse;
-import org.springframework.util.FileCopyUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.util.FileCopyUtils;
+
+import static org.junit.Assert.*;
/**
* @author Arjen Poutsma
@@ -56,12 +59,16 @@ public class ServletServerHttpResponseTests {
headers.add(headerName, headerValue1);
String headerValue2 = "value2";
headers.add(headerName, headerValue2);
+ headers.setContentType(new MediaType("text", "plain", Charset.forName("UTF-8")));
response.close();
assertTrue("Header not set", mockResponse.getHeaderNames().contains(headerName));
List headerValues = mockResponse.getHeaders(headerName);
assertTrue("Header not set", headerValues.contains(headerValue1));
assertTrue("Header not set", headerValues.contains(headerValue2));
+ assertEquals("Invalid Content-Type", "text/plain;charset=UTF-8", mockResponse.getHeader("Content-Type"));
+ assertEquals("Invalid Content-Type", "text/plain;charset=UTF-8", mockResponse.getContentType());
+ assertEquals("Invalid Content-Type", "UTF-8", mockResponse.getCharacterEncoding());
}
@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 38af0e20a12..fe4099c6811 100644
--- a/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolverTests.java
+++ b/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolverTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,7 +45,6 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
-import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.MultipartFile;
@@ -183,11 +182,27 @@ public class RequestParamMethodArgumentResolverTests {
}
@Test(expected = MultipartException.class)
- public void notMultipartRequest() throws Exception {
+ public void isMultipartRequest() throws Exception {
resolver.resolveArgument(paramMultiPartFile, null, webRequest, null);
fail("Expected exception: request is not a multipart request");
}
+ // SPR-9079
+
+ @Test
+ public void isMultipartRequestHttpPut() throws Exception {
+ MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest();
+ MultipartFile expected = new MockMultipartFile("multipartFileList", "Hello World".getBytes());
+ request.addFile(expected);
+ request.setMethod("PUT");
+ webRequest = new ServletWebRequest(request);
+
+ Object actual = resolver.resolveArgument(paramMultipartFileList, null, webRequest, null);
+
+ assertTrue(actual instanceof List);
+ assertEquals(expected, ((List>) actual).get(0));
+ }
+
@Test(expected = IllegalArgumentException.class)
public void missingMultipartFile() throws Exception {
request.setMethod("POST");
diff --git a/spring-web/src/test/java/org/springframework/web/method/annotation/SessionAttributesHandlerTests.java b/spring-web/src/test/java/org/springframework/web/method/annotation/SessionAttributesHandlerTests.java
index 06575004b95..8007e5143bc 100644
--- a/spring-web/src/test/java/org/springframework/web/method/annotation/SessionAttributesHandlerTests.java
+++ b/spring-web/src/test/java/org/springframework/web/method/annotation/SessionAttributesHandlerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,7 +24,6 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.util.HashSet;
-import java.util.Map;
import org.junit.Before;
import org.junit.Test;
@@ -39,17 +38,17 @@ import org.springframework.web.context.request.ServletWebRequest;
/**
* Test fixture with {@link SessionAttributesHandler}.
- *
+ *
* @author Rossen Stoyanchev
*/
public class SessionAttributesHandlerTests {
private Class> handlerType = SessionAttributeHandler.class;
-
+
private SessionAttributesHandler sessionAttributesHandler;
-
+
private SessionAttributeStore sessionAttributeStore;
-
+
private NativeWebRequest request;
@Before
@@ -58,7 +57,7 @@ public class SessionAttributesHandlerTests {
this.sessionAttributesHandler = new SessionAttributesHandler(handlerType, sessionAttributeStore);
this.request = new ServletWebRequest(new MockHttpServletRequest());
}
-
+
@Test
public void isSessionAttribute() throws Exception {
assertTrue(sessionAttributesHandler.isHandlerSessionAttribute("attr1", null));
@@ -72,14 +71,18 @@ public class SessionAttributesHandlerTests {
sessionAttributeStore.storeAttribute(request, "attr1", "value1");
sessionAttributeStore.storeAttribute(request, "attr2", "value2");
sessionAttributeStore.storeAttribute(request, "attr3", new TestBean());
+ sessionAttributeStore.storeAttribute(request, "attr4", new TestBean());
- // Resolve successfully handler session attributes once
- assertTrue(sessionAttributesHandler.isHandlerSessionAttribute("attr1", null));
- assertTrue(sessionAttributesHandler.isHandlerSessionAttribute("attr3", TestBean.class));
+ assertEquals("Named attributes (attr1, attr2) should be 'known' right away",
+ new HashSet(asList("attr1", "attr2")),
+ sessionAttributesHandler.retrieveAttributes(request).keySet());
- Map attributes = sessionAttributesHandler.retrieveAttributes(request);
+ // Resolve 'attr3' by type
+ sessionAttributesHandler.isHandlerSessionAttribute("attr3", TestBean.class);
- assertEquals(new HashSet(asList("attr1", "attr3")), attributes.keySet());
+ assertEquals("Named attributes (attr1, attr2) and resolved attribute (att3) should be 'known'",
+ new HashSet(asList("attr1", "attr2", "attr3")),
+ sessionAttributesHandler.retrieveAttributes(request).keySet());
}
@Test
@@ -88,14 +91,16 @@ public class SessionAttributesHandlerTests {
sessionAttributeStore.storeAttribute(request, "attr2", "value2");
sessionAttributeStore.storeAttribute(request, "attr3", new TestBean());
- // Resolve successfully handler session attributes once
- assertTrue(sessionAttributesHandler.isHandlerSessionAttribute("attr1", null));
- assertTrue(sessionAttributesHandler.isHandlerSessionAttribute("attr3", TestBean.class));
-
sessionAttributesHandler.cleanupAttributes(request);
-
+
assertNull(sessionAttributeStore.retrieveAttribute(request, "attr1"));
- assertNotNull(sessionAttributeStore.retrieveAttribute(request, "attr2"));
+ assertNull(sessionAttributeStore.retrieveAttribute(request, "attr2"));
+ assertNotNull(sessionAttributeStore.retrieveAttribute(request, "attr3"));
+
+ // Resolve 'attr3' by type
+ sessionAttributesHandler.isHandlerSessionAttribute("attr3", TestBean.class);
+ sessionAttributesHandler.cleanupAttributes(request);
+
assertNull(sessionAttributeStore.retrieveAttribute(request, "attr3"));
}
@@ -105,19 +110,14 @@ public class SessionAttributesHandlerTests {
model.put("attr1", "value1");
model.put("attr2", "value2");
model.put("attr3", new TestBean());
-
- // Resolve successfully handler session attributes once
- assertTrue(sessionAttributesHandler.isHandlerSessionAttribute("attr1", null));
- assertTrue(sessionAttributesHandler.isHandlerSessionAttribute("attr2", null));
- assertTrue(sessionAttributesHandler.isHandlerSessionAttribute("attr3", TestBean.class));
-
+
sessionAttributesHandler.storeAttributes(request, model);
-
+
assertEquals("value1", sessionAttributeStore.retrieveAttribute(request, "attr1"));
assertEquals("value2", sessionAttributeStore.retrieveAttribute(request, "attr2"));
assertTrue(sessionAttributeStore.retrieveAttribute(request, "attr3") instanceof TestBean);
}
-
+
@SessionAttributes(value = { "attr1", "attr2" }, types = { TestBean.class })
private static class SessionAttributeHandler {
}
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 303bcd2d157..2d9323ca8d6 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-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,7 +45,7 @@ public class UriComponentsBuilderTests {
URI expected = new URI("http://example.com/foo?bar#baz");
assertEquals("Invalid result URI", expected, result.toUri());
}
-
+
@Test
public void fromPath() throws URISyntaxException {
UriComponents result = UriComponentsBuilder.fromPath("foo").queryParam("bar").fragment("baz").build();
@@ -176,6 +176,15 @@ public class UriComponentsBuilderTests {
assertEquals(Arrays.asList("foo"), result.getPathSegments());
}
+ @Test
+ public void pathSegmentsSomeEmpty() {
+ UriComponentsBuilder builder = UriComponentsBuilder.newInstance().pathSegment("", "foo", "", "bar");
+ UriComponents result = builder.build();
+
+ assertEquals("/foo/bar", result.getPath());
+ assertEquals(Arrays.asList("foo", "bar"), result.getPathSegments());
+ }
+
@Test
public void replacePath() {
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://www.ietf.org/rfc/rfc2396.txt");
@@ -183,26 +192,26 @@ public class UriComponentsBuilderTests {
UriComponents result = builder.build();
assertEquals("http://www.ietf.org/rfc/rfc3986.txt", result.toUriString());
-
+
builder = UriComponentsBuilder.fromUriString("http://www.ietf.org/rfc/rfc2396.txt");
builder.replacePath(null);
result = builder.build();
assertEquals("http://www.ietf.org", result.toUriString());
}
-
+
@Test
public void replaceQuery() {
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://example.com/foo?foo=bar&baz=qux");
builder.replaceQuery("baz=42");
UriComponents result = builder.build();
-
+
assertEquals("http://example.com/foo?baz=42", result.toUriString());
builder = UriComponentsBuilder.fromUriString("http://example.com/foo?foo=bar&baz=qux");
builder.replaceQuery(null);
result = builder.build();
-
+
assertEquals("http://example.com/foo", result.toUriString());
}
@@ -234,13 +243,13 @@ public class UriComponentsBuilderTests {
UriComponentsBuilder builder = UriComponentsBuilder.newInstance().queryParam("baz", "qux", 42);
builder.replaceQueryParam("baz", "xuq", 24);
UriComponents result = builder.build();
-
+
assertEquals("baz=xuq&baz=24", result.getQuery());
builder = UriComponentsBuilder.newInstance().queryParam("baz", "qux", 42);
builder.replaceQueryParam("baz");
result = builder.build();
-
+
assertNull("Query param should have been deleted", result.getQuery());
}
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 00049b00c8b..234a21c0904 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-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -69,4 +69,10 @@ public class UriComponentsTests {
UriComponentsBuilder.fromPath("/fo%2o").build(true);
}
+ @Test
+ public void normalize() {
+ UriComponents uriComponents = UriComponentsBuilder.fromUriString("http://example.com/foo/../bar").build();
+ assertEquals("http://example.com/bar", uriComponents.normalize().toString());
+ }
+
}
diff --git a/spring-web/src/test/java/org/springframework/web/util/UriTemplateTests.java b/spring-web/src/test/java/org/springframework/web/util/UriTemplateTests.java
index f0099328cd6..ba2d7a796d2 100644
--- a/spring-web/src/test/java/org/springframework/web/util/UriTemplateTests.java
+++ b/spring-web/src/test/java/org/springframework/web/util/UriTemplateTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -89,6 +89,15 @@ public class UriTemplateTests {
assertEquals("Invalid expanded template", new URI("http://example.com/hotel%20list/Z%C3%BCrich"), result);
}
+ @Test(expected = IllegalArgumentException.class)
+ public void expandMapUnboundVariables() throws Exception {
+ Map uriVariables = new HashMap(2);
+ uriVariables.put("booking", "42");
+ uriVariables.put("bar", "1");
+ UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}");
+ template.expand(uriVariables);
+ }
+
@Test
public void expandEncoded() throws Exception {
UriTemplate template = new UriTemplate("http://example.com/hotel list/{hotel}");
diff --git a/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/context/PortletContextResource.java b/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/context/PortletContextResource.java
index 599c9e3e54e..86f5794b28d 100644
--- a/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/context/PortletContextResource.java
+++ b/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/context/PortletContextResource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -107,6 +107,28 @@ public class PortletContextResource extends AbstractFileResolvingResource implem
}
}
+ /**
+ * This implementation delegates to PortletContext.getResourceAsStream,
+ * which returns null in case of a non-readable resource (e.g. a directory).
+ * @see javax.portlet.PortletContext#getResourceAsStream(String)
+ */
+ @Override
+ public boolean isReadable() {
+ InputStream is = this.portletContext.getResourceAsStream(this.path);
+ if (is != null) {
+ try {
+ is.close();
+ }
+ catch (IOException ex) {
+ // ignore
+ }
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+
/**
* This implementation delegates to PortletContext.getResourceAsStream,
* but throws a FileNotFoundException if not found.
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java
index 7bac3dfb1ba..d6eddaf7d7e 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java
@@ -840,14 +840,14 @@ public class DispatcherServlet extends FrameworkServlet {
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
+
+ FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
+ if (inputFlashMap != null) {
+ request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
+ }
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
- Map flashMap = this.flashMapManager.getFlashMapForRequest(request);
- if (flashMap != null) {
- request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, flashMap);
- }
-
try {
doDispatch(request, response);
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/FlashMap.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/FlashMap.java
index cb6450ca89b..08b7f736a4a 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/FlashMap.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/FlashMap.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,12 +32,11 @@ import org.springframework.util.StringUtils;
* A FlashMap can be set up with a request path and request parameters to
* help identify the target request. Without this information, a FlashMap is
* made available to the next request, which may or may not be the intended
- * recipient. On a redirect, the target URL is known and for example
- * {@code org.springframework.web.servlet.view.RedirectView} has the
- * opportunity to automatically update the current FlashMap with target
- * URL information.
+ * recipient. On a redirect, the target URL is known and a FlashMap can be
+ * updated with that information. This is done automatically when the
+ * {@code org.springframework.web.servlet.view.RedirectView} is used.
*
- *
Annotated controllers will usually not use this type directly.
+ *
Note: annotated controllers will usually not use FlashMap directly.
* See {@code org.springframework.web.servlet.mvc.support.RedirectAttributes}
* for an overview of using flash attributes in annotated controllers.
*
@@ -58,25 +57,6 @@ public final class FlashMap extends HashMap implements Comparabl
private int timeToLive;
- private final int createdBy;
-
- /**
- * Create a new instance with an id uniquely identifying the creator of
- * this FlashMap.
- * @param createdBy identifies the FlashMapManager instance that created
- * and will manage this FlashMap instance (e.g. via a hashCode)
- */
- public FlashMap(int createdBy) {
- this.createdBy = createdBy;
- }
-
- /**
- * Create a new instance.
- */
- public FlashMap() {
- this.createdBy = 0;
- }
-
/**
* Provide a URL path to help identify the target request for this FlashMap.
* The path may be absolute (e.g. /application/resource) or relative to the
@@ -96,7 +76,6 @@ public final class FlashMap extends HashMap implements Comparabl
/**
* Provide request parameters identifying the request for this FlashMap.
- * Null or empty keys and values are skipped.
* @param params a Map with the names and values of expected parameters.
*/
public FlashMap addTargetRequestParams(MultiValueMap params) {
@@ -112,8 +91,8 @@ public final class FlashMap extends HashMap implements Comparabl
/**
* Provide a request parameter identifying the request for this FlashMap.
- * @param name the expected parameter name, skipped if {@code null}
- * @param value the expected parameter value, skipped if {@code null}
+ * @param name the expected parameter name, skipped if empty or {@code null}
+ * @param value the expected value, skipped if empty or {@code null}
*/
public FlashMap addTargetRequestParam(String name, String value) {
if (StringUtils.hasText(name) && StringUtils.hasText(value)) {
@@ -151,13 +130,6 @@ public final class FlashMap extends HashMap implements Comparabl
}
}
- /**
- * Whether the given id matches the id of the creator of this FlashMap.
- */
- public boolean isCreatedBy(int createdBy) {
- return this.createdBy == createdBy;
- }
-
/**
* Compare two FlashMaps and prefer the one that specifies a target URL
* path or has more target URL parameters. Before comparing FlashMap
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/FlashMapManager.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/FlashMapManager.java
index 8440bd636c5..078713194b9 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/FlashMapManager.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/FlashMapManager.java
@@ -16,44 +16,43 @@
package org.springframework.web.servlet;
-import java.util.Map;
-
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* A strategy interface for retrieving and saving FlashMap instances.
* See {@link FlashMap} for a general overview of flash attributes.
- *
+ *
* @author Rossen Stoyanchev
* @since 3.1
- *
+ *
* @see FlashMap
*/
public interface FlashMapManager {
/**
- * Get a Map with flash attributes saved by a previous request.
- * See {@link FlashMap} for details on how FlashMap instances
- * identifies the target requests they're saved for.
- * If found, the Map is removed from the underlying storage.
+ * Find a FlashMap saved by a previous request that matches to the current
+ * request, remove it from underlying storage, and also remove other
+ * expired FlashMap instances.
+ * This method is invoked in the beginning of every request in contrast
+ * to {@link #saveOutputFlashMap}, which is invoked only when there are
+ * flash attributes to be saved - i.e. before a redirect.
* @param request the current request
- * @return a read-only Map with flash attributes or {@code null}
+ * @param response the current response
+ * @return a FlashMap matching the current request or {@code null}
*/
- Map getFlashMapForRequest(HttpServletRequest request);
+ FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response);
/**
- * Save the given FlashMap, in some underlying storage, mark the beginning
- * of its expiration period, and remove other expired FlashMap instances.
- * The method has no impact if the FlashMap is empty and there are no
- * expired FlashMap instances to be removed.
+ * Save the given FlashMap, in some underlying storage and set the start
+ * of its expiration period.
*