diff --git a/build.gradle b/build.gradle index 3fdd181b0b..c77a580a06 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ buildscript { configure(allprojects) { project -> group = "org.springframework" version = qualifyVersionIfNecessary(version) - + // The following is a work-around until the Gradle build uses // Ant 1.9.x by default. This is necessary to avoid the // "Class not found: javac1.8" issue with Ant versions prior to 1.9.x @@ -305,6 +305,9 @@ project("spring-context") { testCompile("javax.inject:javax.inject-tck:1") } + // pick up RmiInvocationWrapperRTD.xml in src/main + sourceSets.main.resources.srcDirs += "src/main/java" + test { jvmArgs = ["-disableassertions:org.aspectj.weaver.UnresolvedType"] // SPR-7989 } @@ -658,7 +661,7 @@ project("spring-test") { optional(project(":spring-webmvc")) optional(project(":spring-webmvc-portlet"), ) optional("junit:junit:${junitVersion}") - optional("org.testng:testng:6.5.2") + optional("org.testng:testng:6.8.5") optional("javax.servlet:javax.servlet-api:3.0.1") optional("javax.servlet.jsp:jsp-api:2.1") optional("javax.portlet:portlet-api:2.0") @@ -965,7 +968,7 @@ configure(rootProject) { gradleVersion = "1.6" doLast() { - def gradleOpts = "-XX:MaxPermSize=1024m -Xmx1024m" + def gradleOpts = "-XX:MaxMetaspaceSize=1024m -Xmx1024m" def gradleBatOpts = "$gradleOpts -XX:MaxHeapSize=256m" File wrapperFile = file("gradlew") wrapperFile.text = wrapperFile.text.replace("DEFAULT_JVM_OPTS=", diff --git a/gradlew b/gradlew index a1787c628f..26c714686e 100755 --- a/gradlew +++ b/gradlew @@ -7,7 +7,7 @@ ############################################################################## # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -GRADLE_OPTS="-XX:MaxPermSize=1024m -Xmx1024m $GRADLE_OPTS" +GRADLE_OPTS="-XX:MaxMetaspaceSize=1024m -Xmx1024m $GRADLE_OPTS" DEFAULT_JVM_OPTS="" APP_NAME="Gradle" diff --git a/gradlew.bat b/gradlew.bat index e05bf9133f..994a483869 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -9,7 +9,7 @@ if "%OS%"=="Windows_NT" setlocal @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set GRADLE_OPTS=-XX:MaxPermSize=1024m -Xmx1024m -XX:MaxHeapSize=256m %GRADLE_OPTS% +set GRADLE_OPTS=-XX:MaxMetaspaceSize=1024m -Xmx1024m -XX:MaxHeapSize=256m %GRADLE_OPTS% set DEFAULT_JVM_OPTS= set DIRNAME=%~dp0 diff --git a/spring-aop/src/main/java/org/springframework/aop/target/CommonsPoolTargetSource.java b/spring-aop/src/main/java/org/springframework/aop/target/CommonsPoolTargetSource.java index 08f16258a1..9b44df6217 100644 --- a/spring-aop/src/main/java/org/springframework/aop/target/CommonsPoolTargetSource.java +++ b/spring-aop/src/main/java/org/springframework/aop/target/CommonsPoolTargetSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,7 +38,7 @@ import org.springframework.core.Constants; * of configuration properties that are relevant to your chosen implementation. * *

The {@code testOnBorrow}, {@code testOnReturn} and {@code testWhileIdle} - * properties are explictly not mirrored because the implementation of + * properties are explicitly not mirrored because the implementation of * {@code PoolableObjectFactory} used by this class does not implement * meaningful validation. All exposed Commons Pool properties use the corresponding * Commons Pool defaults: for example, diff --git a/spring-aspects/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java b/spring-aspects/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java index 8c7d3a522b..31a5adbd52 100644 --- a/spring-aspects/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java +++ b/spring-aspects/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java @@ -163,4 +163,11 @@ public class AnnotatedClassCacheableService implements CacheableService public Object multiUpdate(Object arg1) { return arg1; } + + @Override + @CachePut(value="primary", key="#result.id") + public TestEntity putRefersToResult(TestEntity arg1) { + arg1.setId(Long.MIN_VALUE); + return arg1; + } } diff --git a/spring-aspects/src/test/java/org/springframework/cache/config/CacheableService.java b/spring-aspects/src/test/java/org/springframework/cache/config/CacheableService.java index 98c1da1e03..6e33a8a471 100644 --- a/spring-aspects/src/test/java/org/springframework/cache/config/CacheableService.java +++ b/spring-aspects/src/test/java/org/springframework/cache/config/CacheableService.java @@ -70,4 +70,6 @@ public interface CacheableService { T multiConditionalCacheAndEvict(Object arg1); T multiUpdate(Object arg1); + + TestEntity putRefersToResult(TestEntity arg1); } diff --git a/spring-aspects/src/test/java/org/springframework/cache/config/DefaultCacheableService.java b/spring-aspects/src/test/java/org/springframework/cache/config/DefaultCacheableService.java index d29d43894e..37fe772c47 100644 --- a/spring-aspects/src/test/java/org/springframework/cache/config/DefaultCacheableService.java +++ b/spring-aspects/src/test/java/org/springframework/cache/config/DefaultCacheableService.java @@ -171,4 +171,11 @@ public class DefaultCacheableService implements CacheableService { public Long multiUpdate(Object arg1) { return Long.valueOf(arg1.toString()); } + + @Override + @CachePut(value="primary", key="#result.id") + public TestEntity putRefersToResult(TestEntity arg1) { + arg1.setId(Long.MIN_VALUE); + return arg1; + } } diff --git a/spring-aspects/src/test/java/org/springframework/cache/config/TestEntity.java b/spring-aspects/src/test/java/org/springframework/cache/config/TestEntity.java new file mode 100644 index 0000000000..4c43007158 --- /dev/null +++ b/spring-aspects/src/test/java/org/springframework/cache/config/TestEntity.java @@ -0,0 +1,56 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cache.config; + +import org.springframework.util.ObjectUtils; + +/** + * Simple test entity for use with caching tests. + * + * @author Michael Plšd + */ +public class TestEntity { + + private Long id; + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + @Override + public int hashCode() { + return ObjectUtils.nullSafeHashCode(this.id); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj == null) { + return false; + } + if (obj instanceof TestEntity) { + return ObjectUtils.nullSafeEquals(this.id, ((TestEntity) obj).id); + } + return false; + } +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index 840e4b816f..7bcc5caba1 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -31,7 +31,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -469,11 +468,14 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto @Override public Map getBeansWithAnnotation(Class annotationType) { - Set beanNames = new LinkedHashSet(getBeanDefinitionCount()); - beanNames.addAll(Arrays.asList(getBeanDefinitionNames())); - beanNames.addAll(Arrays.asList(getSingletonNames())); Map results = new LinkedHashMap(); - for (String beanName : beanNames) { + for (String beanName : getBeanDefinitionNames()) { + BeanDefinition beanDefinition = getBeanDefinition(beanName); + if (!beanDefinition.isAbstract() && (findAnnotationOnBean(beanName, annotationType) != null)) { + results.put(beanName, getBean(beanName)); + } + } + for (String beanName : getSingletonNames()) { if (findAnnotationOnBean(beanName, annotationType) != null) { results.put(beanName, getBean(beanName)); } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultDocumentLoader.java b/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultDocumentLoader.java index f86765cb62..0cca9e8be0 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultDocumentLoader.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultDocumentLoader.java @@ -91,8 +91,8 @@ public class DefaultDocumentLoader implements DocumentLoader { factory.setNamespaceAware(namespaceAware); if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) { + factory.setFeature("http://apache.org/xml/features/validation/schema", false); factory.setValidating(true); - if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) { // Enforce namespace aware for XSD... factory.setNamespaceAware(true); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/FactoryBeanTests-abstract.xml b/spring-beans/src/test/java/org/springframework/beans/factory/FactoryBeanTests-abstract.xml new file mode 100644 index 0000000000..e82ccdc59b --- /dev/null +++ b/spring-beans/src/test/java/org/springframework/beans/factory/FactoryBeanTests-abstract.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/FactoryBeanTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/FactoryBeanTests.java index 62c85b60b2..5d7808f426 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/FactoryBeanTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/FactoryBeanTests.java @@ -26,6 +26,7 @@ import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.core.io.Resource; +import org.springframework.stereotype.Component; import org.springframework.util.Assert; /** @@ -38,6 +39,7 @@ public final class FactoryBeanTests { private static final Class CLASS = FactoryBeanTests.class; private static final Resource RETURNS_NULL_CONTEXT = qualifiedResource(CLASS, "returnsNull.xml"); private static final Resource WITH_AUTOWIRING_CONTEXT = qualifiedResource(CLASS, "withAutowiring.xml"); + private static final Resource ABSTRACT_CONTEXT = qualifiedResource(CLASS, "abstract.xml"); @Test public void testFactoryBeanReturnsNull() throws Exception { @@ -80,6 +82,20 @@ public final class FactoryBeanTests { assertSame(gamma, beta.getGamma()); } + @Test + public void testAbstractFactoryBeanViaAnnotation() throws Exception { + DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); + new XmlBeanDefinitionReader(factory).loadBeanDefinitions(ABSTRACT_CONTEXT); + factory.getBeansWithAnnotation(Component.class); + } + + @Test + public void testAbstractFactoryBeanViaType() throws Exception { + DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); + new XmlBeanDefinitionReader(factory).loadBeanDefinitions(ABSTRACT_CONTEXT); + factory.getBeansOfType(AbstractFactoryBean.class); + } + public static class NullReturningFactoryBean implements FactoryBean { @@ -152,6 +168,7 @@ public final class FactoryBeanTests { } + @Component public static class BetaFactoryBean implements FactoryBean { private Beta beta; @@ -176,4 +193,7 @@ public final class FactoryBeanTests { } } + public abstract static class AbstractFactoryBean implements FactoryBean { + } + } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java index 2480f64fac..f1e844ec02 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java @@ -16,13 +16,9 @@ package org.springframework.beans.factory.support; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; @@ -46,12 +42,12 @@ import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.UrlResource; import org.springframework.tests.Assume; import org.springframework.tests.TestGroup; - import org.springframework.tests.sample.beans.GenericBean; import org.springframework.tests.sample.beans.GenericIntegerBean; import org.springframework.tests.sample.beans.GenericSetOfIntegerBean; import org.springframework.tests.sample.beans.TestBean; +import static org.junit.Assert.*; /** * @author Juergen Hoeller @@ -115,7 +111,7 @@ public class BeanFactoryGenericsTests { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(GenericIntegerBean.class); - List input = new ArrayList(); + List input = new ArrayList(); input.add(1); rbd.getPropertyValues().add("testBeanList", input); @@ -655,18 +651,22 @@ public class BeanFactoryGenericsTests { } /** - * Tests support for parameterized {@code factory-method} declarations such - * as Mockito {@code mock()} method which has the following signature. - * - *
{@code
+	 * Tests support for parameterized static {@code factory-method} declarations such as
+	 * Mockito's {@code mock()} method which has the following signature.
+	 * 
+	 * 
+	 * {@code
 	 * public static  T mock(Class classToMock)
-	 * }
- * + * } + *
+ * + *

* See SPR-9493 + * * @since 3.2 */ @Test - public void parameterizedFactoryMethod() { + public void parameterizedStaticFactoryMethod() { RootBeanDefinition rbd = new RootBeanDefinition(Mockito.class); rbd.setFactoryMethodName("mock"); rbd.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class); @@ -678,6 +678,39 @@ public class BeanFactoryGenericsTests { assertEquals(1, beans.size()); } + /** + * Tests support for parameterized instance {@code factory-method} declarations such + * as EasyMock's {@code IMocksControl.createMock()} method which has the following + * signature. + * + *

+	 * {@code
+	 * public  T createMock(Class toMock)
+	 * }
+	 * 
+ * + *

+ * See SPR-10411 + * + * @since 4.0 + */ + @Test + public void parameterizedInstanceFactoryMethod() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + + RootBeanDefinition rbd = new RootBeanDefinition(MocksControl.class); + bf.registerBeanDefinition("mocksControl", rbd); + + rbd = new RootBeanDefinition(); + rbd.setFactoryBeanName("mocksControl"); + rbd.setFactoryMethodName("createMock"); + rbd.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class); + + bf.registerBeanDefinition("mock", rbd); + + Map beans = bf.getBeansOfType(Runnable.class); + assertEquals(1, beans.size()); + } @SuppressWarnings("serial") public static class NamedUrlList extends LinkedList { @@ -722,4 +755,25 @@ public class BeanFactoryGenericsTests { } } + /** + * Pseudo-implementation of EasyMock's {@code MocksControl} class. + */ + public static class MocksControl { + + @SuppressWarnings("unchecked") + public T createMock(Class toMock) { + + return (T) Proxy.newProxyInstance( + BeanFactoryGenericsTests.class.getClassLoader(), + new Class[] { toMock }, new InvocationHandler() { + + @Override + public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable { + throw new UnsupportedOperationException("mocked!"); + } + }); + } + } + } diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java b/spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java index 222d3308f4..dc49cd0383 100644 --- a/spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java +++ b/spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java @@ -42,14 +42,18 @@ import org.springframework.util.CollectionUtils; public abstract class AbstractCachingConfiguration implements ImportAware { protected AnnotationAttributes enableCaching; + protected CacheManager cacheManager; + protected KeyGenerator keyGenerator; @Autowired(required=false) private Collection cacheManagerBeans; + @Autowired(required=false) private Collection cachingConfigurers; + @Override public void setImportMetadata(AnnotationMetadata importMetadata) { this.enableCaching = AnnotationAttributes.fromMap( @@ -59,6 +63,7 @@ public abstract class AbstractCachingConfiguration implements ImportAware { importMetadata.getClassName()); } + /** * Determine which {@code CacheManager} bean to use. Prefer the result of * {@link CachingConfigurer#cacheManager()} over any by-type matching. If none, fall diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/EnableCaching.java b/spring-context/src/main/java/org/springframework/cache/annotation/EnableCaching.java index 771487a56f..2a477b9e8b 100644 --- a/spring-context/src/main/java/org/springframework/cache/annotation/EnableCaching.java +++ b/spring-context/src/main/java/org/springframework/cache/annotation/EnableCaching.java @@ -120,9 +120,9 @@ import org.springframework.core.Ordered; * customizing the strategy for cache key generation, per Spring's {@link * org.springframework.cache.interceptor.KeyGenerator KeyGenerator} SPI. Normally, * {@code @EnableCaching} will configure Spring's - * {@link org.springframework.cache.interceptor.DefaultKeyGenerator DefaultKeyGenerator} + * {@link org.springframework.cache.interceptor.SimpleKeyGenerator SimpleKeyGenerator} * for this purpose, but when implementing {@code CachingConfigurer}, a key generator - * must be provided explicitly. Return {@code new DefaultKeyGenerator()} from this method + * must be provided explicitly. Return {@code new SimpleKeyGenerator()} from this method * if no customization is necessary. See {@link CachingConfigurer} Javadoc for further * details. * diff --git a/spring-context/src/main/java/org/springframework/cache/config/CacheAdviceParser.java b/spring-context/src/main/java/org/springframework/cache/config/CacheAdviceParser.java index 5092f474c7..b966fc232d 100644 --- a/spring-context/src/main/java/org/springframework/cache/config/CacheAdviceParser.java +++ b/spring-context/src/main/java/org/springframework/cache/config/CacheAdviceParser.java @@ -48,69 +48,17 @@ import org.w3c.dom.Element; */ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser { - /** - * Simple, reusable class used for overriding defaults. - * - * @author Costin Leau - */ - private static class Props { - - private String key; - private String condition; - private String method; - private String[] caches = null; - - Props(Element root) { - String defaultCache = root.getAttribute("cache"); - key = root.getAttribute("key"); - condition = root.getAttribute("condition"); - method = root.getAttribute(METHOD_ATTRIBUTE); - - if (StringUtils.hasText(defaultCache)) { - caches = StringUtils.commaDelimitedListToStringArray(defaultCache.trim()); - } - } - - T merge(Element element, ReaderContext readerCtx, T op) { - String cache = element.getAttribute("cache"); - - // sanity check - String[] localCaches = caches; - if (StringUtils.hasText(cache)) { - localCaches = StringUtils.commaDelimitedListToStringArray(cache.trim()); - } else { - if (caches == null) { - readerCtx.error("No cache specified specified for " + element.getNodeName(), element); - } - } - op.setCacheNames(localCaches); - - op.setKey(getAttributeValue(element, "key", this.key)); - op.setCondition(getAttributeValue(element, "condition", this.condition)); - - return op; - } - - String merge(Element element, ReaderContext readerCtx) { - String m = element.getAttribute(METHOD_ATTRIBUTE); - - if (StringUtils.hasText(m)) { - return m.trim(); - } - if (StringUtils.hasText(method)) { - return method; - } - readerCtx.error("No method specified for " + element.getNodeName(), element); - return null; - } - } - private static final String CACHEABLE_ELEMENT = "cacheable"; + private static final String CACHE_EVICT_ELEMENT = "cache-evict"; + private static final String CACHE_PUT_ELEMENT = "cache-put"; + private static final String METHOD_ATTRIBUTE = "method"; + private static final String DEFS_ELEMENT = "caching"; + @Override protected Class getBeanClass(Element element) { return CacheInterceptor.class; @@ -226,4 +174,66 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser { return defaultValue; } + + /** + * Simple, reusable class used for overriding defaults. + * + * @author Costin Leau + */ + private static class Props { + + private String key; + + private String condition; + + private String method; + + private String[] caches = null; + + + Props(Element root) { + String defaultCache = root.getAttribute("cache"); + key = root.getAttribute("key"); + condition = root.getAttribute("condition"); + method = root.getAttribute(METHOD_ATTRIBUTE); + + if (StringUtils.hasText(defaultCache)) { + caches = StringUtils.commaDelimitedListToStringArray(defaultCache.trim()); + } + } + + + T merge(Element element, ReaderContext readerCtx, T op) { + String cache = element.getAttribute("cache"); + + // sanity check + String[] localCaches = caches; + if (StringUtils.hasText(cache)) { + localCaches = StringUtils.commaDelimitedListToStringArray(cache.trim()); + } else { + if (caches == null) { + readerCtx.error("No cache specified specified for " + element.getNodeName(), element); + } + } + op.setCacheNames(localCaches); + + op.setKey(getAttributeValue(element, "key", this.key)); + op.setCondition(getAttributeValue(element, "condition", this.condition)); + + return op; + } + + String merge(Element element, ReaderContext readerCtx) { + String m = element.getAttribute(METHOD_ATTRIBUTE); + + if (StringUtils.hasText(m)) { + return m.trim(); + } + if (StringUtils.hasText(method)) { + return method; + } + readerCtx.error("No method specified for " + element.getNodeName(), element); + return null; + } + } } diff --git a/spring-context/src/main/java/org/springframework/cache/config/CacheNamespaceHandler.java b/spring-context/src/main/java/org/springframework/cache/config/CacheNamespaceHandler.java index 238a3d2805..a02d359a20 100644 --- a/spring-context/src/main/java/org/springframework/cache/config/CacheNamespaceHandler.java +++ b/spring-context/src/main/java/org/springframework/cache/config/CacheNamespaceHandler.java @@ -35,6 +35,7 @@ import org.w3c.dom.Element; public class CacheNamespaceHandler extends NamespaceHandlerSupport { static final String CACHE_MANAGER_ATTRIBUTE = "cache-manager"; + static final String DEFAULT_CACHE_MANAGER_BEAN_NAME = "cacheManager"; static String extractCacheManager(Element element) { diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java index 8f8fc76fd2..b0da1a4ec5 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java @@ -19,8 +19,8 @@ package org.springframework.cache.interceptor; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.Map; +import java.util.Collections; +import java.util.List; import java.util.Set; import org.apache.commons.logging.Log; @@ -28,11 +28,15 @@ import org.apache.commons.logging.LogFactory; import org.springframework.aop.framework.AopProxyUtils; import org.springframework.beans.factory.InitializingBean; import org.springframework.cache.Cache; +import org.springframework.cache.Cache.ValueWrapper; import org.springframework.cache.CacheManager; +import org.springframework.cache.support.SimpleValueWrapper; import org.springframework.expression.EvaluationContext; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; /** @@ -61,23 +65,212 @@ import org.springframework.util.StringUtils; */ public abstract class CacheAspectSupport implements InitializingBean { - public interface Invoker { - Object invoke(); - } - protected final Log logger = LogFactory.getLog(getClass()); + private CacheManager cacheManager; private CacheOperationSource cacheOperationSource; private final ExpressionEvaluator evaluator = new ExpressionEvaluator(); - private KeyGenerator keyGenerator = new DefaultKeyGenerator(); + private KeyGenerator keyGenerator = new SimpleKeyGenerator(); private boolean initialized = false; - private static final String CACHEABLE = "cacheable", UPDATE = "cacheupdate", EVICT = "cacheevict"; + + @Override + public void afterPropertiesSet() { + Assert.state(this.cacheManager != null, "'cacheManager' is required"); + Assert.state(this.cacheOperationSource != null, "The 'cacheOperationSources' " + + "property is required: If there are no cacheable methods, " + + "then don't use a cache aspect."); + this.initialized = true; + } + + /** + * Convenience method to return a String representation of this Method + * for use in logging. Can be overridden in subclasses to provide a + * different identifier for the given method. + * @param method the method we're interested in + * @param targetClass class the method is on + * @return log message identifying this method + * @see org.springframework.util.ClassUtils#getQualifiedMethodName + */ + protected String methodIdentification(Method method, Class targetClass) { + Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass); + return ClassUtils.getQualifiedMethodName(specificMethod); + } + + protected Collection getCaches(CacheOperation operation) { + Set cacheNames = operation.getCacheNames(); + Collection caches = new ArrayList(cacheNames.size()); + for (String cacheName : cacheNames) { + Cache cache = this.cacheManager.getCache(cacheName); + Assert.notNull(cache, "Cannot find cache named [" + cacheName + "] for " + operation); + caches.add(cache); + } + return caches; + } + + protected CacheOperationContext getOperationContext(CacheOperation operation, + Method method, Object[] args, Object target, Class targetClass) { + return new CacheOperationContext(operation, method, args, target, targetClass); + } + + protected Object execute(Invoker invoker, Object target, Method method, Object[] args) { + + // check whether aspect is enabled + // to cope with cases where the AJ is pulled in automatically + if (this.initialized) { + Class targetClass = getTargetClass(target); + Collection operations = getCacheOperationSource(). + getCacheOperations(method, targetClass); + if (!CollectionUtils.isEmpty(operations)) { + return execute(invoker, new CacheOperationContexts(operations, + method, args, target, targetClass)); + } + } + + return invoker.invoke(); + } + + private Class getTargetClass(Object target) { + Class targetClass = AopProxyUtils.ultimateTargetClass(target); + if (targetClass == null && target != null) { + targetClass = target.getClass(); + } + return targetClass; + } + + private Object execute(Invoker invoker, CacheOperationContexts contexts) { + + // Process any early evictions + processCacheEvicts(contexts.get(CacheEvictOperation.class), true, ExpressionEvaluator.NO_RESULT); + + // Collect puts from any @Cachable miss + List cachePutRequests = new ArrayList(); + collectPutRequests(contexts.get(CacheableOperation.class), + ExpressionEvaluator.NO_RESULT, cachePutRequests, true); + + ValueWrapper result = null; + + // We only attempt to get a cached result if there are no put requests + if(cachePutRequests.isEmpty() && contexts.get(CachePutOperation.class).isEmpty()) { + result = findCachedResult(contexts.get(CacheableOperation.class)); + } + + // Invoke the method if don't have a cache hit + if(result == null) { + result = new SimpleValueWrapper(invoker.invoke()); + } + + // Collect any explicit @CachePuts + collectPutRequests(contexts.get(CachePutOperation.class), result.get(), + cachePutRequests, false); + + // Process any collected put requests, either from @CachePut or a @Cacheable miss + for (CachePutRequest cachePutRequest : cachePutRequests) { + cachePutRequest.apply(result.get()); + } + + // Process any late evictions + processCacheEvicts(contexts.get(CacheEvictOperation.class), false, result.get()); + + return result.get(); + } + + private void processCacheEvicts(Collection contexts, + boolean beforeInvocation, Object result) { + for (CacheOperationContext context : contexts) { + CacheEvictOperation operation = (CacheEvictOperation) context.operation; + if (beforeInvocation == operation.isBeforeInvocation() && + isConditionPassing(context, result)) { + performCacheEvict(context, operation, result); + } + } + } + + private void performCacheEvict(CacheOperationContext context, + CacheEvictOperation operation, Object result) { + Object key = null; + for (Cache cache : context.getCaches()) { + if (operation.isCacheWide()) { + logInvalidating(context, operation, null); + cache.clear(); + } else { + if(key == null) { + key = context.generateKey(result); + } + logInvalidating(context, operation, key); + cache.evict(key); + } + } + } + + private void logInvalidating(CacheOperationContext context, + CacheEvictOperation operation, Object key) { + if (this.logger.isTraceEnabled()) { + this.logger.trace("Invalidating " + + (key == null ? "entire cache" : "cache key " + key) + + " for operation " + operation + " on method " + context.method); + } + } + + private void collectPutRequests(Collection contexts, + Object result, Collection putRequests, boolean whenNotInCache) { + for (CacheOperationContext context : contexts) { + if (isConditionPassing(context, result)) { + Object key = generateKey(context, result); + if (!whenNotInCache || findInCaches(context, key) == null) { + putRequests.add(new CachePutRequest(context, key)); + } + } + } + } + + private Cache.ValueWrapper findCachedResult(Collection contexts) { + ValueWrapper result = null; + for (CacheOperationContext context : contexts) { + if (isConditionPassing(context, ExpressionEvaluator.NO_RESULT)) { + if(result == null) { + result = findInCaches(context, + generateKey(context, ExpressionEvaluator.NO_RESULT)); + } + } + } + return result; + } + + private Cache.ValueWrapper findInCaches(CacheOperationContext context, Object key) { + for (Cache cache : context.getCaches()) { + Cache.ValueWrapper wrapper = cache.get(key); + if (wrapper != null) { + return wrapper; + } + } + return null; + } + + private boolean isConditionPassing(CacheOperationContext context, Object result) { + boolean passing = context.isConditionPassing(result); + if(!passing && this.logger.isTraceEnabled()) { + this.logger.trace("Cache condition failed on method " + context.method + " for operation " + context.operation); + } + return passing; + } + + private Object generateKey(CacheOperationContext context, Object result) { + Object key = context.generateKey(result); + Assert.notNull(key, "Null key returned for cache operation (maybe you " + + "are using named params on classes without debug info?) " + + context.operation); + if (this.logger.isTraceEnabled()) { + this.logger.trace("Computed cache key " + key + " for operation " + context.operation); + } + return key; + } + /** * Set the CacheManager that this cache aspect should delegate to. @@ -116,7 +309,7 @@ public abstract class CacheAspectSupport implements InitializingBean { /** * Set the KeyGenerator for this cache aspect. - * Default is {@link DefaultKeyGenerator}. + * Default is {@link SimpleKeyGenerator}. */ public void setKeyGenerator(KeyGenerator keyGenerator) { this.keyGenerator = keyGenerator; @@ -129,306 +322,30 @@ public abstract class CacheAspectSupport implements InitializingBean { return this.keyGenerator; } - @Override - public void afterPropertiesSet() { - if (this.cacheManager == null) { - throw new IllegalStateException("'cacheManager' is required"); - } - if (this.cacheOperationSource == null) { - throw new IllegalStateException("The 'cacheOperationSources' property is required: " - + "If there are no cacheable methods, then don't use a cache aspect."); - } - this.initialized = true; + public interface Invoker { + Object invoke(); } - /** - * Convenience method to return a String representation of this Method - * for use in logging. Can be overridden in subclasses to provide a - * different identifier for the given method. - * @param method the method we're interested in - * @param targetClass class the method is on - * @return log message identifying this method - * @see org.springframework.util.ClassUtils#getQualifiedMethodName - */ - protected String methodIdentification(Method method, Class targetClass) { - Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass); - return ClassUtils.getQualifiedMethodName(specificMethod); - } - protected Collection getCaches(CacheOperation operation) { - Set cacheNames = operation.getCacheNames(); - Collection caches = new ArrayList(cacheNames.size()); - for (String cacheName : cacheNames) { - Cache cache = this.cacheManager.getCache(cacheName); - if (cache == null) { - throw new IllegalArgumentException("Cannot find cache named [" + cacheName + "] for " + operation); - } - caches.add(cache); - } - return caches; - } + private class CacheOperationContexts { - protected CacheOperationContext getOperationContext(CacheOperation operation, Method method, Object[] args, - Object target, Class targetClass) { + private final MultiValueMap, CacheOperationContext> contexts = + new LinkedMultiValueMap, CacheOperationContext>(); - return new CacheOperationContext(operation, method, args, target, targetClass); - } - - protected Object execute(Invoker invoker, Object target, Method method, Object[] args) { - // check whether aspect is enabled - // to cope with cases where the AJ is pulled in automatically - if (!this.initialized) { - return invoker.invoke(); - } - - // get backing class - Class targetClass = AopProxyUtils.ultimateTargetClass(target); - if (targetClass == null && target != null) { - targetClass = target.getClass(); - } - final Collection cacheOp = getCacheOperationSource().getCacheOperations(method, targetClass); - - // analyze caching information - if (!CollectionUtils.isEmpty(cacheOp)) { - Map> ops = createOperationContext(cacheOp, method, args, target, targetClass); - - // start with evictions - inspectBeforeCacheEvicts(ops.get(EVICT)); - - // follow up with cacheable - CacheStatus status = inspectCacheables(ops.get(CACHEABLE)); - - Object retVal = null; - Map updates = inspectCacheUpdates(ops.get(UPDATE)); - - if (status != null) { - if (status.updateRequired) { - updates.putAll(status.cUpdates); - } - // return cached object - else { - return status.retVal; - } - } - - retVal = invoker.invoke(); - - inspectAfterCacheEvicts(ops.get(EVICT), retVal); - - if (!updates.isEmpty()) { - update(updates, retVal); - } - - return retVal; - } - - return invoker.invoke(); - } - - private void inspectBeforeCacheEvicts(Collection evictions) { - inspectCacheEvicts(evictions, true, ExpressionEvaluator.NO_RESULT); - } - - private void inspectAfterCacheEvicts(Collection evictions, - Object result) { - inspectCacheEvicts(evictions, false, result); - } - - private void inspectCacheEvicts(Collection evictions, - boolean beforeInvocation, Object result) { - - if (!evictions.isEmpty()) { - - boolean log = logger.isTraceEnabled(); - - for (CacheOperationContext context : evictions) { - CacheEvictOperation evictOp = (CacheEvictOperation) context.operation; - - if (beforeInvocation == evictOp.isBeforeInvocation()) { - if (context.isConditionPassing(result)) { - // for each cache - // lazy key initialization - Object key = null; - - for (Cache cache : context.getCaches()) { - // cache-wide flush - if (evictOp.isCacheWide()) { - cache.clear(); - if (log) { - logger.trace("Invalidating entire cache for operation " + evictOp + " on method " + context.method); - } - } else { - // check key - if (key == null) { - key = context.generateKey(); - } - if (log) { - logger.trace("Invalidating cache key " + key + " for operation " + evictOp + " on method " + context.method); - } - cache.evict(key); - } - } - } else { - if (log) { - logger.trace("Cache condition failed on method " + context.method + " for operation " + context.operation); - } - } - } - } - } - } - - private CacheStatus inspectCacheables(Collection cacheables) { - Map cUpdates = new LinkedHashMap(cacheables.size()); - - boolean updateRequired = false; - Object retVal = null; - - if (!cacheables.isEmpty()) { - boolean log = logger.isTraceEnabled(); - boolean atLeastOnePassed = false; - - for (CacheOperationContext context : cacheables) { - if (context.isConditionPassing()) { - atLeastOnePassed = true; - Object key = context.generateKey(); - - if (log) { - logger.trace("Computed cache key " + key + " for operation " + context.operation); - } - if (key == null) { - throw new IllegalArgumentException( - "Null key returned for cache operation (maybe you are using named params on classes without debug info?) " - + context.operation); - } - - // add op/key (in case an update is discovered later on) - cUpdates.put(context, key); - - boolean localCacheHit = false; - - // check whether the cache needs to be inspected or not (the method will be invoked anyway) - if (!updateRequired) { - for (Cache cache : context.getCaches()) { - Cache.ValueWrapper wrapper = cache.get(key); - if (wrapper != null) { - retVal = wrapper.get(); - localCacheHit = true; - break; - } - } - } - - if (!localCacheHit) { - updateRequired = true; - } - } - else { - if (log) { - logger.trace("Cache condition failed on method " + context.method + " for operation " + context.operation); - } - } - } - - // return a status only if at least on cacheable matched - if (atLeastOnePassed) { - return new CacheStatus(cUpdates, updateRequired, retVal); + public CacheOperationContexts(Collection operations, + Method method, Object[] args, Object target, Class targetClass) { + for (CacheOperation operation : operations) { + this.contexts.add(operation.getClass(), new CacheOperationContext(operation, + method, args, target, targetClass)); } } - return null; - } - - private static class CacheStatus { - // caches/key - final Map cUpdates; - final boolean updateRequired; - final Object retVal; - - CacheStatus(Map cUpdates, boolean updateRequired, Object retVal) { - this.cUpdates = cUpdates; - this.updateRequired = updateRequired; - this.retVal = retVal; + public Collection get(Class operationClass) { + return this.contexts.getOrDefault(operationClass, Collections. emptyList()); } } - private Map inspectCacheUpdates(Collection updates) { - - Map cUpdates = new LinkedHashMap(updates.size()); - - if (!updates.isEmpty()) { - boolean log = logger.isTraceEnabled(); - - for (CacheOperationContext context : updates) { - if (context.isConditionPassing()) { - - Object key = context.generateKey(); - - if (log) { - logger.trace("Computed cache key " + key + " for operation " + context.operation); - } - if (key == null) { - throw new IllegalArgumentException( - "Null key returned for cache operation (maybe you are using named params on classes without debug info?) " - + context.operation); - } - - // add op/key (in case an update is discovered later on) - cUpdates.put(context, key); - } - else { - if (log) { - logger.trace("Cache condition failed on method " + context.method + " for operation " + context.operation); - } - } - } - } - - return cUpdates; - } - - private void update(Map updates, Object retVal) { - for (Map.Entry entry : updates.entrySet()) { - CacheOperationContext operationContext = entry.getKey(); - if(operationContext.canPutToCache(retVal)) { - for (Cache cache : operationContext.getCaches()) { - cache.put(entry.getValue(), retVal); - } - } - } - } - - private Map> createOperationContext(Collection cacheOp, - Method method, Object[] args, Object target, Class targetClass) { - Map> map = new LinkedHashMap>(3); - - Collection cacheables = new ArrayList(); - Collection evicts = new ArrayList(); - Collection updates = new ArrayList(); - - for (CacheOperation cacheOperation : cacheOp) { - CacheOperationContext opContext = getOperationContext(cacheOperation, method, args, target, targetClass); - - if (cacheOperation instanceof CacheableOperation) { - cacheables.add(opContext); - } - - if (cacheOperation instanceof CacheEvictOperation) { - evicts.add(opContext); - } - - if (cacheOperation instanceof CachePutOperation) { - updates.add(opContext); - } - } - - map.put(CACHEABLE, cacheables); - map.put(EVICT, evicts); - map.put(UPDATE, updates); - - return map; - } protected class CacheOperationContext { @@ -444,6 +361,7 @@ public abstract class CacheAspectSupport implements InitializingBean { private final Collection caches; + public CacheOperationContext(CacheOperation operation, Method method, Object[] args, Object target, Class targetClass) { this.operation = operation; this.method = method; @@ -453,14 +371,10 @@ public abstract class CacheAspectSupport implements InitializingBean { this.caches = CacheAspectSupport.this.getCaches(operation); } - protected boolean isConditionPassing() { - return isConditionPassing(ExpressionEvaluator.NO_RESULT); - } - protected boolean isConditionPassing(Object result) { if (StringUtils.hasText(this.operation.getCondition())) { EvaluationContext evaluationContext = createEvaluationContext(result); - return evaluator.condition(this.operation.getCondition(), this.method, + return CacheAspectSupport.this.evaluator.condition(this.operation.getCondition(), this.method, evaluationContext); } return true; @@ -476,7 +390,7 @@ public abstract class CacheAspectSupport implements InitializingBean { } if(StringUtils.hasText(unless)) { EvaluationContext evaluationContext = createEvaluationContext(value); - return !evaluator.unless(unless, this.method, evaluationContext); + return !CacheAspectSupport.this.evaluator.unless(unless, this.method, evaluationContext); } return true; } @@ -485,16 +399,16 @@ public abstract class CacheAspectSupport implements InitializingBean { * Computes the key for the given caching operation. * @return generated key (null if none can be generated) */ - protected Object generateKey() { + protected Object generateKey(Object result) { if (StringUtils.hasText(this.operation.getKey())) { - EvaluationContext evaluationContext = createEvaluationContext(ExpressionEvaluator.NO_RESULT); - return evaluator.key(this.operation.getKey(), this.method, evaluationContext); + EvaluationContext evaluationContext = createEvaluationContext(result); + return CacheAspectSupport.this.evaluator.key(this.operation.getKey(), this.method, evaluationContext); } - return keyGenerator.generate(this.target, this.method, this.args); + return CacheAspectSupport.this.keyGenerator.generate(this.target, this.method, this.args); } private EvaluationContext createEvaluationContext(Object result) { - return evaluator.createEvaluationContext(this.caches, this.method, this.args, + return CacheAspectSupport.this.evaluator.createEvaluationContext(this.caches, this.method, this.args, this.target, this.targetClass, result); } @@ -502,4 +416,25 @@ public abstract class CacheAspectSupport implements InitializingBean { return this.caches; } } + + + private static class CachePutRequest { + + private final CacheOperationContext context; + + private final Object key; + + public CachePutRequest(CacheOperationContext context, Object key) { + this.context = context; + this.key = key; + } + + public void apply(Object result) { + if(this.context.canPutToCache(result)) { + for (Cache cache : this.context.getCaches()) { + cache.put(this.key, result); + } + } + } + } } diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheEvictOperation.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheEvictOperation.java index f671c84f75..6bb937a5c5 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheEvictOperation.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheEvictOperation.java @@ -25,6 +25,7 @@ package org.springframework.cache.interceptor; public class CacheEvictOperation extends CacheOperation { private boolean cacheWide = false; + private boolean beforeInvocation = false; diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheInterceptor.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheInterceptor.java index ad4ab9721b..b4b19bea3b 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheInterceptor.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheInterceptor.java @@ -41,14 +41,6 @@ import org.aopalliance.intercept.MethodInvocation; @SuppressWarnings("serial") public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable { - private static class ThrowableWrapper extends RuntimeException { - private final Throwable original; - - ThrowableWrapper(Throwable original) { - this.original = original; - } - } - @Override public Object invoke(final MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); @@ -70,4 +62,14 @@ public class CacheInterceptor extends CacheAspectSupport implements MethodInterc throw th.original; } } + + + private static class ThrowableWrapper extends RuntimeException { + private final Throwable original; + + ThrowableWrapper(Throwable original) { + this.original = original; + } + } + } diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperation.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperation.java index 46d84590e8..bff9ff175c 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperation.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperation.java @@ -23,15 +23,18 @@ import java.util.Set; import org.springframework.util.Assert; /** - * Base class implementing {@link CacheOperation}. + * Base class for cache operations. * * @author Costin Leau */ public abstract class CacheOperation { private Set cacheNames = Collections.emptySet(); + private String condition = ""; + private String key = ""; + private String name = ""; diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheProxyFactoryBean.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheProxyFactoryBean.java index 67a22e2b35..8891608e4d 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheProxyFactoryBean.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheProxyFactoryBean.java @@ -42,8 +42,10 @@ import org.springframework.aop.support.DefaultPointcutAdvisor; public class CacheProxyFactoryBean extends AbstractSingletonProxyFactoryBean { private final CacheInterceptor cachingInterceptor = new CacheInterceptor(); + private Pointcut pointcut; + /** * Set a pointcut, i.e a bean that can cause conditional invocation * of the CacheInterceptor depending on method and attributes passed. @@ -58,12 +60,11 @@ public class CacheProxyFactoryBean extends AbstractSingletonProxyFactoryBean { @Override protected Object createMainInterceptor() { this.cachingInterceptor.afterPropertiesSet(); - if (this.pointcut != null) { - return new DefaultPointcutAdvisor(this.pointcut, this.cachingInterceptor); - } else { + if (this.pointcut == null) { // Rely on default pointcut. throw new UnsupportedOperationException(); } + return new DefaultPointcutAdvisor(this.pointcut, this.cachingInterceptor); } /** diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CompositeCacheOperationSource.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CompositeCacheOperationSource.java index 8c01c66417..a18db38b1d 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/CompositeCacheOperationSource.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CompositeCacheOperationSource.java @@ -35,6 +35,7 @@ public class CompositeCacheOperationSource implements CacheOperationSource, Seri private final CacheOperationSource[] cacheOperationSources; + /** * Create a new CompositeCacheOperationSource for the given sources. * @param cacheOperationSources the CacheOperationSource instances to combine diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/DefaultKeyGenerator.java b/spring-context/src/main/java/org/springframework/cache/interceptor/DefaultKeyGenerator.java index a37218804a..d7fae627d5 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/DefaultKeyGenerator.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/DefaultKeyGenerator.java @@ -27,15 +27,25 @@ import org.springframework.cache.interceptor.KeyGenerator; * Uses the constant value {@value #NULL_PARAM_KEY} for any * {@code null} parameters given. * + *

NOTE: As this implementation returns only a hash of the parameters + * it is possible for key collisions to occur. Since Spring 4.0 the + * {@link SimpleKeyGenerator} is used when no explicit key generator + * has been defined. This class remains for applications that do not + * wish to migrate to the {@link SimpleKeyGenerator}. + * * @author Costin Leau * @author Chris Beams * @since 3.1 + * @see SimpleKeyGenerator + * @see org.springframework.cache.annotation.CachingConfigurer */ public class DefaultKeyGenerator implements KeyGenerator { public static final int NO_PARAM_KEY = 0; + public static final int NULL_PARAM_KEY = 53; + @Override public Object generate(Object target, Method method, Object... params) { if (params.length == 1) { diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/ExpressionEvaluator.java b/spring-context/src/main/java/org/springframework/cache/interceptor/ExpressionEvaluator.java index ba109397a2..06704c5f27 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/ExpressionEvaluator.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/ExpressionEvaluator.java @@ -42,6 +42,7 @@ class ExpressionEvaluator { public static final Object NO_RESULT = new Object(); + private final SpelExpressionParser parser = new SpelExpressionParser(); // shared param discoverer since it caches data internally diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/LazyParamAwareEvaluationContext.java b/spring-context/src/main/java/org/springframework/cache/interceptor/LazyParamAwareEvaluationContext.java index 7dc1289a41..631ea6e442 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/LazyParamAwareEvaluationContext.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/LazyParamAwareEvaluationContext.java @@ -85,14 +85,14 @@ class LazyParamAwareEvaluationContext extends StandardEvaluationContext { return; } - String mKey = toString(this.method); - Method targetMethod = this.methodCache.get(mKey); + String methodKey = toString(this.method); + Method targetMethod = this.methodCache.get(methodKey); if (targetMethod == null) { targetMethod = AopUtils.getMostSpecificMethod(this.method, this.targetClass); if (targetMethod == null) { targetMethod = this.method; } - this.methodCache.put(mKey, targetMethod); + this.methodCache.put(methodKey, targetMethod); } // save arguments as indexed variables diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/NameMatchCacheOperationSource.java b/spring-context/src/main/java/org/springframework/cache/interceptor/NameMatchCacheOperationSource.java index c1cbdc0e9b..fc074d6238 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/NameMatchCacheOperationSource.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/NameMatchCacheOperationSource.java @@ -42,9 +42,11 @@ public class NameMatchCacheOperationSource implements CacheOperationSource, Seri */ protected static final Log logger = LogFactory.getLog(NameMatchCacheOperationSource.class); + /** Keys are method names; values are TransactionAttributes */ private Map> nameMap = new LinkedHashMap>(); + /** * Set a name/attribute map, consisting of method names * (e.g. "myMethod") and CacheOperation instances diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/SimpleKey.java b/spring-context/src/main/java/org/springframework/cache/interceptor/SimpleKey.java new file mode 100644 index 0000000000..805edbbaad --- /dev/null +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/SimpleKey.java @@ -0,0 +1,73 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cache.interceptor; + +import java.io.Serializable; +import java.util.Arrays; + +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * A simple key as returned from the {@link SimpleKeyGenerator}. + * + * @author Phillip Webb + * @since 4.0 + * @see SimpleKeyGenerator + */ +public final class SimpleKey implements Serializable { + + private static final long serialVersionUID = 1; + + public static final SimpleKey EMPTY = new SimpleKey(new Object[] {}); + + + private final Object[] params; + + + /** + * Create a new {@link SimpleKey} instance. + * @param elements the elements of the key + */ + public SimpleKey(Object[] elements) { + Assert.notNull(elements, "Elements must not be null"); + this.params = new Object[elements.length]; + System.arraycopy(elements, 0, this.params, 0, elements.length); + } + + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj != null && getClass() == obj.getClass()) { + return Arrays.equals(this.params, ((SimpleKey) obj).params); + } + return false; + } + + @Override + public int hashCode() { + return Arrays.hashCode(params); + } + + @Override + public String toString() { + return "SimpleKey [" + StringUtils.arrayToCommaDelimitedString(this.params) + "]"; + } +} diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/SimpleKeyGenerator.java b/spring-context/src/main/java/org/springframework/cache/interceptor/SimpleKeyGenerator.java new file mode 100644 index 0000000000..7d6fc6f16d --- /dev/null +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/SimpleKeyGenerator.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cache.interceptor; + +import java.lang.reflect.Method; + +/** + * Simple key generator. Returns the parameter itself if a single non-null value + * is given, otherwise returns a {@link SimpleKey} of the parameters. + * + *

Unlike {@link DefaultKeyGenerator}, no collisions will occur with the keys + * generated by this class. The returned {@link SimpleKey} object can be safely + * used with a {@link org.springframework.cache.concurrent.ConcurrentMapCache}, + * however, might not be suitable for all {@link org.springframework.cache.Cache} + * implementations. + * + * @author Phillip Webb + * @since 4.0 + * @see SimpleKey + * @see DefaultKeyGenerator + * @see org.springframework.cache.annotation.CachingConfigurer + */ +public class SimpleKeyGenerator implements KeyGenerator { + + @Override + public Object generate(Object target, Method method, Object... params) { + if(params.length == 0) { + return SimpleKey.EMPTY; + } + if(params.length == 1 && params[0] != null) { + return params[0]; + } + return new SimpleKey(params); + } + +} diff --git a/spring-context/src/main/java/org/springframework/cache/support/SimpleCacheManager.java b/spring-context/src/main/java/org/springframework/cache/support/SimpleCacheManager.java index e90bfaca6b..5c373a2b70 100644 --- a/spring-context/src/main/java/org/springframework/cache/support/SimpleCacheManager.java +++ b/spring-context/src/main/java/org/springframework/cache/support/SimpleCacheManager.java @@ -31,6 +31,7 @@ public class SimpleCacheManager extends AbstractCacheManager { private Collection caches; + /** * Specify the collection of Cache instances to use for this CacheManager. */ diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java index b660b0355b..be17b59d6d 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java @@ -45,12 +45,12 @@ public class AnnotatedBeanDefinitionReader { private final BeanDefinitionRegistry registry; - private Environment environment; - private BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator(); private ScopeMetadataResolver scopeMetadataResolver = new AnnotationScopeMetadataResolver(); + private ConditionEvaluator conditionEvaluator; + /** * Create a new {@code AnnotatedBeanDefinitionReader} for the given registry. @@ -79,7 +79,8 @@ public class AnnotatedBeanDefinitionReader { Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); Assert.notNull(environment, "Environment must not be null"); this.registry = registry; - this.environment = environment; + this.conditionEvaluator = new ConditionEvaluator(registry, environment, + null, null, null); AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry); } @@ -92,12 +93,13 @@ public class AnnotatedBeanDefinitionReader { /** * Set the Environment to use when evaluating whether - * {@link Profile @Profile}-annotated component classes should be registered. + * {@link Conditional @Conditional}-annotated component classes should be registered. *

The default is a {@link StandardEnvironment}. * @see #registerBean(Class, String, Class...) */ public void setEnvironment(Environment environment) { - this.environment = environment; + this.conditionEvaluator = new ConditionEvaluator(this.registry, environment, + null, null, null); } /** @@ -133,8 +135,7 @@ public class AnnotatedBeanDefinitionReader { public void registerBean(Class annotatedClass, String name, Class... qualifiers) { AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass); - if (ConditionalAnnotationHelper.shouldSkip(abd, this.registry, - this.environment, this.beanNameGenerator)) { + if (conditionEvaluator.shouldSkip(abd.getMetadata())) { return; } ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd); diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java index 00d2f9810d..89ac1c8c27 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -117,7 +117,10 @@ public class AnnotationBeanNameGenerator implements BeanNameGenerator { (metaAnnotationTypes != null && metaAnnotationTypes.contains(COMPONENT_ANNOTATION_CLASSNAME)) || annotationType.equals("javax.annotation.ManagedBean") || annotationType.equals("javax.inject.Named"); - return (isStereotype && attributes != null && attributes.containsKey("value")); + + return (isStereotype && attributes != null && + attributes.containsKey("value") && + attributes.get("value") instanceof String); } /** diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java index a016b88554..42f5b0ab9a 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java @@ -31,6 +31,7 @@ import org.springframework.core.env.Environment; import org.springframework.core.env.EnvironmentCapable; import org.springframework.core.env.StandardEnvironment; import org.springframework.core.io.ResourceLoader; +import org.springframework.core.type.classreading.MetadataReader; import org.springframework.util.Assert; import org.springframework.util.PatternMatchUtils; @@ -52,7 +53,6 @@ import org.springframework.util.PatternMatchUtils; * @author Mark Fisher * @author Juergen Hoeller * @author Chris Beams - * @author Phillip Webb * @since 2.5 * @see AnnotationConfigApplicationContext#scan * @see org.springframework.stereotype.Component @@ -299,10 +299,6 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo * bean definition has been found for the specified name */ protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException { - if (ConditionalAnnotationHelper.shouldSkip(beanDefinition, getRegistry(), - getEnvironment(), this.beanNameGenerator)) { - return false; - } if (!this.registry.containsBeanDefinition(beanName)) { return true; } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java index a12e9ab190..08336b34b0 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java @@ -25,12 +25,11 @@ import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.ResourceLoaderAware; -import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.env.Environment; import org.springframework.core.env.EnvironmentCapable; import org.springframework.core.env.StandardEnvironment; @@ -39,7 +38,6 @@ import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternUtils; -import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; @@ -88,6 +86,8 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC private final List excludeFilters = new LinkedList(); + private ConditionEvaluator conditionEvaluator; + /** * Create a ClassPathScanningCandidateComponentProvider with a {@link StandardEnvironment}. @@ -159,12 +159,13 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC /** * Set the Environment to use when resolving placeholders and evaluating - * {@link Profile @Profile}-annotated component classes. + * {@link Conditional @Conditional}-annotated component classes. *

The default is a {@link StandardEnvironment} * @param environment the Environment to use */ public void setEnvironment(Environment environment) { this.environment = environment; + this.conditionEvaluator = null; } @Override @@ -172,6 +173,13 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC return this.environment; } + /** + * Returns the {@link BeanDefinitionRegistry} used by this scanner or {@code null}. + */ + protected BeanDefinitionRegistry getRegistry() { + return null; + } + /** * Set the resource pattern to use when scanning the classpath. * This value will be appended to each base package name. @@ -333,17 +341,26 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC } for (TypeFilter tf : this.includeFilters) { if (tf.match(metadataReader, this.metadataReaderFactory)) { - AnnotationMetadata metadata = metadataReader.getAnnotationMetadata(); - if (!metadata.isAnnotated(Profile.class.getName())) { - return true; - } - AnnotationAttributes profile = MetadataUtils.attributesFor(metadata, Profile.class); - return this.environment.acceptsProfiles(profile.getStringArray("value")); + return isConditionMatch(metadataReader); } } return false; } + /** + * Determine whether the given class is a candidate component based on any + * {@code @Conditional} annotations. + * @param metadataReader the ASM ClassReader for the class + * @return whether the class qualifies as a candidate component + */ + private boolean isConditionMatch(MetadataReader metadataReader) { + if (this.conditionEvaluator == null) { + this.conditionEvaluator = new ConditionEvaluator(getRegistry(), + getEnvironment(), null, null, getResourceLoader()); + } + return !conditionEvaluator.shouldSkip(metadataReader.getAnnotationMetadata()); + } + /** * Determine whether the given bean definition qualifies as candidate. *

The default implementation checks whether the class is concrete diff --git a/spring-context/src/main/java/org/springframework/context/annotation/Condition.java b/spring-context/src/main/java/org/springframework/context/annotation/Condition.java index 69543830aa..a3cfe9fc80 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/Condition.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/Condition.java @@ -25,15 +25,18 @@ import org.springframework.core.type.AnnotationMetadata; * A single {@code condition} that must be {@linkplain #matches matched} in order * for a component to be registered. * - *

Conditions are checked immediately before a component bean-definition is due to be - * registered and are free to veto registration based on any criteria that can be - * determined at that point. + *

Conditions are checked immediately before the bean-definition is due to be + * registered and are free to veto registration based on any criteria that can + * be determined at that point. * *

Conditions must follow the same restrictions as {@link BeanFactoryPostProcessor} - * and take care to never interact with bean instances. + * and take care to never interact with bean instances. For more fine-grained control + * of conditions that interact with {@code @Configuration} beans consider the + * {@link ConfigurationCondition} interface. * * @author Phillip Webb * @since 4.0 + * @see ConfigurationCondition * @see Conditional * @see ConditionContext */ diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConditionContext.java b/spring-context/src/main/java/org/springframework/context/annotation/ConditionContext.java index 564586501c..541f749c25 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConditionContext.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConditionContext.java @@ -18,6 +18,7 @@ package org.springframework.context.annotation; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.ApplicationContext; import org.springframework.core.env.Environment; import org.springframework.core.io.ResourceLoader; @@ -31,8 +32,8 @@ public interface ConditionContext { /** * Returns the {@link BeanDefinitionRegistry} that will hold the bean definition - * should the condition match. - * @return the registry (never {@code null}) + * should the condition match or {@code null} if the registry is not available. + * @return the registry or {@code null} */ BeanDefinitionRegistry getRegistry(); @@ -45,13 +46,11 @@ public interface ConditionContext { /** * Returns the {@link ConfigurableListableBeanFactory} that will hold the bean - * definition should the condition match. If a - * {@link ConfigurableListableBeanFactory} is unavailable an - * {@link IllegalStateException} will be thrown. - * @return the bean factory - * @throws IllegalStateException if the bean factory could not be obtained + * definition should the condition match or {@code null} if the bean factory is + * not available. + * @return the bean factory or {@code null} */ - ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException; + ConfigurableListableBeanFactory getBeanFactory(); /** * Returns the {@link ResourceLoader} currently being used or {@code null} if the @@ -67,4 +66,6 @@ public interface ConditionContext { */ ClassLoader getClassLoader(); + ApplicationContext getApplicationContext(); + } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConditionEvaluator.java b/spring-context/src/main/java/org/springframework/context/annotation/ConditionEvaluator.java new file mode 100644 index 0000000000..32142bc59e --- /dev/null +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConditionEvaluator.java @@ -0,0 +1,229 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.context.annotation; + +import java.util.Collections; +import java.util.List; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.ConfigurationCondition.ConfigurationPhase; +import org.springframework.core.env.Environment; +import org.springframework.core.env.EnvironmentCapable; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.MultiValueMap; + +/** + * Internal class used to evaluate {@link Conditional} annotations. + * + * @author Phillip Webb + * @since 4.0 + */ +class ConditionEvaluator { + + private static final String CONDITIONAL_ANNOTATION = Conditional.class.getName(); + + + private final ConditionContextImpl context; + + + /** + * Create a new {@link ConditionEvaluator} instance. + */ + public ConditionEvaluator(BeanDefinitionRegistry registry, Environment environment, + ApplicationContext applicationContext, ClassLoader classLoader, + ResourceLoader resourceLoader) { + this.context = new ConditionContextImpl(registry, environment, + applicationContext, classLoader, resourceLoader); + } + + + /** + * Determine if an item should be skipped based on {@code @Conditional} annotations. + * The {@link ConfigurationPhase} will be deduced from the type of item (i.e. a + * {@code @Configuration} class will be {@link ConfigurationPhase#PARSE_CONFIGURATION}) + * @param metadata the meta data + * @return if the item should be skipped + */ + public boolean shouldSkip(AnnotatedTypeMetadata metadata) { + return shouldSkip(metadata, null); + } + + /** + * Determine if an item should be skipped based on {@code @Conditional} annotations. + * @param metadata the meta data + * @param phase the phase of the call + * @return if the item should be skipped + */ + public boolean shouldSkip(AnnotatedTypeMetadata metadata, ConfigurationPhase phase) { + if (metadata == null || !metadata.isAnnotated(CONDITIONAL_ANNOTATION)) { + return false; + } + + if (phase == null) { + if (metadata instanceof AnnotationMetadata && + ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) { + return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION); + } + return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN); + } + + for (String[] conditionClasses : getConditionClasses(metadata)) { + for (String conditionClass : conditionClasses) { + Condition condition = getCondition(conditionClass, context.getClassLoader()); + ConfigurationPhase requiredPhase = null; + if (condition instanceof ConfigurationCondition) { + requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase(); + } + if (requiredPhase == null || requiredPhase == phase) { + if (!condition.matches(context, metadata)) { + return true; + } + } + } + } + return false; + } + + @SuppressWarnings("unchecked") + private List getConditionClasses(AnnotatedTypeMetadata metadata) { + MultiValueMap attributes = metadata.getAllAnnotationAttributes( + CONDITIONAL_ANNOTATION, true); + Object values = attributes == null ? null : attributes.get("value"); + return (List) (values == null ? Collections.emptyList() : values); + } + + private Condition getCondition(String conditionClassName, + ClassLoader classloader) { + Class conditionClass = ClassUtils.resolveClassName(conditionClassName, + classloader); + return (Condition) BeanUtils.instantiateClass(conditionClass); + } + + + /** + * Implementation of a {@link ConditionContext}. + */ + private static class ConditionContextImpl implements ConditionContext { + + private BeanDefinitionRegistry registry; + + private ConfigurableListableBeanFactory beanFactory; + + private Environment environment; + + private ApplicationContext applicationContext; + + private ClassLoader classLoader; + + private ResourceLoader resourceLoader; + + + public ConditionContextImpl(BeanDefinitionRegistry registry, + Environment environment, ApplicationContext applicationContext, + ClassLoader classLoader, ResourceLoader resourceLoader) { + this.registry = registry; + this.beanFactory = deduceBeanFactory(registry); + this.environment = environment; + this.applicationContext = applicationContext; + this.classLoader = classLoader; + this.resourceLoader = resourceLoader; + } + + private ConfigurableListableBeanFactory deduceBeanFactory(Object source) { + if (source == null) { + return null; + } + if (source instanceof ConfigurableListableBeanFactory) { + return (ConfigurableListableBeanFactory) source; + } + else if (source instanceof ConfigurableApplicationContext) { + return deduceBeanFactory(((ConfigurableApplicationContext) source).getBeanFactory()); + } + return null; + } + + @Override + public BeanDefinitionRegistry getRegistry() { + if (this.registry != null) { + return this.registry; + } + if(getBeanFactory() != null && getBeanFactory() instanceof BeanDefinitionRegistry) { + return (BeanDefinitionRegistry) getBeanFactory(); + } + return null; + } + + @Override + public Environment getEnvironment() { + if (this.environment != null) { + return this.environment; + } + if (getRegistry() != null && getRegistry() instanceof EnvironmentCapable) { + return ((EnvironmentCapable) getRegistry()).getEnvironment(); + } + return null; + } + + @Override + public ConfigurableListableBeanFactory getBeanFactory() { + Assert.state(this.beanFactory != null, "Unable to locate the BeanFactory"); + return this.beanFactory; + } + + @Override + public ResourceLoader getResourceLoader() { + if (this.resourceLoader != null) { + return this.resourceLoader; + } + if (registry instanceof ResourceLoader) { + return (ResourceLoader) registry; + } + return null; + } + + @Override + public ClassLoader getClassLoader() { + if (this.classLoader != null) { + return this.classLoader; + } + if (getResourceLoader() != null) { + return getResourceLoader().getClassLoader(); + } + return null; + } + + @Override + public ApplicationContext getApplicationContext() { + if (this.applicationContext != null) { + return this.applicationContext; + } + if (getRegistry() != null && getRegistry() instanceof ApplicationContext) { + return (ApplicationContext) getRegistry(); + } + return null; + } + } + +} diff --git a/spring-context/src/main/java/org/springframework/context/annotation/Conditional.java b/spring-context/src/main/java/org/springframework/context/annotation/Conditional.java index 9a98714254..898e3ebcd3 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/Conditional.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/Conditional.java @@ -22,11 +22,11 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * Indicates that a component is is only eligible for registration when all + * Indicates that a component is only eligible for registration when all * {@linkplain #value() specified conditions} match. * *

A condition is any state that can be determined programmatically - * immediately before the bean is due to be created (see {@link Condition} for details). + * before the bean definition is due to be registered (see {@link Condition} for details). * *

The {@code @Conditional} annotation may be used in any of the following ways: *

    diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConditionalAnnotationHelper.java b/spring-context/src/main/java/org/springframework/context/annotation/ConditionalAnnotationHelper.java deleted file mode 100644 index 0eba197672..0000000000 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConditionalAnnotationHelper.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright 2002-2013 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.context.annotation; - -import java.util.Collections; -import java.util.List; - -import org.springframework.beans.BeanUtils; -import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.BeanNameGenerator; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.core.env.Environment; -import org.springframework.core.env.EnvironmentCapable; -import org.springframework.core.io.ResourceLoader; -import org.springframework.core.type.AnnotatedTypeMetadata; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; -import org.springframework.util.MultiValueMap; - -/** - * Helper class used to determine if registration should be skipped based due to a - * {@code @Conditional} annotation. - * - * @author Phillip Webb - * @since 4.0 - * @see Conditional - */ -abstract class ConditionalAnnotationHelper { - - private static final String CONDITIONAL_ANNOTATION = Conditional.class.getName(); - - - public static boolean shouldSkip(BeanDefinition beanDefinition, - BeanDefinitionRegistry registry, Environment environment, - BeanNameGenerator beanNameGenerator) { - if (hasCondition(getMetadata(beanDefinition))) { - ConditionContextImpl context = new ConditionContextImpl(registry, - environment, beanNameGenerator); - return shouldSkip(getMetadata(beanDefinition), context); - } - return false; - } - - public static boolean shouldSkip(BeanMethod beanMethod, - BeanDefinitionRegistry registry, Environment environment, - BeanNameGenerator beanNameGenerator) { - if (hasCondition(getMetadata(beanMethod))) { - ConditionContextImpl context = new ConditionContextImpl(registry, - environment, beanNameGenerator); - return shouldSkip(getMetadata(beanMethod), context); - } - return false; - } - - public static boolean shouldSkip(ConfigurationClass configurationClass, - BeanDefinitionRegistry registry, Environment environment, - BeanNameGenerator beanNameGenerator) { - if (hasCondition(configurationClass)) { - ConditionContextImpl context = new ConditionContextImpl(registry, - environment, beanNameGenerator); - return shouldSkip(configurationClass, context); - } - return false; - } - - public static boolean shouldSkip(ConfigurationClass configClass, - ConditionContextImpl context) { - if (configClass == null) { - return false; - } - return shouldSkip(configClass.getMetadata(), context); - } - - private static boolean shouldSkip(AnnotatedTypeMetadata metadata, - ConditionContextImpl context) { - if (metadata != null) { - for (String[] conditionClasses : getConditionClasses(metadata)) { - for (String conditionClass : conditionClasses) { - if (!getCondition(conditionClass, context.getClassLoader()).matches( - context, metadata)) { - return true; - } - } - } - } - return false; - } - - private static AnnotatedTypeMetadata getMetadata(BeanMethod beanMethod) { - return (beanMethod == null ? null : beanMethod.getMetadata()); - } - - private static AnnotatedTypeMetadata getMetadata(BeanDefinition beanDefinition) { - if (beanDefinition != null && beanDefinition instanceof AnnotatedBeanDefinition) { - return ((AnnotatedBeanDefinition) beanDefinition).getMetadata(); - } - return null; - } - - private static boolean hasCondition(ConfigurationClass configurationClass) { - if (configurationClass == null) { - return false; - } - return hasCondition(configurationClass.getMetadata()) - || hasCondition(configurationClass.getImportedBy()); - } - - private static boolean hasCondition(AnnotatedTypeMetadata metadata) { - return (metadata != null) && metadata.isAnnotated(CONDITIONAL_ANNOTATION); - } - - @SuppressWarnings("unchecked") - private static List getConditionClasses(AnnotatedTypeMetadata metadata) { - MultiValueMap attributes = metadata.getAllAnnotationAttributes( - CONDITIONAL_ANNOTATION, true); - Object values = attributes == null ? null : attributes.get("value"); - return (List) (values == null ? Collections.emptyList() : values); - } - - private static Condition getCondition(String conditionClassName, - ClassLoader classloader) { - Class conditionClass = ClassUtils.resolveClassName(conditionClassName, - classloader); - return (Condition) BeanUtils.instantiateClass(conditionClass); - } - - - /** - * Implementation of a {@link ConditionContext}. - */ - private static class ConditionContextImpl implements ConditionContext { - - private BeanDefinitionRegistry registry; - - private ConfigurableListableBeanFactory beanFactory; - - private Environment environment; - - - public ConditionContextImpl(BeanDefinitionRegistry registry, - Environment environment, BeanNameGenerator beanNameGenerator) { - Assert.notNull(registry, "Registry must not be null"); - this.registry = registry; - this.beanFactory = deduceBeanFactory(registry); - this.environment = environment; - if (this.environment == null) { - this.environment = deduceEnvironment(registry); - } - } - - - private ConfigurableListableBeanFactory deduceBeanFactory(Object source) { - if (source instanceof ConfigurableListableBeanFactory) { - return (ConfigurableListableBeanFactory) source; - } - else if (source instanceof ConfigurableApplicationContext) { - return deduceBeanFactory(((ConfigurableApplicationContext) source).getBeanFactory()); - } - return null; - } - - private Environment deduceEnvironment(BeanDefinitionRegistry registry) { - if (registry instanceof EnvironmentCapable) { - return ((EnvironmentCapable) registry).getEnvironment(); - } - return null; - } - - @Override - public BeanDefinitionRegistry getRegistry() { - return this.registry; - } - - @Override - public Environment getEnvironment() { - return this.environment; - } - - @Override - public ConfigurableListableBeanFactory getBeanFactory() { - Assert.state(this.beanFactory != null, "Unable to locate the BeanFactory"); - return this.beanFactory; - } - - @Override - public ResourceLoader getResourceLoader() { - if (registry instanceof ResourceLoader) { - return (ResourceLoader) registry; - } - return null; - } - - @Override - public ClassLoader getClassLoader() { - ResourceLoader resourceLoader = getResourceLoader(); - return (resourceLoader == null ? null : resourceLoader.getClassLoader()); - } - } - -} diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java index 2e91b45d5f..9138e2357f 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java @@ -16,6 +16,7 @@ package org.springframework.context.annotation; +import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; @@ -61,6 +62,9 @@ final class ConfigurationClass { private final Map> importedResources = new LinkedHashMap>(); + private final Set importBeanDefinitionRegistrars = + new LinkedHashSet(); + /** * Create a new {@link ConfigurationClass} with the given name. @@ -173,11 +177,18 @@ final class ConfigurationClass { this.importedResources.put(importedResource, readerClass); } + public void addImportBeanDefinitionRegistrar(ImportBeanDefinitionRegistrar registrar) { + this.importBeanDefinitionRegistrars.add(registrar); + } + + public Set getImportBeanDefinitionRegistrars() { + return Collections.unmodifiableSet(importBeanDefinitionRegistrars); + } + public Map> getImportedResources() { return this.importedResources; } - public void validate(ProblemReporter problemReporter) { // A configuration class may not be final (CGLIB limitation) if (getMetadata().isAnnotated(Configuration.class.getName())) { @@ -208,7 +219,6 @@ final class ConfigurationClass { } } - @Override public boolean equals(Object other) { return (this == other || (other instanceof ConfigurationClass && diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java index 27f417f655..4aa586dd2c 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java @@ -16,8 +16,6 @@ package org.springframework.context.annotation; -import static org.springframework.context.annotation.MetadataUtils.attributesFor; - import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; @@ -28,6 +26,7 @@ import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition; import org.springframework.beans.factory.annotation.Autowire; @@ -43,6 +42,8 @@ import org.springframework.beans.factory.support.BeanDefinitionReader; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.ConfigurationCondition.ConfigurationPhase; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.env.Environment; import org.springframework.core.io.Resource; @@ -52,6 +53,8 @@ import org.springframework.core.type.MethodMetadata; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.util.StringUtils; +import static org.springframework.context.annotation.MetadataUtils.*; + /** * Reads a given fully-populated set of ConfigurationClass instances, registering bean * definitions with the given {@link BeanDefinitionRegistry} based on its contents. @@ -84,15 +87,17 @@ class ConfigurationClassBeanDefinitionReader { private final BeanNameGenerator importBeanNameGenerator; + private final ConditionEvaluator conditionEvaluator; /** * Create a new {@link ConfigurationClassBeanDefinitionReader} instance that will be used * to populate the given {@link BeanDefinitionRegistry}. */ public ConfigurationClassBeanDefinitionReader( - BeanDefinitionRegistry registry, SourceExtractor sourceExtractor, - ProblemReporter problemReporter, MetadataReaderFactory metadataReaderFactory, - ResourceLoader resourceLoader, Environment environment, BeanNameGenerator importBeanNameGenerator) { + BeanDefinitionRegistry registry, ApplicationContext applicationContext, + SourceExtractor sourceExtractor, ProblemReporter problemReporter, + MetadataReaderFactory metadataReaderFactory, ResourceLoader resourceLoader, + Environment environment, BeanNameGenerator importBeanNameGenerator) { this.registry = registry; this.sourceExtractor = sourceExtractor; @@ -101,6 +106,8 @@ class ConfigurationClassBeanDefinitionReader { this.resourceLoader = resourceLoader; this.environment = environment; this.importBeanNameGenerator = importBeanNameGenerator; + this.conditionEvaluator = new ConditionEvaluator(registry, environment, + applicationContext, null, resourceLoader); } @@ -109,8 +116,9 @@ class ConfigurationClassBeanDefinitionReader { * based on its contents. */ public void loadBeanDefinitions(Set configurationModel) { + TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator(); for (ConfigurationClass configClass : configurationModel) { - loadBeanDefinitionsForConfigurationClass(configClass); + loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator); } } @@ -118,7 +126,13 @@ class ConfigurationClassBeanDefinitionReader { * Read a particular {@link ConfigurationClass}, registering bean definitions for the * class itself, all its {@link Bean} methods */ - public void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass) { + private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, + TrackedConditionEvaluator trackedConditionEvaluator) { + if (trackedConditionEvaluator.shouldSkip(configClass)) { + removeBeanDefinition(configClass); + return; + } + if (configClass.isImported()) { registerBeanDefinitionForImportedConfigurationClass(configClass); } @@ -126,6 +140,17 @@ class ConfigurationClassBeanDefinitionReader { loadBeanDefinitionsForBeanMethod(beanMethod); } loadBeanDefinitionsFromImportedResources(configClass.getImportedResources()); + loadBeanDefinitionsFromRegistrars(configClass.getMetadata(), configClass.getImportBeanDefinitionRegistrars()); + } + + private void removeBeanDefinition(ConfigurationClass configClass) { + if (StringUtils.hasLength(configClass.getBeanName())) { + try { + this.registry.removeBeanDefinition(configClass.getBeanName()); + } + catch (NoSuchBeanDefinitionException ex) { + } + } } /** @@ -153,8 +178,8 @@ class ConfigurationClassBeanDefinitionReader { * with the BeanDefinitionRegistry based on its contents. */ private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) { - if (ConditionalAnnotationHelper.shouldSkip(beanMethod, this.registry, - this.environment, this.importBeanNameGenerator)) { + if (conditionEvaluator.shouldSkip(beanMethod.getMetadata(), + ConfigurationPhase.REGISTER_BEAN)) { return; } ConfigurationClass configClass = beanMethod.getConfigurationClass(); @@ -300,6 +325,14 @@ class ConfigurationClassBeanDefinitionReader { } } + private void loadBeanDefinitionsFromRegistrars( + AnnotationMetadata importingClassMetadata, + Set importBeanDefinitionRegistrars) { + for (ImportBeanDefinitionRegistrar registrar : importBeanDefinitionRegistrars) { + registrar.registerBeanDefinitions(importingClassMetadata, this.registry); + } + } + /** * {@link RootBeanDefinition} marker subclass used to signify that a bean definition @@ -357,4 +390,32 @@ class ConfigurationClassBeanDefinitionReader { } } + + /** + * Evaluate {@Code @Conditional} annotations, tracking results and taking into + * account 'imported by'. + */ + private class TrackedConditionEvaluator { + + private final Map skipped = new HashMap(); + + public boolean shouldSkip(ConfigurationClass configClass) { + Boolean skip = this.skipped.get(configClass); + if (skip == null) { + if (configClass.isImported()) { + if (shouldSkip(configClass.getImportedBy())) { + // The config that imported this one was skipped, therefore we are skipped + skip = true; + } + } + if (skip == null) { + skip = conditionEvaluator.shouldSkip(configClass.getMetadata(), + ConfigurationPhase.REGISTER_BEAN); + } + this.skipped.put(configClass, skip); + } + return skip; + } + } + } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java index 967f8f231e..d2210c812f 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java @@ -411,7 +411,8 @@ class ConfigurationClassEnhancer { Field field = ReflectionUtils.findField(enhancedConfigInstance.getClass(), BEAN_FACTORY_FIELD); Assert.state(field != null, "Unable to find generated bean factory field"); Object beanFactory = ReflectionUtils.getField(field, enhancedConfigInstance); - Assert.isInstanceOf(ConfigurableBeanFactory.class, beanFactory); + Assert.state(beanFactory != null, "The BeanFactory has not been injected into the @Configuration class"); + Assert.state(beanFactory instanceof ConfigurableBeanFactory, "The injected BeanFactory is not a ConfigurableBeanFactory"); return (ConfigurableBeanFactory) beanFactory; } } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java index 294d94bb04..ba0052358a 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java @@ -17,8 +17,7 @@ package org.springframework.context.annotation; import java.io.IOException; -import java.lang.annotation.Annotation; -import java.util.Arrays; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -47,7 +46,10 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionReader; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanNameGenerator; +import org.springframework.context.ApplicationContext; +import org.springframework.context.EnvironmentAware; import org.springframework.context.ResourceLoaderAware; +import org.springframework.context.annotation.ConfigurationCondition.ConfigurationPhase; import org.springframework.core.NestedIOException; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.annotation.AnnotationAwareOrderComparator; @@ -62,7 +64,6 @@ import org.springframework.core.type.StandardAnnotationMetadata; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.core.type.filter.AssignableTypeFilter; -import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import static org.springframework.context.annotation.MetadataUtils.*; @@ -75,7 +76,8 @@ import static org.springframework.context.annotation.MetadataUtils.*; * *

    This class helps separate the concern of parsing the structure of a Configuration * class from the concern of registering BeanDefinition objects based on the - * content of that model. + * content of that model (with the exception of {@code @ComponentScan} annotations which + * need to be registered immediately). * *

    This ASM-based implementation avoids reflection and eager class loading in order to * interoperate effectively with lazy class loading in a Spring ApplicationContext. @@ -107,8 +109,6 @@ class ConfigurationClassParser { private final BeanDefinitionRegistry registry; - private final BeanNameGenerator beanNameGenerator; - private final ComponentScanAnnotationParser componentScanParser; private final Set configurationClasses = new LinkedHashSet(); @@ -121,6 +121,8 @@ class ConfigurationClassParser { private final List deferredImportSelectors = new LinkedList(); + private final ConditionEvaluator conditionEvaluator; + /** * Create a new {@link ConfigurationClassParser} instance that will be used @@ -128,16 +130,18 @@ class ConfigurationClassParser { */ public ConfigurationClassParser(MetadataReaderFactory metadataReaderFactory, ProblemReporter problemReporter, Environment environment, ResourceLoader resourceLoader, - BeanNameGenerator componentScanBeanNameGenerator, BeanDefinitionRegistry registry) { + BeanNameGenerator componentScanBeanNameGenerator, BeanDefinitionRegistry registry, + ApplicationContext applicationContext) { this.metadataReaderFactory = metadataReaderFactory; this.problemReporter = problemReporter; this.environment = environment; this.resourceLoader = resourceLoader; this.registry = registry; - this.beanNameGenerator = componentScanBeanNameGenerator; this.componentScanParser = new ComponentScanAnnotationParser( resourceLoader, environment, componentScanBeanNameGenerator, registry); + this.conditionEvaluator = new ConditionEvaluator(registry, environment, + applicationContext, null, resourceLoader); } @@ -165,7 +169,7 @@ class ConfigurationClassParser { * @param beanName may be null, but if populated represents the bean id * (assumes that this configuration class was configured via XML) */ - public void parse(String className, String beanName) throws IOException { + protected final void parse(String className, String beanName) throws IOException { MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className); processConfigurationClass(new ConfigurationClass(reader, beanName)); } @@ -175,13 +179,14 @@ class ConfigurationClassParser { * @param clazz the Class to parse * @param beanName must not be null (as of Spring 3.1.1) */ - public void parse(Class clazz, String beanName) throws IOException { + protected final void parse(Class clazz, String beanName) throws IOException { processConfigurationClass(new ConfigurationClass(clazz, beanName)); } protected void processConfigurationClass(ConfigurationClass configClass) throws IOException { - if (ConditionalAnnotationHelper.shouldSkip(configClass, this.registry, this.environment, this.beanNameGenerator)) { + + if (conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) { return; } @@ -197,56 +202,58 @@ class ConfigurationClassParser { } // Recursively process the configuration class and its superclass hierarchy. - AnnotationMetadata metadata = configClass.getMetadata(); + SourceClass sourceClass = asSourceClass(configClass); do { - metadata = doProcessConfigurationClass(configClass, metadata); + sourceClass = doProcessConfigurationClass(configClass, sourceClass); } - while (metadata != null); + while (sourceClass != null); this.configurationClasses.add(configClass); } /** - * @return annotation metadata of superclass, {@code null} if none found or previously processed + * Apply processing and build a complete {@link ConfigurationClass} by reading the + * annotations, members and methods from the source class. This method can be called + * multiple times as relevant sources are discovered. + * @param configClass the configuration class being build + * @param sourceClass a source class + * @return the superclass, {@code null} if none found or previously processed */ - protected AnnotationMetadata doProcessConfigurationClass( - ConfigurationClass configClass, AnnotationMetadata metadata) throws IOException { + protected final SourceClass doProcessConfigurationClass( + ConfigurationClass configClass, SourceClass sourceClass) throws IOException { // recursively process any member (nested) classes first - processMemberClasses(configClass, metadata); + processMemberClasses(configClass, sourceClass); // process any @PropertySource annotations - AnnotationAttributes propertySource = attributesFor(metadata, org.springframework.context.annotation.PropertySource.class); + AnnotationAttributes propertySource = attributesFor(sourceClass.getMetadata(), org.springframework.context.annotation.PropertySource.class); if (propertySource != null) { processPropertySource(propertySource); } // process any @ComponentScan annotations - AnnotationAttributes componentScan = attributesFor(metadata, ComponentScan.class); + AnnotationAttributes componentScan = attributesFor(sourceClass.getMetadata(), ComponentScan.class); if (componentScan != null) { // the config class is annotated with @ComponentScan -> perform the scan immediately - Set scannedBeanDefinitions = - this.componentScanParser.parse(componentScan, metadata.getClassName()); + if (!conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) { + Set scannedBeanDefinitions = + this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); - // check the set of scanned definitions for any further config classes and parse recursively if necessary - for (BeanDefinitionHolder holder : scannedBeanDefinitions) { - if (ConfigurationClassUtils.checkConfigurationClassCandidate(holder.getBeanDefinition(), this.metadataReaderFactory)) { - this.parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName()); + // check the set of scanned definitions for any further config classes and parse recursively if necessary + for (BeanDefinitionHolder holder : scannedBeanDefinitions) { + if (ConfigurationClassUtils.checkConfigurationClassCandidate(holder.getBeanDefinition(), this.metadataReaderFactory)) { + parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName()); + } } } } // process any @Import annotations - Set imports = new LinkedHashSet(); - Set visited = new LinkedHashSet(); - collectImports(metadata, imports, visited); - if (!imports.isEmpty()) { - processImport(configClass, imports, true); - } + processImports(configClass, getImports(sourceClass), true); // process any @ImportResource annotations - if (metadata.isAnnotated(ImportResource.class.getName())) { - AnnotationAttributes importResource = attributesFor(metadata, ImportResource.class); + if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) { + AnnotationAttributes importResource = attributesFor(sourceClass.getMetadata(), ImportResource.class); String[] resources = importResource.getStringArray("value"); Class readerClass = importResource.getClass("reader"); for (String resource : resources) { @@ -255,34 +262,22 @@ class ConfigurationClassParser { } // process individual @Bean methods - Set beanMethods = metadata.getAnnotatedMethods(Bean.class.getName()); + Set beanMethods = sourceClass.getMetadata().getAnnotatedMethods(Bean.class.getName()); for (MethodMetadata methodMetadata : beanMethods) { configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); } // process superclass, if any - if (metadata.hasSuperClass()) { - String superclass = metadata.getSuperClassName(); + if (sourceClass.getMetadata().hasSuperClass()) { + String superclass = sourceClass.getMetadata().getSuperClassName(); if (!this.knownSuperclasses.containsKey(superclass)) { this.knownSuperclasses.put(superclass, configClass); // superclass found, return its annotation metadata and recurse - if (metadata instanceof StandardAnnotationMetadata) { - Class clazz = ((StandardAnnotationMetadata) metadata).getIntrospectedClass(); - return new StandardAnnotationMetadata(clazz.getSuperclass(), true); + try { + return sourceClass.getSuperClass(); } - else if (superclass.startsWith("java")) { - // never load core JDK classes via ASM, in particular not java.lang.Object! - try { - return new StandardAnnotationMetadata( - this.resourceLoader.getClassLoader().loadClass(superclass), true); - } - catch (ClassNotFoundException ex) { - throw new IllegalStateException(ex); - } - } - else { - MetadataReader reader = this.metadataReaderFactory.getMetadataReader(superclass); - return reader.getAnnotationMetadata(); + catch (ClassNotFoundException ex) { + throw new IllegalStateException(ex); } } } @@ -293,24 +288,13 @@ class ConfigurationClassParser { /** * Register member (nested) classes that happen to be configuration classes themselves. - * @param metadata the metadata representation of the containing class + * @param sourceClass the source class to process * @throws IOException if there is any problem reading metadata from a member class */ - private void processMemberClasses(ConfigurationClass configClass, AnnotationMetadata metadata) throws IOException { - if (metadata instanceof StandardAnnotationMetadata) { - for (Class memberClass : ((StandardAnnotationMetadata) metadata).getIntrospectedClass().getDeclaredClasses()) { - if (ConfigurationClassUtils.isConfigurationCandidate(new StandardAnnotationMetadata(memberClass))) { - processConfigurationClass(new ConfigurationClass(memberClass, configClass)); - } - } - } - else { - for (String memberClassName : metadata.getMemberClassNames()) { - MetadataReader reader = this.metadataReaderFactory.getMetadataReader(memberClassName); - AnnotationMetadata memberClassMetadata = reader.getAnnotationMetadata(); - if (ConfigurationClassUtils.isConfigurationCandidate(memberClassMetadata)) { - processConfigurationClass(new ConfigurationClass(reader, configClass)); - } + private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass) throws IOException { + for (SourceClass memberClass : sourceClass.getMemberClasses()) { + if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata())) { + processConfigurationClass(memberClass.asConfigClass(configClass)); } } } @@ -350,59 +334,46 @@ class ConfigurationClassParser { } } + /** + * Returns {@code @Import} class, considering all meta-annotations. + */ + private Set getImports(SourceClass sourceClass) throws IOException { + Set imports = new LinkedHashSet(); + Set visited = new LinkedHashSet(); + collectImports(sourceClass, imports, visited); + return imports; + } + /** * Recursively collect all declared {@code @Import} values. Unlike most * meta-annotations it is valid to have several {@code @Import}s declared with * different values, the usual process or returning values from the first * meta-annotation on a class is not sufficient. - *

    For example, it is common for a {@code @Configuration} class to declare direct + *

    + * For example, it is common for a {@code @Configuration} class to declare direct * {@code @Import}s in addition to meta-imports originating from an {@code @Enable} * annotation. - * @param metadata the metadata representation of the class to search + * + * @param sourceClass the class to search * @param imports the imports collected so far * @param visited used to track visited classes to prevent infinite recursion * @throws IOException if there is any problem reading metadata from the named class */ - private void collectImports(AnnotationMetadata metadata, Set imports, Set visited) throws IOException { - String className = metadata.getClassName(); - if (visited.add(className)) { - if (metadata instanceof StandardAnnotationMetadata) { - StandardAnnotationMetadata stdMetadata = (StandardAnnotationMetadata) metadata; - for (Annotation ann : stdMetadata.getIntrospectedClass().getAnnotations()) { - if (!ann.annotationType().getName().startsWith("java") && !(ann instanceof Import)) { - collectImports(new StandardAnnotationMetadata(ann.annotationType()), imports, visited); - } - } - Map attributes = stdMetadata.getAnnotationAttributes(Import.class.getName(), false); - if (attributes != null) { - Class[] value = (Class[]) attributes.get("value"); - if (!ObjectUtils.isEmpty(value)) { - imports.addAll(Arrays.asList(value)); - } - } - } - else { - for (String annotationType : metadata.getAnnotationTypes()) { - if (!className.startsWith("java") && !className.equals(Import.class.getName())) { - try { - collectImports( - new StandardAnnotationMetadata(this.resourceLoader.getClassLoader().loadClass(annotationType)), - imports, visited); - } - catch (ClassNotFoundException ex) { - // - } - } - } - Map attributes = metadata.getAnnotationAttributes(Import.class.getName(), true); - if (attributes != null) { - String[] value = (String[]) attributes.get("value"); - if (!ObjectUtils.isEmpty(value)) { - imports.addAll(Arrays.asList(value)); + private void collectImports(SourceClass sourceClass, Set imports, + Set visited) throws IOException { + try { + if (visited.add(sourceClass)) { + for (SourceClass annotation : sourceClass.getAnnotations()) { + if(!annotation.getMetadata().getClassName().startsWith("java") && !annotation.isAssignable(Import.class)) { + collectImports(annotation, imports, visited); } } + imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value")); } } + catch (ClassNotFoundException ex) { + throw new NestedIOException("Unable to collect imports", ex); + } } private void processDeferredImportSelectors() { @@ -411,16 +382,21 @@ class ConfigurationClassParser { try { ConfigurationClass configClass = deferredImport.getConfigurationClass(); String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata()); - processImport(configClass, Arrays.asList(imports), false); + processImports(configClass, asSourceClasses(imports), false); } - catch (IOException ex) { + catch (Exception ex) { throw new BeanDefinitionStoreException("Failed to load bean class: ", ex); } } this.deferredImportSelectors.clear(); } - private void processImport(ConfigurationClass configClass, Collection classesToImport, boolean checkForCircularImports) throws IOException { + private void processImports(ConfigurationClass configClass, + Collection sourceClasses, boolean checkForCircularImports) + throws IOException { + if(sourceClasses.isEmpty()) { + return; + } if (checkForCircularImports && this.importStack.contains(configClass)) { this.problemReporter.error(new CircularImportProblem(configClass, this.importStack, configClass.getMetadata())); } @@ -428,37 +404,33 @@ class ConfigurationClassParser { this.importStack.push(configClass); AnnotationMetadata importingClassMetadata = configClass.getMetadata(); try { - for (Object candidate : classesToImport) { - Object candidateToCheck = (candidate instanceof Class ? (Class) candidate : - this.metadataReaderFactory.getMetadataReader((String) candidate)); - if (checkAssignability(ImportSelector.class, candidateToCheck)) { + for (SourceClass candidate : sourceClasses) { + if (candidate.isAssignable(ImportSelector.class)) { // the candidate class is an ImportSelector -> delegate to it to determine imports - Class candidateClass = (candidate instanceof Class ? (Class) candidate : - this.resourceLoader.getClassLoader().loadClass((String) candidate)); + Class candidateClass = candidate.loadClass(); ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class); invokeAwareMethods(selector); if(selector instanceof DeferredImportSelector) { this.deferredImportSelectors.add(new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector)); } else { - processImport(configClass, Arrays.asList(selector.selectImports(importingClassMetadata)), false); + String[] importClassNames = selector.selectImports(importingClassMetadata); + Collection importSourceClasses = asSourceClasses(importClassNames); + processImports(configClass, importSourceClasses, false); } } - else if (checkAssignability(ImportBeanDefinitionRegistrar.class, candidateToCheck)) { + else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) { // the candidate class is an ImportBeanDefinitionRegistrar -> delegate to it to register additional bean definitions - Class candidateClass = (candidate instanceof Class ? (Class) candidate : - this.resourceLoader.getClassLoader().loadClass((String) candidate)); + Class candidateClass = candidate.loadClass(); ImportBeanDefinitionRegistrar registrar = BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class); invokeAwareMethods(registrar); - registrar.registerBeanDefinitions(importingClassMetadata, this.registry); + configClass.addImportBeanDefinitionRegistrar(registrar); } else { // candidate class not an ImportSelector or ImportBeanDefinitionRegistrar -> process it as a @Configuration class this.importStack.registerImport(importingClassMetadata.getClassName(), - (candidate instanceof Class ? ((Class) candidate).getName() : (String) candidate)); - processConfigurationClass((candidateToCheck instanceof Class ? - new ConfigurationClass((Class) candidateToCheck, configClass) : - new ConfigurationClass((MetadataReader) candidateToCheck, configClass))); + candidate.getMetadata().getClassName()); + processConfigurationClass(candidate.asConfigClass(configClass)); } } } @@ -471,21 +443,15 @@ class ConfigurationClassParser { } } - private boolean checkAssignability(Class clazz, Object candidate) throws IOException { - if (candidate instanceof Class) { - return clazz.isAssignableFrom((Class) candidate); - } - else { - return new AssignableTypeFilter(clazz).match((MetadataReader) candidate, this.metadataReaderFactory); - } - } - /** * Invoke {@link ResourceLoaderAware}, {@link BeanClassLoaderAware} and * {@link BeanFactoryAware} contracts if implemented by the given {@code bean}. */ private void invokeAwareMethods(Object importStrategyBean) { if (importStrategyBean instanceof Aware) { + if (importStrategyBean instanceof EnvironmentAware) { + ((EnvironmentAware) importStrategyBean).setEnvironment(this.environment); + } if (importStrategyBean instanceof ResourceLoaderAware) { ((ResourceLoaderAware) importStrategyBean).setResourceLoader(this.resourceLoader); } @@ -524,10 +490,68 @@ class ConfigurationClassParser { return this.importStack; } + /** + * Factory method to obtain a {@link SourceClass} from a {@link ConfigurationClass}. + */ + public SourceClass asSourceClass(ConfigurationClass configurationClass) + throws IOException { + try { + AnnotationMetadata metadata = configurationClass.getMetadata(); + if (metadata instanceof StandardAnnotationMetadata) { + return asSourceClass(((StandardAnnotationMetadata) metadata).getIntrospectedClass()); + } + return asSourceClass(configurationClass.getMetadata().getClassName()); + } + catch (ClassNotFoundException ex) { + throw new IllegalStateException(ex); + } + } + + /** + * Factory method to obtain a {@link SourceClass} from a {@link Class}. + */ + public SourceClass asSourceClass(Class classType) + throws ClassNotFoundException, IOException { + try { + // Sanity test that we can read annotations, if not fall back to ASM + classType.getAnnotations(); + } + catch (Throwable ex) { + return asSourceClass(classType.getName()); + } + return new SourceClass(classType); + } + + /** + * Factory method to obtain {@link SourceClass}s from class names. + */ + public Collection asSourceClasses(String[] classNamess) + throws ClassNotFoundException, IOException { + List annotatedClasses = new ArrayList(); + for (String className : classNamess) { + annotatedClasses.add(asSourceClass(className)); + } + return annotatedClasses; + } + + /** + * Factory method to obtain a {@link SourceClass} from a class name. + */ + public SourceClass asSourceClass(String className) + throws ClassNotFoundException, IOException { + if (className.startsWith("java")) { + // Never use ASM for core java types + return new SourceClass(this.resourceLoader.getClassLoader().loadClass( + className)); + } + return new SourceClass(this.metadataReaderFactory.getMetadataReader(className)); + } + interface ImportRegistry { String getImportingClassFor(String importedClass); + } @@ -607,6 +631,151 @@ class ConfigurationClassParser { } + /** + * Simple wrapper that allows annotated source classes to be dealt with in a uniform + * manor, regardless of how they are loaded. + */ + private class SourceClass { + + private final Object source; // Class or MetaDataReader + + private final AnnotationMetadata metadata; + + + private SourceClass(Object source) { + this.source = source; + if (source instanceof Class) { + this.metadata = new StandardAnnotationMetadata((Class) source, true); + } + else { + this.metadata = ((MetadataReader) source).getAnnotationMetadata(); + } + } + + + public Class loadClass() throws ClassNotFoundException { + if(source instanceof Class) { + return (Class) source; + } + String className = ((MetadataReader) source).getClassMetadata().getClassName(); + return resourceLoader.getClassLoader().loadClass(className); + } + + public boolean isAssignable(Class clazz) throws IOException { + if (source instanceof Class) { + return clazz.isAssignableFrom((Class) source); + } + return new AssignableTypeFilter(clazz).match((MetadataReader) source, + metadataReaderFactory); + } + + public ConfigurationClass asConfigClass(ConfigurationClass importedBy) + throws IOException { + if (this.source instanceof Class) { + return new ConfigurationClass((Class) this.source, importedBy); + } + return new ConfigurationClass((MetadataReader) source, importedBy); + } + + public Collection getMemberClasses() throws IOException { + List members = new ArrayList(); + if (source instanceof Class) { + Class sourceClass = (Class) source; + for (Class declaredClass : sourceClass.getDeclaredClasses()) { + try { + members.add(asSourceClass(declaredClass)); + } + catch (ClassNotFoundException e) { + } + } + } + else { + MetadataReader sourceReader = (MetadataReader) source; + for (String memberClassName : sourceReader.getClassMetadata().getMemberClassNames()) { + try { + members.add(asSourceClass(memberClassName)); + } + catch (ClassNotFoundException e) { + } + } + } + return members; + } + + public SourceClass getSuperClass() throws ClassNotFoundException, IOException { + if (source instanceof Class) { + return asSourceClass(((Class) source).getSuperclass()); + } + return asSourceClass(((MetadataReader) source).getClassMetadata().getSuperClassName()); + } + + public Set getAnnotations() throws ClassNotFoundException, IOException { + Set annotations = new LinkedHashSet(); + for(String annotation : getMetadata().getAnnotationTypes()) { + annotations.add(getRelated(annotation)); + } + return annotations; + } + + public Collection getAnnotationAttributes(String annotationType, + String attribute) throws ClassNotFoundException, IOException { + Map annotationAttributes = getMetadata().getAnnotationAttributes( + annotationType, true); + if (annotationAttributes == null + || !annotationAttributes.containsKey(attribute)) { + return Collections.emptySet(); + } + String[] classNames = (String[]) annotationAttributes.get(attribute); + Set rtn = new LinkedHashSet(); + for (String className : classNames) { + rtn.add(getRelated(className)); + } + return rtn; + } + + private SourceClass getRelated(String className) throws IOException, + ClassNotFoundException { + if (source instanceof Class) { + try { + Class clazz = resourceLoader.getClassLoader().loadClass(className); + return asSourceClass(clazz); + } + catch (ClassNotFoundException ex) { + } + } + return asSourceClass(className); + } + + public AnnotationMetadata getMetadata() { + return this.metadata; + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj == null) { + return false; + } + if (obj instanceof SourceClass) { + return toString().equals(obj.toString()); + } + return false; + } + + @Override + public String toString() { + return getMetadata().getClassName(); + } + } + + /** * {@link Problem} registered upon detection of a circular {@link Import}. */ diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java index 944fd051f2..f659878fb0 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java @@ -16,6 +16,7 @@ package org.springframework.context.annotation; +import java.beans.PropertyDescriptor; import java.io.IOException; import java.util.HashSet; import java.util.LinkedHashMap; @@ -26,17 +27,19 @@ import java.util.Stack; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.beans.BeansException; +import org.springframework.beans.PropertyValues; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter; import org.springframework.beans.factory.config.SingletonBeanRegistry; import org.springframework.beans.factory.parsing.FailFastProblemReporter; import org.springframework.beans.factory.parsing.PassThroughSourceExtractor; @@ -47,8 +50,11 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; import org.springframework.context.EnvironmentAware; import org.springframework.context.ResourceLoaderAware; +import org.springframework.context.annotation.ConfigurationClassEnhancer.EnhancedConfiguration; import org.springframework.context.annotation.ConfigurationClassParser.ImportRegistry; import org.springframework.core.Ordered; import org.springframework.core.PriorityOrdered; @@ -86,7 +92,8 @@ import static org.springframework.context.annotation.AnnotationConfigUtils.*; * @since 3.0 */ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor, - ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware { + ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware, ApplicationContextAware, + Ordered { private static final String IMPORT_AWARE_PROCESSOR_BEAN_NAME = ConfigurationClassPostProcessor.class.getName() + ".importAwareProcessor"; @@ -94,6 +101,9 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo private static final String IMPORT_REGISTRY_BEAN_NAME = ConfigurationClassPostProcessor.class.getName() + ".importRegistry"; + private static final String ENHANCED_CONFIGURATION_PROCESSOR_BEAN_NAME = + ConfigurationClassPostProcessor.class.getName() + ".enhancedConfigurationProcessor"; + private final Log logger = LogFactory.getLog(getClass()); @@ -103,6 +113,8 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo private Environment environment; + private ApplicationContext applicationContext; + private ResourceLoader resourceLoader = new DefaultResourceLoader(); private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); @@ -131,6 +143,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo }; + /** * Set the {@link SourceExtractor} to use for generated bean definitions * that correspond to {@link Bean} factory methods. @@ -190,6 +203,12 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo this.environment = environment; } + @Override + public void setApplicationContext(ApplicationContext applicationContext) + throws BeansException { + this.applicationContext = applicationContext; + } + @Override public void setResourceLoader(ResourceLoader resourceLoader) { Assert.notNull(resourceLoader, "ResourceLoader must not be null"); @@ -214,6 +233,10 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo iabpp.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); registry.registerBeanDefinition(IMPORT_AWARE_PROCESSOR_BEAN_NAME, iabpp); + RootBeanDefinition ecbpp = new RootBeanDefinition(EnhancedConfigurationBeanPostProcessor.class); + ecbpp.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + registry.registerBeanDefinition(ENHANCED_CONFIGURATION_PROCESSOR_BEAN_NAME, ecbpp); + int registryId = System.identityHashCode(registry); if (this.registriesPostProcessed.contains(registryId)) { throw new IllegalStateException( @@ -280,7 +303,8 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo // Parse each @Configuration class ConfigurationClassParser parser = new ConfigurationClassParser( this.metadataReaderFactory, this.problemReporter, this.environment, - this.resourceLoader, this.componentScanBeanNameGenerator, registry); + this.resourceLoader, this.componentScanBeanNameGenerator, registry, + this.applicationContext); parser.parse(configCandidates); parser.validate(); @@ -302,15 +326,12 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo // Read the model and create bean definitions based on its content if (this.reader == null) { this.reader = new ConfigurationClassBeanDefinitionReader( - registry, this.sourceExtractor, this.problemReporter, this.metadataReaderFactory, + registry, this.applicationContext, this.sourceExtractor, + this.problemReporter, this.metadataReaderFactory, this.resourceLoader, this.environment, this.importBeanNameGenerator); } - for (ConfigurationClass configurationClass : parser.getConfigurationClasses()) { - if (!ConditionalAnnotationHelper.shouldSkip(configurationClass, registry, - this.environment, this.importBeanNameGenerator)) { - reader.loadBeanDefinitionsForConfigurationClass(configurationClass); - } - } + + reader.loadBeanDefinitions(parser.getConfigurationClasses()); // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes if (singletonRegistry != null) { @@ -366,6 +387,11 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo } } + @Override + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE; + } + private static class ImportAwareBeanPostProcessor implements PriorityOrdered, BeanFactoryAware, BeanPostProcessor { @@ -410,4 +436,40 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo } } + + /** + * {@link InstantiationAwareBeanPostProcessorAdapter} that ensures + * {@link EnhancedConfiguration} beans are injected with the {@link BeanFactory} + * before the {@link AutowiredAnnotationBeanPostProcessor} runs (SPR-10668). + */ + private static class EnhancedConfigurationBeanPostProcessor extends + InstantiationAwareBeanPostProcessorAdapter implements PriorityOrdered, + BeanFactoryAware { + + private BeanFactory beanFactory; + + @Override + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE; + } + + @Override + public PropertyValues postProcessPropertyValues(PropertyValues pvs, + PropertyDescriptor[] pds, Object bean, String beanName) + throws BeansException { + // Inject the BeanFactory before AutowiredAnnotationBeanPostProcessor's + // postProcessPropertyValues method attempts to auto-wire other configuration + // beans. + if (bean instanceof EnhancedConfiguration) { + ((EnhancedConfiguration) bean).setBeanFactory(this.beanFactory); + } + return pvs; + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + + } } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java index d7bed91502..46a58b7eb0 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,6 @@ import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.StandardAnnotationMetadata; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; -import org.springframework.stereotype.Component; /** * Utilities for processing @{@link Configuration} classes. @@ -105,7 +104,6 @@ abstract class ConfigurationClassUtils { return false; // do not consider an interface or an annotation } return metadata.isAnnotated(Import.class.getName()) || - metadata.isAnnotated(Component.class.getName()) || metadata.hasAnnotatedMethods(Bean.class.getName()); } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationCondition.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationCondition.java new file mode 100644 index 0000000000..641c50b62d --- /dev/null +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationCondition.java @@ -0,0 +1,61 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.context.annotation; + +/** + * A {@link Condition} that offers more fine-grained control when used with + * {@code @Configuration}. Allows certain {@link Condition}s to adapt when they match + * based on the configuration phase. For example, a condition that checks if a bean has + * already been registered might choose to only be evaluated during the + * {@link ConfigurationPhase#REGISTER_BEAN REGISTER_BEAN} {@link ConfigurationPhase}. + * + * @author Phillip Webb + * @since 4.0 + */ +public interface ConfigurationCondition extends Condition { + + /** + * Returns the {@link ConfigurationPhase} in which the condition should be evaluated. + */ + ConfigurationPhase getConfigurationPhase(); + + /** + * The various configuration phases where the condition could be evaluated. + */ + public static enum ConfigurationPhase { + + /** + * The {@link Condition} should be evaluated as a {@code @Configuration} class is + * being parsed. + * + *

    If the condition does not match at this point the {@code @Configuration} + * class will not be added. + */ + PARSE_CONFIGURATION, + + /** + * The {@link Condition} should be evaluated when adding a regular (non + * {@code @Configuration}) bean. The condition will not prevent + * {@code @Configuration} classes from being added. + * + *

    At the time that the condition is evaluated all {@code @Configuration}s + * will have been parsed. + */ + REGISTER_BEAN + } + +} diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ImportBeanDefinitionRegistrar.java b/spring-context/src/main/java/org/springframework/context/annotation/ImportBeanDefinitionRegistrar.java index 648a8e9211..6795d6172c 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ImportBeanDefinitionRegistrar.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ImportBeanDefinitionRegistrar.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,6 +33,7 @@ import org.springframework.core.type.AnnotationMetadata; * {@link org.springframework.beans.factory.Aware Aware} interfaces, and their respective * methods will be called prior to {@link #registerBeanDefinitions}: *

      + *
    • {@link org.springframework.context.EnvironmentAware}
    • *
    • {@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware} *
    • {@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware} *
    • {@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware} diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ImportSelector.java b/spring-context/src/main/java/org/springframework/context/annotation/ImportSelector.java index 5d8848001d..b98719f97f 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ImportSelector.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ImportSelector.java @@ -27,6 +27,7 @@ import org.springframework.core.type.AnnotationMetadata; * {@link org.springframework.beans.factory.Aware Aware} interfaces, and their respective * methods will be called prior to {@link #selectImports}: *
        + *
      • {@link org.springframework.context.EnvironmentAware}
      • *
      • {@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}
      • *
      • {@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}
      • *
      • {@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}
      • diff --git a/spring-context/src/main/java/org/springframework/remoting/rmi/RmiRegistryFactoryBean.java b/spring-context/src/main/java/org/springframework/remoting/rmi/RmiRegistryFactoryBean.java index 0a6ded9114..b7107934b3 100644 --- a/spring-context/src/main/java/org/springframework/remoting/rmi/RmiRegistryFactoryBean.java +++ b/spring-context/src/main/java/org/springframework/remoting/rmi/RmiRegistryFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -181,7 +181,7 @@ public class RmiRegistryFactoryBean implements FactoryBean, Initializi throws RemoteException { if (registryHost != null) { - // Host explictly specified: only lookup possible. + // Host explicitly specified: only lookup possible. if (logger.isInfoEnabled()) { logger.info("Looking for RMI registry at port '" + registryPort + "' of host [" + registryHost + "]"); } diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationPostProcessor.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationPostProcessor.java index 7a61e6b2b1..f016ca1d9a 100644 --- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationPostProcessor.java @@ -83,7 +83,12 @@ public class MethodValidationPostProcessor extends AbstractAdvisingBeanPostProce *

        Default is the default ValidatorFactory's default Validator. */ public void setValidator(Validator validator) { - this.validator = validator; + if(validator instanceof LocalValidatorFactoryBean) { + this.validator = ((LocalValidatorFactoryBean) validator).getValidator(); + } + else { + this.validator = validator; + } } /** diff --git a/spring-context/src/test/java/org/springframework/cache/config/AbstractAnnotationTests.java b/spring-context/src/test/java/org/springframework/cache/config/AbstractAnnotationTests.java index 41d717d692..19f9ded42a 100644 --- a/spring-context/src/test/java/org/springframework/cache/config/AbstractAnnotationTests.java +++ b/spring-context/src/test/java/org/springframework/cache/config/AbstractAnnotationTests.java @@ -367,6 +367,16 @@ public abstract class AbstractAnnotationTests { assertSame(r2, secondary.get(o).get()); } + public void testPutRefersToResult(CacheableService service) throws Exception { + Long id = Long.MIN_VALUE; + TestEntity entity = new TestEntity(); + Cache primary = cm.getCache("primary"); + assertNull(primary.get(id)); + assertNull(entity.getId()); + service.putRefersToResult(entity); + assertSame(entity, primary.get(id).get()); + } + public void testMultiCacheAndEvict(CacheableService service) { String methodName = "multiCacheAndEvict"; @@ -621,6 +631,16 @@ public abstract class AbstractAnnotationTests { testMultiPut(ccs); } + @Test + public void testPutRefersToResult() throws Exception { + testPutRefersToResult(cs); + } + + @Test + public void testClassPutRefersToResult() throws Exception { + testPutRefersToResult(ccs); + } + @Test public void testMultiCacheAndEvict() { testMultiCacheAndEvict(cs); diff --git a/spring-context/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java b/spring-context/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java index f168ab990a..0c5ca6712d 100644 --- a/spring-context/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java +++ b/spring-context/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java @@ -31,6 +31,7 @@ import org.springframework.cache.annotation.Caching; public class AnnotatedClassCacheableService implements CacheableService { private final AtomicLong counter = new AtomicLong(); + public static final AtomicLong nullInvocations = new AtomicLong(); @Override @@ -164,4 +165,11 @@ public class AnnotatedClassCacheableService implements CacheableService public Object multiUpdate(Object arg1) { return arg1; } + + @Override + @CachePut(value="primary", key="#result.id") + public TestEntity putRefersToResult(TestEntity arg1) { + arg1.setId(Long.MIN_VALUE); + return arg1; + } } diff --git a/spring-context/src/test/java/org/springframework/cache/config/CacheableService.java b/spring-context/src/test/java/org/springframework/cache/config/CacheableService.java index 98c1da1e03..6e33a8a471 100644 --- a/spring-context/src/test/java/org/springframework/cache/config/CacheableService.java +++ b/spring-context/src/test/java/org/springframework/cache/config/CacheableService.java @@ -70,4 +70,6 @@ public interface CacheableService { T multiConditionalCacheAndEvict(Object arg1); T multiUpdate(Object arg1); + + TestEntity putRefersToResult(TestEntity arg1); } diff --git a/spring-context/src/test/java/org/springframework/cache/config/DefaultCacheableService.java b/spring-context/src/test/java/org/springframework/cache/config/DefaultCacheableService.java index f987d7bbe6..c7b1df4fcf 100644 --- a/spring-context/src/test/java/org/springframework/cache/config/DefaultCacheableService.java +++ b/spring-context/src/test/java/org/springframework/cache/config/DefaultCacheableService.java @@ -170,4 +170,11 @@ public class DefaultCacheableService implements CacheableService { public Long multiUpdate(Object arg1) { return Long.valueOf(arg1.toString()); } + + @Override + @CachePut(value="primary", key="#result.id") + public TestEntity putRefersToResult(TestEntity arg1) { + arg1.setId(Long.MIN_VALUE); + return arg1; + } } diff --git a/spring-context/src/test/java/org/springframework/cache/config/TestEntity.java b/spring-context/src/test/java/org/springframework/cache/config/TestEntity.java new file mode 100644 index 0000000000..4c43007158 --- /dev/null +++ b/spring-context/src/test/java/org/springframework/cache/config/TestEntity.java @@ -0,0 +1,56 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cache.config; + +import org.springframework.util.ObjectUtils; + +/** + * Simple test entity for use with caching tests. + * + * @author Michael Plšd + */ +public class TestEntity { + + private Long id; + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + @Override + public int hashCode() { + return ObjectUtils.nullSafeHashCode(this.id); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj == null) { + return false; + } + if (obj instanceof TestEntity) { + return ObjectUtils.nullSafeEquals(this.id, ((TestEntity) obj).id); + } + return false; + } +} diff --git a/spring-context/src/test/java/org/springframework/cache/interceptor/SimpleKeyGeneratorTests.java b/spring-context/src/test/java/org/springframework/cache/interceptor/SimpleKeyGeneratorTests.java new file mode 100644 index 0000000000..2d5305dd61 --- /dev/null +++ b/spring-context/src/test/java/org/springframework/cache/interceptor/SimpleKeyGeneratorTests.java @@ -0,0 +1,89 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cache.interceptor; + +import org.junit.Test; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +/** + * Tests for {@link SimpleKeyGenerator} and {@link SimpleKey}. + * + * @author Phillip Webb + */ +public class SimpleKeyGeneratorTests { + + private SimpleKeyGenerator generator = new SimpleKeyGenerator(); + + @Test + public void noValues() throws Exception { + Object k1 = generator.generate(null, null, new Object[] {}); + Object k2 = generator.generate(null, null, new Object[] {}); + Object k3 = generator.generate(null, null, new Object[] { "different" }); + assertThat(k1.hashCode(), equalTo(k2.hashCode())); + assertThat(k1.hashCode(), not(equalTo(k3.hashCode()))); + assertThat(k1, equalTo(k2)); + assertThat(k1, not(equalTo(k3))); + } + + @Test + public void singleValue() throws Exception { + Object k1 = generator.generate(null, null, new Object[] { "a" }); + Object k2 = generator.generate(null, null, new Object[] { "a" }); + Object k3 = generator.generate(null, null, new Object[] { "different" }); + assertThat(k1.hashCode(), equalTo(k2.hashCode())); + assertThat(k1.hashCode(), not(equalTo(k3.hashCode()))); + assertThat(k1, equalTo(k2)); + assertThat(k1, not(equalTo(k3))); + assertThat(k1, equalTo((Object) "a")); + } + + @Test + public void multipleValues() throws Exception { + Object k1 = generator.generate(null, null, new Object[] { "a", 1, "b" }); + Object k2 = generator.generate(null, null, new Object[] { "a", 1, "b" }); + Object k3 = generator.generate(null, null, new Object[] { "b", 1, "a" }); + assertThat(k1.hashCode(), equalTo(k2.hashCode())); + assertThat(k1.hashCode(), not(equalTo(k3.hashCode()))); + assertThat(k1, equalTo(k2)); + assertThat(k1, not(equalTo(k3))); + } + + @Test + public void singleNullValue() throws Exception { + Object k1 = generator.generate(null, null, new Object[] { null }); + Object k2 = generator.generate(null, null, new Object[] { null }); + Object k3 = generator.generate(null, null, new Object[] { "different" }); + assertThat(k1.hashCode(), equalTo(k2.hashCode())); + assertThat(k1.hashCode(), not(equalTo(k3.hashCode()))); + assertThat(k1, equalTo(k2)); + assertThat(k1, not(equalTo(k3))); + assertThat(k1, instanceOf(SimpleKey.class)); + } + + @Test + public void multiplNullValues() throws Exception { + Object k1 = generator.generate(null, null, new Object[] { "a", null, "b", null }); + Object k2 = generator.generate(null, null, new Object[] { "a", null, "b", null }); + Object k3 = generator.generate(null, null, new Object[] { "a", null, "b" }); + assertThat(k1.hashCode(), equalTo(k2.hashCode())); + assertThat(k1.hashCode(), not(equalTo(k3.hashCode()))); + assertThat(k1, equalTo(k2)); + assertThat(k1, not(equalTo(k3))); + } +} diff --git a/spring-context/src/test/java/org/springframework/context/annotation/AnnotationBeanNameGeneratorTests.java b/spring-context/src/test/java/org/springframework/context/annotation/AnnotationBeanNameGeneratorTests.java index e225839893..e2ca64e599 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/AnnotationBeanNameGeneratorTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/AnnotationBeanNameGeneratorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,16 +16,21 @@ package org.springframework.context.annotation; -import example.scannable.DefaultNamedComponent; -import org.junit.Test; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.junit.Test; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.SimpleBeanDefinitionRegistry; import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; +import example.scannable.DefaultNamedComponent; import static org.junit.Assert.*; /** @@ -81,6 +86,22 @@ public class AnnotationBeanNameGeneratorTests { assertEquals(expectedGeneratedBeanName, beanName); } + @Test + public void testGenerateBeanNameFromMetaComponentWithStringValue() { + BeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); + AnnotatedBeanDefinition bd = new AnnotatedGenericBeanDefinition(ComponentFromStringMeta.class); + String beanName = this.beanNameGenerator.generateBeanName(bd, registry); + assertEquals("henry", beanName); + } + + @Test + public void testGenerateBeanNameFromMetaComponentWithNonStringValue() { + BeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); + AnnotatedBeanDefinition bd = new AnnotatedGenericBeanDefinition(ComponentFromNonStringMeta.class); + String beanName = this.beanNameGenerator.generateBeanName(bd, registry); + assertEquals("annotationBeanNameGeneratorTests.ComponentFromNonStringMeta", beanName); + } + @Component("walden") private static class ComponentWithName { @@ -96,4 +117,19 @@ public class AnnotationBeanNameGeneratorTests { private static class AnonymousComponent { } + @Service("henry") + private static class ComponentFromStringMeta { + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + @Component + public @interface NonStringMetaComponent { + long value(); + } + + @NonStringMetaComponent(123) + private static class ComponentFromNonStringMeta { + } + } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/AsmCircularImportDetectionTests.java b/spring-context/src/test/java/org/springframework/context/annotation/AsmCircularImportDetectionTests.java index 61b2612d29..e7795333e5 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/AsmCircularImportDetectionTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/AsmCircularImportDetectionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,7 +41,8 @@ public class AsmCircularImportDetectionTests extends AbstractCircularImportDetec new StandardEnvironment(), new DefaultResourceLoader(), new AnnotationBeanNameGenerator(), - new DefaultListableBeanFactory()); + new DefaultListableBeanFactory(), + null); } @Override diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassWithConditionTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassWithConditionTests.java index 49dfa4a966..d26e6b5f78 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassWithConditionTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassWithConditionTests.java @@ -16,55 +16,80 @@ package org.springframework.context.annotation; -import static org.hamcrest.Matchers.equalTo; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.junit.After; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.core.type.AnnotationMetadata; import org.springframework.stereotype.Component; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + /** - * Test for {@link Conditional} beans. + * Tests for {@link Conditional} beans. * * @author Phillip Webb */ +@SuppressWarnings("resource") public class ConfigurationClassWithConditionTests { + private final AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + @Rule public ExpectedException thrown = ExpectedException.none(); + @After + public void closeContext() { + ctx.close(); + } + @Test - public void conditionalOnBeanMatch() throws Exception { - AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + public void conditionalOnMissingBeanMatch() throws Exception { ctx.register(BeanOneConfiguration.class, BeanTwoConfiguration.class); ctx.refresh(); assertTrue(ctx.containsBean("bean1")); assertFalse(ctx.containsBean("bean2")); + assertFalse(ctx.containsBean("configurationClassWithConditionTests.BeanTwoConfiguration")); + } + + @Test + public void conditionalOnMissingBeanNoMatch() throws Exception { + ctx.register(BeanTwoConfiguration.class); + ctx.refresh(); + assertFalse(ctx.containsBean("bean1")); + assertTrue(ctx.containsBean("bean2")); + assertTrue(ctx.containsBean("configurationClassWithConditionTests.BeanTwoConfiguration")); + } + + @Test + public void conditionalOnBeanMatch() throws Exception { + ctx.register(BeanOneConfiguration.class, BeanThreeConfiguration.class); + ctx.refresh(); + assertTrue(ctx.containsBean("bean1")); + assertTrue(ctx.containsBean("bean3")); } @Test public void conditionalOnBeanNoMatch() throws Exception { - AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); - ctx.register(BeanTwoConfiguration.class); + ctx.register(BeanThreeConfiguration.class); ctx.refresh(); - assertTrue(ctx.containsBean("bean2")); + assertFalse(ctx.containsBean("bean1")); + assertFalse(ctx.containsBean("bean3")); } @Test public void metaConditional() throws Exception { - AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.register(ConfigurationWithMetaCondition.class); ctx.refresh(); assertTrue(ctx.containsBean("bean")); @@ -72,7 +97,6 @@ public class ConfigurationClassWithConditionTests { @Test public void nonConfigurationClass() throws Exception { - AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.register(NonConfigurationClass.class); ctx.refresh(); thrown.expect(NoSuchBeanDefinitionException.class); @@ -81,13 +105,38 @@ public class ConfigurationClassWithConditionTests { @Test public void methodConditional() throws Exception { - AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.register(ConditionOnMethodConfiguration.class); ctx.refresh(); thrown.expect(NoSuchBeanDefinitionException.class); assertNull(ctx.getBean(ExampleBean.class)); } + @Test + public void importsNotCreated() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(ImportsNotCreated.class); + ctx.refresh(); + } + + @Test + public void importsNotLoaded() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(ImportsNotLoaded.class); + ctx.refresh(); + assertThat(ctx.containsBeanDefinition("a"), equalTo(false)); + assertThat(ctx.containsBeanDefinition("b"), equalTo(false)); + } + + @Test + public void sensibleConditionContext() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.setResourceLoader(new DefaultResourceLoader()); + ctx.setClassLoader(getClass().getClassLoader()); + ctx.register(SensibleConditionContext.class); + ctx.refresh(); + assertThat(ctx.getBean(ExampleBean.class), instanceOf(ExampleBean.class)); + } + @Configuration static class BeanOneConfiguration { @Bean @@ -105,6 +154,15 @@ public class ConfigurationClassWithConditionTests { } } + @Configuration + @Conditional(HasBeanOneCondition.class) + static class BeanThreeConfiguration { + @Bean + public ExampleBean bean3() { + return new ExampleBean(); + } + } + @Configuration @MetaConditional("test") static class ConfigurationWithMetaCondition { @@ -135,6 +193,19 @@ public class ConfigurationClassWithConditionTests { } } + static class HasBeanOneCondition implements ConfigurationCondition { + + @Override + public ConfigurationPhase getConfigurationPhase() { + return ConfigurationPhase.REGISTER_BEAN; + } + + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + return context.getBeanFactory().containsBeanDefinition("bean1"); + } + } + static class MetaConditionalFilter implements Condition { @Override @@ -167,6 +238,112 @@ public class ConfigurationClassWithConditionTests { } } + @Configuration + @Never + @Import({ ConfigurationNotCreated.class, RegistrarNotCreated.class, ImportSelectorNotCreated.class }) + static class ImportsNotCreated { + static { + if (true) throw new RuntimeException(); + } + } + + @Configuration + static class ConfigurationNotCreated { + static { + if (true) throw new RuntimeException(); + } + } + + static class RegistrarNotCreated implements ImportBeanDefinitionRegistrar { + static { + if (true) throw new RuntimeException(); + } + + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, + BeanDefinitionRegistry registry) { + } + } + + static class ImportSelectorNotCreated implements ImportSelector { + + static { + if (true) throw new RuntimeException(); + } + + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + return new String[] {}; + } + + } + + @Configuration + @Never + @Import({ ConfigurationNotLoaded.class, RegistrarNotLoaded.class, ImportSelectorNotLoaded.class }) + static class ImportsNotLoaded { + static { + if (true) throw new RuntimeException(); + } + } + + @Configuration + static class ConfigurationNotLoaded { + @Bean + public String a() { + return "a"; + } + } + + static class RegistrarNotLoaded implements ImportBeanDefinitionRegistrar { + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, + BeanDefinitionRegistry registry) { + throw new RuntimeException(); + } + } + + static class ImportSelectorNotLoaded implements ImportSelector { + + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + return new String[] { SelectedConfigurationNotLoaded.class.getName() }; + } + + } + + @Configuration + static class SelectedConfigurationNotLoaded { + @Bean + public String b() { + return "b"; + } + } + + @Configuration + @Conditional(SensibleConditionContextCondition.class) + static class SensibleConditionContext { + @Bean + ExampleBean exampleBean() { + return new ExampleBean(); + } + } + + static class SensibleConditionContextCondition implements Condition { + + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + assertThat(context.getApplicationContext(), notNullValue()); + assertThat(context.getBeanFactory(), notNullValue()); + assertThat(context.getClassLoader(), notNullValue()); + assertThat(context.getEnvironment(), notNullValue()); + assertThat(context.getRegistry(), notNullValue()); + assertThat(context.getResourceLoader(), notNullValue()); + return true; + } + + } + static class ExampleBean { } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ImportBeanDefinitionRegistrarTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ImportBeanDefinitionRegistrarTests.java index 6f55b1f669..6a45feb0d5 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ImportBeanDefinitionRegistrarTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ImportBeanDefinitionRegistrarTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,26 +16,27 @@ package org.springframework.context.annotation; -import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.*; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.junit.Test; - import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.EnvironmentAware; import org.springframework.context.MessageSource; import org.springframework.context.ResourceLoaderAware; +import org.springframework.core.env.Environment; import org.springframework.core.io.ResourceLoader; import org.springframework.core.type.AnnotationMetadata; +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + /** * Integration tests for {@link ImportBeanDefinitionRegistrar}. * @@ -53,6 +54,7 @@ public class ImportBeanDefinitionRegistrarTests { assertThat(SampleRegistrar.beanFactory, is((BeanFactory) context.getBeanFactory())); assertThat(SampleRegistrar.classLoader, is(context.getBeanFactory().getBeanClassLoader())); assertThat(SampleRegistrar.resourceLoader, is(notNullValue())); + assertThat(SampleRegistrar.environment, is((Environment) context.getEnvironment())); } @Sample @@ -69,11 +71,12 @@ public class ImportBeanDefinitionRegistrarTests { } static class SampleRegistrar implements ImportBeanDefinitionRegistrar, BeanClassLoaderAware, ResourceLoaderAware, - BeanFactoryAware { + BeanFactoryAware, EnvironmentAware { static ClassLoader classLoader; static ResourceLoader resourceLoader; static BeanFactory beanFactory; + static Environment environment; @Override public void setBeanClassLoader(ClassLoader classLoader) { @@ -90,6 +93,11 @@ public class ImportBeanDefinitionRegistrarTests { SampleRegistrar.resourceLoader = resourceLoader; } + @Override + public void setEnvironment(Environment environment) { + SampleRegistrar.environment = environment; + } + @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ImportSelectorTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ImportSelectorTests.java index abdfcdbf6d..b15e54221c 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ImportSelectorTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ImportSelectorTests.java @@ -16,11 +16,6 @@ package org.springframework.context.annotation; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.spy; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -28,12 +23,27 @@ import java.lang.annotation.Target; import org.junit.Test; import org.mockito.InOrder; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.context.EnvironmentAware; +import org.springframework.context.MessageSource; +import org.springframework.context.ResourceLoaderAware; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrarTests.SampleRegistrar; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; +import org.springframework.core.env.Environment; +import org.springframework.core.io.ResourceLoader; import org.springframework.core.type.AnnotationMetadata; +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + /** * Tests for {@link ImportSelector} and {@link DeferredImportSelector}. * @@ -50,10 +60,62 @@ public class ImportSelectorTests { context.refresh(); context.getBean(Config.class); InOrder ordered = inOrder(beanFactory); - ordered.verify(beanFactory).registerBeanDefinition(eq("a"), any(BeanDefinition.class)); - ordered.verify(beanFactory).registerBeanDefinition(eq("b"), any(BeanDefinition.class)); - ordered.verify(beanFactory).registerBeanDefinition(eq("d"), any(BeanDefinition.class)); - ordered.verify(beanFactory).registerBeanDefinition(eq("c"), any(BeanDefinition.class)); + ordered.verify(beanFactory).registerBeanDefinition(eq("a"), (BeanDefinition) anyObject()); + ordered.verify(beanFactory).registerBeanDefinition(eq("b"), (BeanDefinition) anyObject()); + ordered.verify(beanFactory).registerBeanDefinition(eq("d"), (BeanDefinition) anyObject()); + ordered.verify(beanFactory).registerBeanDefinition(eq("c"), (BeanDefinition) anyObject()); + } + + @Test + public void invokeAwareMethodsInImportSelector() { + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AwareConfig.class); + context.getBean(MessageSource.class); + + assertThat(SampleRegistrar.beanFactory, is((BeanFactory) context.getBeanFactory())); + assertThat(SampleRegistrar.classLoader, is(context.getBeanFactory().getBeanClassLoader())); + assertThat(SampleRegistrar.resourceLoader, is(notNullValue())); + assertThat(SampleRegistrar.environment, is((Environment) context.getEnvironment())); + } + + @Configuration + @Import(SampleImportSelector.class) + static class AwareConfig { + + } + + static class SampleImportSelector implements ImportSelector, BeanClassLoaderAware, ResourceLoaderAware, + BeanFactoryAware, EnvironmentAware { + + static ClassLoader classLoader; + static ResourceLoader resourceLoader; + static BeanFactory beanFactory; + static Environment environment; + + @Override + public void setBeanClassLoader(ClassLoader classLoader) { + SampleRegistrar.classLoader = classLoader; + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + SampleRegistrar.beanFactory = beanFactory; + } + + @Override + public void setResourceLoader(ResourceLoader resourceLoader) { + SampleRegistrar.resourceLoader = resourceLoader; + } + + @Override + public void setEnvironment(Environment environment) { + SampleRegistrar.environment = environment; + } + + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + return new String[] {}; + } } @Sample diff --git a/spring-context/src/test/java/org/springframework/context/annotation/componentscan/simple/SimpleComponent.java b/spring-context/src/test/java/org/springframework/context/annotation/componentscan/simple/SimpleComponent.java index 956b90d157..9d63ad60a2 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/componentscan/simple/SimpleComponent.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/componentscan/simple/SimpleComponent.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,15 @@ package org.springframework.context.annotation.componentscan.simple; +import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; @Component public class SimpleComponent { + @Bean + public String exampleBean() { + return "example"; + } + } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/configuration/ScopingTests.java b/spring-context/src/test/java/org/springframework/context/annotation/configuration/ScopingTests.java index 52aaaac7e9..dcffc2c2ed 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/configuration/ScopingTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/configuration/ScopingTests.java @@ -193,8 +193,8 @@ public class ScopingTests { @Test public void testScopedConfigurationBeanDefinitionCount() throws Exception { // count the beans - // 6 @Beans + 1 Configuration + 2 scoped proxy + 1 importRegistry - assertEquals(10, ctx.getBeanDefinitionCount()); + // 6 @Beans + 1 Configuration + 2 scoped proxy + 1 importRegistry + 1 enhanced config post processor + assertEquals(11, ctx.getBeanDefinitionCount()); } // /** diff --git a/spring-context/src/test/java/org/springframework/context/annotation/configuration/Spr10668Tests.java b/spring-context/src/test/java/org/springframework/context/annotation/configuration/Spr10668Tests.java new file mode 100644 index 0000000000..d59e4133d5 --- /dev/null +++ b/spring-context/src/test/java/org/springframework/context/annotation/configuration/Spr10668Tests.java @@ -0,0 +1,78 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.context.annotation.configuration; + +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.annotation.Autowired; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.junit.Assert.*; + +/** + * Tests for SPR-10668. + * + * @author Oliver Gierke + * @author Phillip Webb + */ +public class Spr10668Tests { + + @Test + public void testSelfInjectHierarchy() throws Exception { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + ChildConfig.class); + assertNotNull(context.getBean(MyComponent.class)); + context.close(); + } + + @Configuration + public static class ParentConfig implements BeanFactoryAware { + + @Autowired(required = false) + MyComponent component; + + public ParentConfig() { + System.out.println("Parent " + getClass()); + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + System.out.println("BFA " + getClass()); + } + + } + + @Configuration + public static class ChildConfig extends ParentConfig { + + @Bean + public MyComponentImpl myComponent() { + return new MyComponentImpl(); + } + + } + + public static interface MyComponent { + } + + public static class MyComponentImpl implements MyComponent { + } +} diff --git a/spring-context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.java b/spring-context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.java index f8f13285c8..9da942c3ed 100644 --- a/spring-context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.java +++ b/spring-context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.java @@ -265,4 +265,23 @@ public class PropertySourcesPlaceholderConfigurerTests { thrown.expect(IllegalStateException.class); ppc.getAppliedPropertySources(); } + + @Test + public void multipleLocationsWithDefaultResolvedValue() throws Exception { + // SPR-10619 + PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer(); + ClassPathResource doesNotHave = new ClassPathResource("test.properties", getClass()); + ClassPathResource setToTrue = new ClassPathResource("placeholder.properties", getClass()); + ppc.setLocations(new Resource[] { doesNotHave, setToTrue }); + ppc.setIgnoreResourceNotFound(true); + ppc.setIgnoreUnresolvablePlaceholders(true); + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.registerBeanDefinition("testBean", + genericBeanDefinition(TestBean.class) + .addPropertyValue("jedi", "${jedi:false}") + .getBeanDefinition()); + ppc.postProcessBeanFactory(bf); + assertThat(bf.getBean(TestBean.class).isJedi(), equalTo(true)); + } + } diff --git a/spring-context/src/test/java/org/springframework/context/support/placeholder.properties b/spring-context/src/test/java/org/springframework/context/support/placeholder.properties index 6c9d097621..11f0d7f030 100644 --- a/spring-context/src/test/java/org/springframework/context/support/placeholder.properties +++ b/spring-context/src/test/java/org/springframework/context/support/placeholder.properties @@ -1,3 +1,4 @@ targetName=wrappedAssemblerOne logicName=logicTwo realLogicName=realLogic +jedi=true diff --git a/spring-context/src/test/resources/org/springframework/cache/config/cache-advice.xml b/spring-context/src/test/resources/org/springframework/cache/config/cache-advice.xml index 5ebf54ddcb..f13c780916 100644 --- a/spring-context/src/test/resources/org/springframework/cache/config/cache-advice.xml +++ b/spring-context/src/test/resources/org/springframework/cache/config/cache-advice.xml @@ -45,6 +45,7 @@ + @@ -82,6 +83,7 @@ + diff --git a/spring-core/src/main/java/org/springframework/core/io/PathResource.java b/spring-core/src/main/java/org/springframework/core/io/PathResource.java new file mode 100644 index 0000000000..5a2c9f93b5 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/io/PathResource.java @@ -0,0 +1,252 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.core.io; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URI; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.springframework.util.Assert; + +/** + * {@link Resource} implementation for {@code java.nio.file.Path} handles. + * Supports resolution as File, and also as URL. + * Implements the extended {@link WritableResource} interface. + * + * @author Philippe Marschall + * @since 4.0 + * @see java.nio.file.Path + */ +public class PathResource extends AbstractResource implements WritableResource { + + private final Path path; + + + /** + * Create a new PathResource from a Path handle. + *

        Note: Unlike {@link FileSystemResource}, when building relative resources + * via {@link #createRelative}, the relative path will be built underneath the + * given root: + * e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"! + * @param path a Path handle + */ + public PathResource(Path path) { + Assert.notNull(path, "Path must not be null"); + this.path = path.normalize(); + } + + /** + * Create a new PathResource from a Path handle. + *

        Note: Unlike {@link FileSystemResource}, when building relative resources + * via {@link #createRelative}, the relative path will be built underneath the + * given root: + * e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"! + * @param path a path + * @see java.nio.file.Paths#get(String, String...) + */ + public PathResource(String path) { + Assert.notNull(path, "Path must not be null"); + this.path = Paths.get(path).normalize(); + } + + /** + * Create a new PathResource from a Path handle. + *

        Note: Unlike {@link FileSystemResource}, when building relative resources + * via {@link #createRelative}, the relative path will be built underneath the + * given root: + * e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"! + * @see java.nio.file.Paths#get(URI) + * @param uri a path URI + */ + public PathResource(URI uri) { + Assert.notNull(uri, "URI must not be null"); + this.path = Paths.get(uri).normalize(); + } + + + /** + * Return the file path for this resource. + */ + public final String getPath() { + return this.path.toString(); + } + + /** + * This implementation returns whether the underlying file exists. + * @see org.springframework.core.io.PathResource#exists() + */ + @Override + public boolean exists() { + return Files.exists(this.path); + } + + /** + * This implementation checks whether the underlying file is marked as readable + * (and corresponds to an actual file with content, not to a directory). + * @see java.nio.file.Files#isReadable(Path) + * @see java.nio.file.Files#isDirectory(Path, java.nio.file.LinkOption...) + */ + @Override + public boolean isReadable() { + return (Files.isReadable(this.path) && !Files.isDirectory(this.path)); + } + + /** + * This implementation opens a InputStream for the underlying file. + * @see java.nio.file.spi.FileSystemProvider#newInputStream(Path, OpenOption...) + */ + @Override + public InputStream getInputStream() throws IOException { + if(!exists()) { + throw new FileNotFoundException(getPath() + " (No such file or directory)"); + } + if(Files.isDirectory(this.path)) { + throw new FileNotFoundException(getPath() + " (Is a directory)"); + } + return Files.newInputStream(this.path); + } + + /** + * This implementation returns a URL for the underlying file. + * @see java.nio.file.Path#toUri() + * @see java.net.URI#toURL() + */ + @Override + public URL getURL() throws IOException { + return this.path.toUri().toURL(); + } + + /** + * This implementation returns a URI for the underlying file. + * @see java.nio.file.Path#toUri() + */ + @Override + public URI getURI() throws IOException { + return this.path.toUri(); + } + + /** + * This implementation returns the underlying File reference. + */ + @Override + public File getFile() throws IOException { + try { + return this.path.toFile(); + } + catch (UnsupportedOperationException ex) { + // only Paths on the default file system can be converted to a File + // do exception translation for cases where conversion is not possible + throw new FileNotFoundException(this.path + " cannot be resolved to " + + "absolute file path"); + } + } + + /** + * This implementation returns the underlying File's length. + */ + @Override + public long contentLength() throws IOException { + return Files.size(this.path); + } + + /** + * This implementation returns the underlying File's timestamp. + * @see java.nio.file.Files#getLastModifiedTime(Path, java.nio.file.LinkOption...) + */ + @Override + public long lastModified() throws IOException { + // we can not use the super class method since it uses conversion to a File and + // only Paths on the default file system can be converted to a File + return Files.getLastModifiedTime(path).toMillis(); + } + + /** + * This implementation creates a FileResource, applying the given path + * relative to the path of the underlying file of this resource descriptor. + * @see java.nio.file.Path#resolve(String) + */ + @Override + public Resource createRelative(String relativePath) throws IOException { + return new PathResource(this.path.resolve(relativePath)); + } + + /** + * This implementation returns the name of the file. + * @see java.nio.file.Path#getFileName() + */ + @Override + public String getFilename() { + return this.path.getFileName().toString(); + } + + @Override + public String getDescription() { + return "path [" + this.path.toAbsolutePath() + "]"; + } + + // implementation of WritableResource + + /** + * This implementation checks whether the underlying file is marked as writable + * (and corresponds to an actual file with content, not to a directory). + * @see java.nio.file.Files#isWritable(Path) + * @see java.nio.file.Files#isDirectory(Path, java.nio.file.LinkOption...) + */ + @Override + public boolean isWritable() { + return Files.isWritable(this.path) && !Files.isDirectory(this.path); + } + + /** + * This implementation opens a OutputStream for the underlying file. + * @see java.nio.file.spi.FileSystemProvider#newOutputStream(Path, OpenOption...) + */ + @Override + public OutputStream getOutputStream() throws IOException { + if(Files.isDirectory(this.path)) { + throw new FileNotFoundException(getPath() + " (Is a directory)"); + } + return Files.newOutputStream(this.path); + } + + + /** + * This implementation compares the underlying Path references. + */ + @Override + public boolean equals(Object obj) { + return (obj == this || + (obj instanceof PathResource && this.path.equals(((PathResource) obj).path))); + } + + /** + * This implementation returns the hash code of the underlying Path reference. + */ + @Override + public int hashCode() { + return this.path.hashCode(); + } + +} 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 cc6eef308f..e3fc2a4e59 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 @@ -42,6 +42,7 @@ import java.net.URL; * @see UrlResource * @see ByteArrayResource * @see InputStreamResource + * @see PathResource */ public interface Resource extends InputStreamSource { diff --git a/spring-core/src/main/java/org/springframework/util/SocketUtils.java b/spring-core/src/main/java/org/springframework/util/SocketUtils.java new file mode 100644 index 0000000000..4f6958ca71 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/util/SocketUtils.java @@ -0,0 +1,323 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.util; + +import java.net.DatagramSocket; +import java.net.ServerSocket; +import java.util.Random; +import java.util.SortedSet; +import java.util.TreeSet; + +import javax.net.ServerSocketFactory; + +/** + * Simple utility methods for working with network sockets — for example, + * for finding available ports on {@code localhost}. + * + *

        Within this class, a TCP port refers to a port for a {@link ServerSocket}; + * whereas, a UDP port refers to a port for a {@link DatagramSocket}. + * + * @author Sam Brannen + * @author Ben Hale + * @author Arjen Poutsma + * @author Gunnar Hillert + * @since 4.0 + */ +public final class SocketUtils { + + /** + * The default minimum value for port ranges used when finding an available + * socket port. + */ + public static final int PORT_RANGE_MIN = 1024; + + /** + * The default maximum value for port ranges used when finding an available + * socket port. + */ + public static final int PORT_RANGE_MAX = 65535; + + private static final Random random = new Random(System.currentTimeMillis()); + + + /** + * Although {@code SocketUtils} consists solely of static utility methods, + * this constructor is intentionally {@code public}. + * + *

        Rationale

        + * + *

        Static methods from this class may be invoked from within XML + * configuration files using the Spring Expression Language (SpEL) and the + * following syntax. + * + *

        <bean id="bean1" ... p:port="#{T(org.springframework.util.SocketUtils).findAvailableTcpPort(12000)}" />
        + * + * If this constructor were {@code private}, you would be required to supply + * the fully qualified class name to SpEL's {@code T()} function for each usage. + * Thus, the fact that this constructor is {@code public} allows you to reduce + * boilerplate configuration with SpEL as can be seen in the following example. + * + *
        <bean id="socketUtils" class="org.springframework.util.SocketUtils" />
        +	 *
        +	 *<bean id="bean1" ... p:port="#{socketUtils.findAvailableTcpPort(12000)}" />
        +	 *
        +	 *<bean id="bean2" ... p:port="#{socketUtils.findAvailableTcpPort(30000)}" />
        + */ + public SocketUtils() { + /* no-op */ + } + + /** + * Find an available TCP port randomly selected from the range + * [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}]. + * + * @return an available TCP port number + * @throws IllegalStateException if no available port could be found + */ + public static int findAvailableTcpPort() { + return findAvailableTcpPort(PORT_RANGE_MIN); + } + + /** + * Find an available TCP port randomly selected from the range + * [{@code minPort}, {@value #PORT_RANGE_MAX}]. + * + * @param minPort the minimum port number + * @return an available TCP port number + * @throws IllegalStateException if no available port could be found + */ + public static int findAvailableTcpPort(int minPort) { + return findAvailableTcpPort(minPort, PORT_RANGE_MAX); + } + + /** + * Find an available TCP port randomly selected from the range + * [{@code minPort}, {@code maxPort}]. + * + * @param minPort the minimum port number + * @param maxPort the maximum port number + * @return an available TCP port number + * @throws IllegalStateException if no available port could be found + */ + public static int findAvailableTcpPort(int minPort, int maxPort) { + return SocketType.TCP.findAvailablePort(minPort, maxPort); + } + + /** + * Find the requested number of available TCP ports, each randomly selected + * from the range [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}]. + * + * @param numRequested the number of available ports to find + * @return a sorted set of available TCP port numbers + * @throws IllegalStateException if the requested number of available ports could not be found + */ + public static SortedSet findAvailableTcpPorts(int numRequested) { + return findAvailableTcpPorts(numRequested, PORT_RANGE_MIN, PORT_RANGE_MAX); + } + + /** + * Find the requested number of available TCP ports, each randomly selected + * from the range [{@code minPort}, {@code maxPort}]. + * + * @param numRequested the number of available ports to find + * @param minPort the minimum port number + * @param maxPort the maximum port number + * @return a sorted set of available TCP port numbers + * @throws IllegalStateException if the requested number of available ports could not be found + */ + public static SortedSet findAvailableTcpPorts(int numRequested, int minPort, int maxPort) { + return SocketType.TCP.findAvailablePorts(numRequested, minPort, maxPort); + } + + /** + * Find an available UDP port randomly selected from the range + * [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}]. + * + * @return an available UDP port number + * @throws IllegalStateException if no available port could be found + */ + public static int findAvailableUdpPort() { + return findAvailableUdpPort(PORT_RANGE_MIN); + } + + /** + * Find an available UDP port randomly selected from the range + * [{@code minPort}, {@value #PORT_RANGE_MAX}]. + * + * @param minPort the minimum port number + * @return an available UDP port number + * @throws IllegalStateException if no available port could be found + */ + public static int findAvailableUdpPort(int minPort) { + return findAvailableUdpPort(minPort, PORT_RANGE_MAX); + } + + /** + * Find an available UDP port randomly selected from the range + * [{@code minPort}, {@code maxPort}]. + * + * @param minPort the minimum port number + * @param maxPort the maximum port number + * @return an available UDP port number + * @throws IllegalStateException if no available port could be found + */ + public static int findAvailableUdpPort(int minPort, int maxPort) { + return SocketType.UDP.findAvailablePort(minPort, maxPort); + } + + /** + * Find the requested number of available UDP ports, each randomly selected + * from the range [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}]. + * + * @param numRequested the number of available ports to find + * @return a sorted set of available UDP port numbers + * @throws IllegalStateException if the requested number of available ports could not be found + */ + public static SortedSet findAvailableUdpPorts(int numRequested) { + return findAvailableUdpPorts(numRequested, PORT_RANGE_MIN, PORT_RANGE_MAX); + } + + /** + * Find the requested number of available UDP ports, each randomly selected + * from the range [{@code minPort}, {@code maxPort}]. + * + * @param numRequested the number of available ports to find + * @param minPort the minimum port number + * @param maxPort the maximum port number + * @return a sorted set of available UDP port numbers + * @throws IllegalStateException if the requested number of available ports could not be found + */ + public static SortedSet findAvailableUdpPorts(int numRequested, int minPort, int maxPort) { + return SocketType.UDP.findAvailablePorts(numRequested, minPort, maxPort); + } + + + private static enum SocketType { + + TCP { + + @Override + protected boolean isPortAvailable(int port) { + try { + ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket(port); + serverSocket.close(); + return true; + } + catch (Exception ex) { + return false; + } + } + }, + + UDP { + + @Override + protected boolean isPortAvailable(int port) { + try { + DatagramSocket socket = new DatagramSocket(port); + socket.close(); + return true; + } + catch (Exception ex) { + return false; + } + } + }; + + /** + * Determine if the specified port for this {@code SocketType} is + * currently available on {@code localhost}. + */ + protected abstract boolean isPortAvailable(int port); + + /** + * Find a pseudo-random port number within the range + * [{@code minPort}, {@code maxPort}]. + * + * @param minPort the minimum port number + * @param maxPort the maximum port number + * @return a random port number within the specified range + */ + private int findRandomPort(int minPort, int maxPort) { + int portRange = maxPort - minPort; + return minPort + random.nextInt(portRange); + } + + /** + * Find an available port for this {@code SocketType}, randomly selected + * from the range [{@code minPort}, {@code maxPort}]. + * + * @param minPort the minimum port number + * @param maxPort the maximum port number + * @return an available port number for this socket type + * @throws IllegalStateException if no available port could be found + */ + int findAvailablePort(int minPort, int maxPort) { + Assert.isTrue(minPort > 0, "'minPort' must be greater than 0"); + Assert.isTrue(maxPort > minPort, "'maxPort' must be greater than 'minPort'"); + Assert.isTrue(maxPort <= PORT_RANGE_MAX, "'maxPort' must be less than or equal to " + PORT_RANGE_MAX); + + int portRange = maxPort - minPort; + int candidatePort; + int searchCounter = 0; + do { + if (++searchCounter > portRange) { + throw new IllegalStateException(String.format( + "Could not find an available %s port in the range [%d, %d] after %d attempts", name(), minPort, + maxPort, searchCounter)); + } + candidatePort = findRandomPort(minPort, maxPort); + } while (!isPortAvailable(candidatePort)); + + return candidatePort; + } + + /** + * Find the requested number of available ports for this {@code SocketType}, + * each randomly selected from the range [{@code minPort}, {@code maxPort}]. + * + * @param numRequested the number of available ports to find + * @param minPort the minimum port number + * @param maxPort the maximum port number + * @return a sorted set of available port numbers for this socket type + * @throws IllegalStateException if the requested number of available ports could not be found + */ + SortedSet findAvailablePorts(int numRequested, int minPort, int maxPort) { + Assert.isTrue(minPort > 0, "'minPort' must be greater than 0"); + Assert.isTrue(maxPort > minPort, "'maxPort' must be greater than 'minPort'"); + Assert.isTrue(maxPort <= PORT_RANGE_MAX, "'maxPort' must be less than or equal to " + PORT_RANGE_MAX); + Assert.isTrue(numRequested > 0, "'numRequested' must be greater than 0"); + Assert.isTrue((maxPort - minPort) >= numRequested, + "'numRequested' must not be greater than 'maxPort' - 'minPort'"); + + final SortedSet availablePorts = new TreeSet(); + int attemptCount = 0; + while ((++attemptCount <= numRequested + 100) && (availablePorts.size() < numRequested)) { + availablePorts.add(findAvailablePort(minPort, maxPort)); + } + + if (availablePorts.size() != numRequested) { + throw new IllegalStateException(String.format( + "Could not find %d available %s ports in the range [%d, %d]", numRequested, name(), minPort, + maxPort)); + } + + return availablePorts; + } + } + +} diff --git a/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java b/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java index 5bdc32d9a2..fecad196da 100644 --- a/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java +++ b/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -65,10 +65,13 @@ public class GenericTypeResolverTests { @Test public void methodReturnTypes() { - assertEquals(Integer.class, resolveReturnTypeArgument(findMethod(MyTypeWithMethods.class, "integer"), MyInterfaceType.class)); - assertEquals(String.class, resolveReturnTypeArgument(findMethod(MyTypeWithMethods.class, "string"), MyInterfaceType.class)); + assertEquals(Integer.class, + resolveReturnTypeArgument(findMethod(MyTypeWithMethods.class, "integer"), MyInterfaceType.class)); + assertEquals(String.class, + resolveReturnTypeArgument(findMethod(MyTypeWithMethods.class, "string"), MyInterfaceType.class)); assertEquals(null, resolveReturnTypeArgument(findMethod(MyTypeWithMethods.class, "raw"), MyInterfaceType.class)); - assertEquals(null, resolveReturnTypeArgument(findMethod(MyTypeWithMethods.class, "object"), MyInterfaceType.class)); + assertEquals(null, + resolveReturnTypeArgument(findMethod(MyTypeWithMethods.class, "object"), MyInterfaceType.class)); } /** @@ -135,20 +138,22 @@ public class GenericTypeResolverTests { */ @Test public void testResolveType() { - Method intMessageMethod = findMethod(MyTypeWithMethods.class, "readIntegerInputMessage", MyInterfaceType.class); - MethodParameter intMessageMethodParam = new MethodParameter(intMessageMethod, 0); - assertEquals(MyInterfaceType.class, - resolveType(intMessageMethodParam.getGenericParameterType(), new HashMap())); + Method intMessageMethod = findMethod(MyTypeWithMethods.class, "readIntegerInputMessage", MyInterfaceType.class); + MethodParameter intMessageMethodParam = new MethodParameter(intMessageMethod, 0); + assertEquals(MyInterfaceType.class, + resolveType(intMessageMethodParam.getGenericParameterType(), new HashMap())); - Method intArrMessageMethod = findMethod(MyTypeWithMethods.class, "readIntegerArrayInputMessage", MyInterfaceType[].class); - MethodParameter intArrMessageMethodParam = new MethodParameter(intArrMessageMethod, 0); - assertEquals(MyInterfaceType[].class, - resolveType(intArrMessageMethodParam.getGenericParameterType(), new HashMap())); + Method intArrMessageMethod = findMethod(MyTypeWithMethods.class, "readIntegerArrayInputMessage", + MyInterfaceType[].class); + MethodParameter intArrMessageMethodParam = new MethodParameter(intArrMessageMethod, 0); + assertEquals(MyInterfaceType[].class, + resolveType(intArrMessageMethodParam.getGenericParameterType(), new HashMap())); - Method genericArrMessageMethod = findMethod(MySimpleTypeWithMethods.class, "readGenericArrayInputMessage", Object[].class); - MethodParameter genericArrMessageMethodParam = new MethodParameter(genericArrMessageMethod, 0); - Map varMap = getTypeVariableMap(MySimpleTypeWithMethods.class); - assertEquals(Integer[].class, resolveType(genericArrMessageMethodParam.getGenericParameterType(), varMap)); + Method genericArrMessageMethod = findMethod(MySimpleTypeWithMethods.class, "readGenericArrayInputMessage", + Object[].class); + MethodParameter genericArrMessageMethodParam = new MethodParameter(genericArrMessageMethod, 0); + Map varMap = getTypeVariableMap(MySimpleTypeWithMethods.class); + assertEquals(Integer[].class, resolveType(genericArrMessageMethodParam.getGenericParameterType(), varMap)); } @@ -171,26 +176,44 @@ public class GenericTypeResolverTests { } public static class MyTypeWithMethods { - public MyInterfaceType integer() { return null; } - public MySimpleInterfaceType string() { return null; } - public Object object() { return null; } + + public MyInterfaceType integer() { + return null; + } + + public MySimpleInterfaceType string() { + return null; + } + + public Object object() { + return null; + } + @SuppressWarnings("rawtypes") - public MyInterfaceType raw() { return null; } - public String notParameterized() { return null; } - public String notParameterizedWithArguments(Integer x, Boolean b) { return null; } + public MyInterfaceType raw() { + return null; + } + + public String notParameterized() { + return null; + } + + public String notParameterizedWithArguments(Integer x, Boolean b) { + return null; + } /** - * Simulates a factory method that wraps the supplied object in a proxy - * of the same type. + * Simulates a factory method that wraps the supplied object in a proxy of the + * same type. */ public static T createProxy(T object) { return null; } /** - * Similar to {@link #createProxy(Object)} but adds an additional argument - * before the argument of type {@code T}. Note that they may potentially - * be of the same time when invoked! + * Similar to {@link #createProxy(Object)} but adds an additional argument before + * the argument of type {@code T}. Note that they may potentially be of the same + * time when invoked! */ public static T createNamedProxy(String name, T object) { return null; @@ -204,8 +227,8 @@ public class GenericTypeResolverTests { } /** - * Similar to {@link #createMock(Class)} but adds an additional method - * argument before the parameterized argument. + * Similar to {@link #createMock(Class)} but adds an additional method argument + * before the parameterized argument. */ public static T createNamedMock(String name, Class toMock) { return null; @@ -220,8 +243,8 @@ public class GenericTypeResolverTests { } /** - * Extract some value of the type supported by the interface (i.e., by - * a concrete, non-generic implementation of the interface). + * Extract some value of the type supported by the interface (i.e., by a concrete, + * non-generic implementation of the interface). */ public static T extractValueFrom(MyInterfaceType myInterfaceType) { return null; diff --git a/spring-core/src/test/java/org/springframework/core/io/PathResourceTests.java b/spring-core/src/test/java/org/springframework/core/io/PathResourceTests.java new file mode 100644 index 0000000000..a4f87b638f --- /dev/null +++ b/spring-core/src/test/java/org/springframework/core/io/PathResourceTests.java @@ -0,0 +1,277 @@ +/* + * 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.io; + +import java.io.File; +import java.io.FileNotFoundException; +import java.net.URI; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.springframework.util.FileCopyUtils; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; +import static org.mockito.BDDMockito.*; + +/** + * Unit tests for the {@link PathResource} class. + * + * @author Philippe Marschall + * @author Phillip Webb + */ +public class PathResourceTests { + + private static final String TEST_DIR = "src/test/java/org/springframework/core/io"; + + private static final String TEST_FILE = "src/test/java/org/springframework/core/io/example.properties"; + + private static final String NON_EXISTING_FILE = "src/test/java/org/springframework/core/io/doesnotexist.properties"; + + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + + @Test + public void nullPath() throws Exception { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Path must not be null"); + new PathResource((Path) null); + } + + @Test + public void nullPathString() throws Exception { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Path must not be null"); + new PathResource((String) null); + } + + @Test + public void nullUri() throws Exception { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("URI must not be null"); + new PathResource((URI) null); + } + + @Test + public void createFromPath() throws Exception { + Path path = Paths.get(TEST_FILE); + PathResource resource = new PathResource(path); + assertThat(resource.getPath(), equalTo(TEST_FILE)); + } + + @Test + public void createFromString() throws Exception { + PathResource resource = new PathResource(TEST_FILE); + assertThat(resource.getPath(), equalTo(TEST_FILE)); + } + + @Test + public void createFromUri() throws Exception { + File file = new File(TEST_FILE); + PathResource resource = new PathResource(file.toURI()); + assertThat(resource.getPath(), equalTo(file.getAbsoluteFile().toString())); + } + + @Test + public void getPathForFile() throws Exception { + PathResource resource = new PathResource(TEST_FILE); + assertThat(resource.getPath(), equalTo(TEST_FILE)); + } + + @Test + public void getPathForDir() throws Exception { + PathResource resource = new PathResource(TEST_DIR); + assertThat(resource.getPath(), equalTo(TEST_DIR)); + } + + @Test + public void fileExists() throws Exception { + PathResource resource = new PathResource(TEST_FILE); + assertThat(resource.exists(), equalTo(true)); + } + + @Test + public void dirExists() throws Exception { + PathResource resource = new PathResource(TEST_DIR); + assertThat(resource.exists(), equalTo(true)); + } + + @Test + public void fileDoesNotExist() throws Exception { + PathResource resource = new PathResource(NON_EXISTING_FILE); + assertThat(resource.exists(), equalTo(false)); + } + + @Test + public void fileIsReadable() throws Exception { + PathResource resource = new PathResource(TEST_FILE); + assertThat(resource.isReadable(), equalTo(true)); + } + + @Test + public void doesNotExistIsNotReadable() throws Exception { + PathResource resource = new PathResource(NON_EXISTING_FILE); + assertThat(resource.isReadable(), equalTo(false)); + } + + @Test + public void directoryIsNotReadable() throws Exception { + PathResource resource = new PathResource(TEST_DIR); + assertThat(resource.isReadable(), equalTo(false)); + } + + @Test + public void getInputStream() throws Exception { + PathResource resource = new PathResource(TEST_FILE); + byte[] bytes = FileCopyUtils.copyToByteArray(resource.getInputStream()); + assertThat(bytes.length, greaterThan(0)); + } + + @Test + public void getInputStreamForDir() throws Exception { + PathResource resource = new PathResource(TEST_DIR); + thrown.expect(FileNotFoundException.class); + resource.getInputStream(); + } + + @Test + public void getInputStreamDoesNotExist() throws Exception { + PathResource resource = new PathResource(NON_EXISTING_FILE); + thrown.expect(FileNotFoundException.class); + resource.getInputStream(); + } + + @Test + public void getUrl() throws Exception { + PathResource resource = new PathResource(TEST_FILE); + assertTrue(resource.getURL().toString().endsWith(TEST_FILE)); + } + + @Test + public void getUri() throws Exception { + PathResource resource = new PathResource(TEST_FILE); + assertTrue(resource.getURI().toString().endsWith(TEST_FILE)); + } + + @Test + public void getFile() throws Exception { + PathResource resource = new PathResource(TEST_FILE); + File file = new File(TEST_FILE); + assertThat(resource.getFile().getAbsoluteFile(), equalTo(file.getAbsoluteFile())); + } + + @Test + public void getFileUnsupported() throws Exception { + Path path = mock(Path.class); + given(path.normalize()).willReturn(path); + given(path.toFile()).willThrow(new UnsupportedOperationException()); + PathResource resource = new PathResource(path); + thrown.expect(FileNotFoundException.class); + resource.getFile(); + } + + @Test + public void contentLength() throws Exception { + PathResource resource = new PathResource(TEST_FILE); + File file = new File(TEST_FILE); + assertThat(resource.contentLength(), equalTo(file.length())); + } + + @Test + public void contentLengthForDirectory() throws Exception { + PathResource resource = new PathResource(TEST_DIR); + File file = new File(TEST_DIR); + assertThat(resource.contentLength(), equalTo(file.length())); + } + + @Test + public void lastModified() throws Exception { + PathResource resource = new PathResource(TEST_FILE); + File file = new File(TEST_FILE); + assertThat(resource.lastModified(), equalTo(file.lastModified())); + } + + @Test + public void createRelativeFromDir() throws Exception { + Resource resource = new PathResource(TEST_DIR).createRelative("example.properties"); + assertThat(resource, equalTo((Resource) new PathResource(TEST_FILE))); + } + + @Test + public void createRelativeFromFile() throws Exception { + Resource resource = new PathResource(TEST_FILE).createRelative("../example.properties"); + assertThat(resource, equalTo((Resource) new PathResource(TEST_FILE))); + } + + @Test + public void filename() throws Exception { + Resource resource = new PathResource(TEST_FILE); + assertThat(resource.getFilename(), equalTo("example.properties")); + } + + @Test + public void description() throws Exception { + Resource resource = new PathResource(TEST_FILE); + assertThat(resource.getDescription(), containsString("path [")); + assertThat(resource.getDescription(), containsString(TEST_FILE)); + } + + @Test + public void fileIsWritable() throws Exception { + PathResource resource = new PathResource(TEST_FILE); + assertThat(resource.isWritable(), equalTo(true)); + } + + @Test + public void directoryIsNotWritable() throws Exception { + PathResource resource = new PathResource(TEST_DIR); + assertThat(resource.isWritable(), equalTo(false)); + } + + @Test + public void outputStream() throws Exception { + PathResource resource = new PathResource(temporaryFolder.newFile("test").toPath()); + FileCopyUtils.copy("test".getBytes(), resource.getOutputStream()); + assertThat(resource.contentLength(), equalTo(4L)); + } + + @Test + public void doesNotExistOutputStream() throws Exception { + File file = temporaryFolder.newFile("test"); + file.delete(); + PathResource resource = new PathResource(file.toPath()); + FileCopyUtils.copy("test".getBytes(), resource.getOutputStream()); + assertThat(resource.contentLength(), equalTo(4L)); + } + + @Test + public void directoryOutputStream() throws Exception { + PathResource resource = new PathResource(TEST_DIR); + thrown.expect(FileNotFoundException.class); + resource.getOutputStream(); + } + +} diff --git a/spring-core/src/test/java/org/springframework/util/SocketUtilsTests.java b/spring-core/src/test/java/org/springframework/util/SocketUtilsTests.java new file mode 100644 index 0000000000..e69767c491 --- /dev/null +++ b/spring-core/src/test/java/org/springframework/util/SocketUtilsTests.java @@ -0,0 +1,180 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.util; + +import java.util.SortedSet; + +import org.junit.Test; + +import static org.junit.Assert.*; +import static org.springframework.util.SocketUtils.*; + +/** + * Unit tests for {@link SocketUtils}. + * + * @author Sam Brannen + * @since 4.0 + */ +public class SocketUtilsTests { + + private void assertPortInRange(int port, int minPort, int maxPort) { + assertTrue("port [" + port + "] >= " + minPort, port >= minPort); + assertTrue("port [" + port + "] <= " + maxPort, port <= maxPort); + } + + private void assertAvailablePorts(SortedSet ports, int numRequested, int minPort, int maxPort) { + assertEquals("number of ports requested", numRequested, ports.size()); + for (int port : ports) { + assertPortInRange(port, minPort, maxPort); + } + } + + // --- TCP ----------------------------------------------------------------- + + @Test(expected = IllegalArgumentException.class) + public void findAvailableTcpPortWithZeroMinPort() { + SocketUtils.findAvailableTcpPort(0); + } + + @Test(expected = IllegalArgumentException.class) + public void findAvailableTcpPortWithNegativeMinPort() { + SocketUtils.findAvailableTcpPort(-500); + } + + @Test + public void findAvailableTcpPort() { + int port = SocketUtils.findAvailableTcpPort(); + assertPortInRange(port, PORT_RANGE_MIN, PORT_RANGE_MAX); + } + + @Test + public void findAvailableTcpPortWithMin() { + int port = SocketUtils.findAvailableTcpPort(50000); + assertPortInRange(port, 50000, PORT_RANGE_MAX); + } + + @Test + public void findAvailableTcpPortInRange() { + int minPort = 20000; + int maxPort = minPort + 1000; + int port = SocketUtils.findAvailableTcpPort(minPort, maxPort); + assertPortInRange(port, minPort, maxPort); + } + + @Test + public void find4AvailableTcpPorts() { + findAvailableTcpPorts(4); + } + + @Test + public void find50AvailableTcpPorts() { + findAvailableTcpPorts(50); + } + + @Test + public void find4AvailableTcpPortsInRange() { + findAvailableTcpPorts(4, 30000, 35000); + } + + @Test + public void find50AvailableTcpPortsInRange() { + findAvailableTcpPorts(50, 40000, 45000); + } + + @Test(expected = IllegalArgumentException.class) + public void findAvailableTcpPortsWithRequestedNumberGreaterThanSizeOfRange() { + findAvailableTcpPorts(50, 45000, 45010); + } + + private void findAvailableTcpPorts(int numRequested) { + SortedSet ports = SocketUtils.findAvailableTcpPorts(numRequested); + assertAvailablePorts(ports, numRequested, PORT_RANGE_MIN, PORT_RANGE_MAX); + } + + private void findAvailableTcpPorts(int numRequested, int minPort, int maxPort) { + SortedSet ports = SocketUtils.findAvailableTcpPorts(numRequested, minPort, maxPort); + assertAvailablePorts(ports, numRequested, minPort, maxPort); + } + + // --- UDP ----------------------------------------------------------------- + + @Test(expected = IllegalArgumentException.class) + public void findAvailableUdpPortWithZeroMinPort() { + SocketUtils.findAvailableUdpPort(0); + } + + @Test(expected = IllegalArgumentException.class) + public void findAvailableUdpPortWithNegativeMinPort() { + SocketUtils.findAvailableUdpPort(-500); + } + + @Test + public void findAvailableUdpPort() { + int port = SocketUtils.findAvailableUdpPort(); + assertPortInRange(port, PORT_RANGE_MIN, PORT_RANGE_MAX); + } + + @Test + public void findAvailableUdpPortWithMin() { + int port = SocketUtils.findAvailableUdpPort(50000); + assertPortInRange(port, 50000, PORT_RANGE_MAX); + } + + @Test + public void findAvailableUdpPortInRange() { + int minPort = 20000; + int maxPort = minPort + 1000; + int port = SocketUtils.findAvailableUdpPort(minPort, maxPort); + assertPortInRange(port, minPort, maxPort); + } + + @Test + public void find4AvailableUdpPorts() { + findAvailableUdpPorts(4); + } + + @Test + public void find50AvailableUdpPorts() { + findAvailableUdpPorts(50); + } + + @Test + public void find4AvailableUdpPortsInRange() { + findAvailableUdpPorts(4, 30000, 35000); + } + + @Test + public void find50AvailableUdpPortsInRange() { + findAvailableUdpPorts(50, 40000, 45000); + } + + @Test(expected = IllegalArgumentException.class) + public void findAvailableUdpPortsWithRequestedNumberGreaterThanSizeOfRange() { + findAvailableUdpPorts(50, 45000, 45010); + } + + private void findAvailableUdpPorts(int numRequested) { + SortedSet ports = SocketUtils.findAvailableUdpPorts(numRequested); + assertAvailablePorts(ports, numRequested, PORT_RANGE_MIN, PORT_RANGE_MAX); + } + + private void findAvailableUdpPorts(int numRequested, int minPort, int maxPort) { + SortedSet ports = SocketUtils.findAvailableUdpPorts(numRequested, minPort, maxPort); + assertAvailablePorts(ports, numRequested, minPort, maxPort); + } + +} diff --git a/spring-expression/src/main/java/org/springframework/expression/ConstructorExecutor.java b/spring-expression/src/main/java/org/springframework/expression/ConstructorExecutor.java index bb04735237..71efb3039a 100644 --- a/spring-expression/src/main/java/org/springframework/expression/ConstructorExecutor.java +++ b/spring-expression/src/main/java/org/springframework/expression/ConstructorExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,13 +19,15 @@ package org.springframework.expression; // TODO Is the resolver/executor model too pervasive in this package? /** - * Executors are built by resolvers and can be cached by the infrastructure to repeat an operation quickly without going - * back to the resolvers. For example, the particular constructor to run on a class may be discovered by the reflection - * constructor resolver - it will then build a ConstructorExecutor that executes that constructor and the - * ConstructorExecutor can be reused without needing to go back to the resolver to discover the constructor again. + * Executors are built by resolvers and can be cached by the infrastructure to repeat an + * operation quickly without going back to the resolvers. For example, the particular + * constructor to run on a class may be discovered by the reflection constructor resolver + * - it will then build a ConstructorExecutor that executes that constructor and the + * ConstructorExecutor can be reused without needing to go back to the resolver to + * discover the constructor again. * - * They can become stale, and in that case should throw an AccessException - this will cause the infrastructure to go - * back to the resolvers to ask for a new one. + *

        They can become stale, and in that case should throw an AccessException - this will + * cause the infrastructure to go back to the resolvers to ask for a new one. * * @author Andy Clement * @since 3.0 @@ -34,11 +36,13 @@ public interface ConstructorExecutor { /** * Execute a constructor in the specified context using the specified arguments. + * * @param context the evaluation context in which the command is being executed - * @param arguments the arguments to the constructor call, should match (in terms of number and type) whatever the - * command will need to run + * @param arguments the arguments to the constructor call, should match (in terms of + * number and type) whatever the command will need to run * @return the new object - * @throws AccessException if there is a problem executing the command or the CommandExecutor is no longer valid + * @throws AccessException if there is a problem executing the command or the + * CommandExecutor is no longer valid */ TypedValue execute(EvaluationContext context, Object... arguments) throws AccessException; diff --git a/spring-expression/src/main/java/org/springframework/expression/ConstructorResolver.java b/spring-expression/src/main/java/org/springframework/expression/ConstructorResolver.java index e1892d86c0..d942978af9 100644 --- a/spring-expression/src/main/java/org/springframework/expression/ConstructorResolver.java +++ b/spring-expression/src/main/java/org/springframework/expression/ConstructorResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,18 +21,19 @@ import java.util.List; import org.springframework.core.convert.TypeDescriptor; /** - * A constructor resolver attempts locate a constructor and returns a ConstructorExecutor that can be used to invoke - * that constructor. The ConstructorExecutor will be cached but if it 'goes stale' the resolvers will be called again. - * + * A constructor resolver attempts locate a constructor and returns a ConstructorExecutor + * that can be used to invoke that constructor. The ConstructorExecutor will be cached but + * if it 'goes stale' the resolvers will be called again. + * * @author Andy Clement * @since 3.0 */ public interface ConstructorResolver { /** - * Within the supplied context determine a suitable constructor on the supplied type that can handle the - * specified arguments. Return a ConstructorExecutor that can be used to invoke that constructor - * (or {@code null} if no constructor could be found). + * Within the supplied context determine a suitable constructor on the supplied type + * that can handle the specified arguments. Return a ConstructorExecutor that can be + * used to invoke that constructor (or {@code null} if no constructor could be found). * @param context the current evaluation context * @param typeName the type upon which to look for the constructor * @param argumentTypes the arguments that the constructor must be able to handle diff --git a/spring-expression/src/main/java/org/springframework/expression/EvaluationContext.java b/spring-expression/src/main/java/org/springframework/expression/EvaluationContext.java index 27a83cab37..a4953fe06d 100644 --- a/spring-expression/src/main/java/org/springframework/expression/EvaluationContext.java +++ b/spring-expression/src/main/java/org/springframework/expression/EvaluationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,12 +19,12 @@ package org.springframework.expression; import java.util.List; /** - * Expressions are executed in an evaluation context. It is in this context that references - * are resolved when encountered during expression evaluation. + * Expressions are executed in an evaluation context. It is in this context that + * references are resolved when encountered during expression evaluation. * *

        There is a default implementation of the EvaluationContext, - * {@link org.springframework.expression.spel.support.StandardEvaluationContext} - * that can be extended, rather than having to implement everything. + * {@link org.springframework.expression.spel.support.StandardEvaluationContext} that can + * be extended, rather than having to implement everything. * * @author Andy Clement * @author Juergen Hoeller @@ -33,8 +33,9 @@ import java.util.List; public interface EvaluationContext { /** - * @return the default root context object against which unqualified properties/methods/etc - * should be resolved. This can be overridden when evaluating an expression. + * @return the default root context object against which unqualified + * properties/methods/etc should be resolved. This can be overridden when + * evaluating an expression. */ TypedValue getRootObject(); @@ -54,7 +55,8 @@ public interface EvaluationContext { List getPropertyAccessors(); /** - * @return a type locator that can be used to find types, either by short or fully qualified name. + * @return a type locator that can be used to find types, either by short or fully + * qualified name. */ TypeLocator getTypeLocator(); diff --git a/spring-expression/src/main/java/org/springframework/expression/Expression.java b/spring-expression/src/main/java/org/springframework/expression/Expression.java index fdad75b17f..b72a353d70 100644 --- a/spring-expression/src/main/java/org/springframework/expression/Expression.java +++ b/spring-expression/src/main/java/org/springframework/expression/Expression.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,10 +19,9 @@ package org.springframework.expression; import org.springframework.core.convert.TypeDescriptor; /** - * An expression capable of evaluating itself against context objects. - * Encapsulates the details of a previously parsed expression string. - * Provides a common abstraction for expression evaluation independent - * of any language like OGNL or the Unified EL. + * An expression capable of evaluating itself against context objects. Encapsulates the + * details of a previously parsed expression string. Provides a common abstraction for + * expression evaluation independent of any language like OGNL or the Unified EL. * * @author Keith Donald * @author Andy Clement diff --git a/spring-expression/src/main/java/org/springframework/expression/ExpressionException.java b/spring-expression/src/main/java/org/springframework/expression/ExpressionException.java index ecea713cad..240c8053be 100644 --- a/spring-expression/src/main/java/org/springframework/expression/ExpressionException.java +++ b/spring-expression/src/main/java/org/springframework/expression/ExpressionException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,6 @@ package org.springframework.expression; - /** * Super class for exceptions that can occur whilst processing expressions * @@ -27,8 +26,10 @@ package org.springframework.expression; public class ExpressionException extends RuntimeException { protected String expressionString; + protected int position; // -1 if not known - but should be known in all reasonable cases + /** * Creates a new expression exception. * @param expressionString the expression string @@ -85,15 +86,16 @@ public class ExpressionException extends RuntimeException { super(message,cause); } + public String toDetailedString() { StringBuilder output = new StringBuilder(); - if (expressionString!=null) { + if (this.expressionString!=null) { output.append("Expression '"); - output.append(expressionString); + output.append(this.expressionString); output.append("'"); - if (position!=-1) { + if (this.position!=-1) { output.append(" @ "); - output.append(position); + output.append(this.position); } output.append(": "); } @@ -106,7 +108,7 @@ public class ExpressionException extends RuntimeException { } public final int getPosition() { - return position; + return this.position; } } diff --git a/spring-expression/src/main/java/org/springframework/expression/ExpressionInvocationTargetException.java b/spring-expression/src/main/java/org/springframework/expression/ExpressionInvocationTargetException.java index a6b11fb26a..30a4e00843 100644 --- a/spring-expression/src/main/java/org/springframework/expression/ExpressionInvocationTargetException.java +++ b/spring-expression/src/main/java/org/springframework/expression/ExpressionInvocationTargetException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,13 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.expression; /** - * This exception wraps (as cause) a checked exception thrown by some method that SpEL invokes. - * It differs from a SpelEvaluationException because this indicates the occurrence of a checked exception - * that the invoked method was defined to throw. SpelEvaluationExceptions are for handling (and wrapping) - * unexpected exceptions. + * This exception wraps (as cause) a checked exception thrown by some method that SpEL + * invokes. It differs from a SpelEvaluationException because this indicates the + * occurrence of a checked exception that the invoked method was defined to throw. + * SpelEvaluationExceptions are for handling (and wrapping) unexpected exceptions. * * @author Andy Clement * @since 3.0.3 diff --git a/spring-expression/src/main/java/org/springframework/expression/MethodExecutor.java b/spring-expression/src/main/java/org/springframework/expression/MethodExecutor.java index bd4dd74516..1506f98630 100644 --- a/spring-expression/src/main/java/org/springframework/expression/MethodExecutor.java +++ b/spring-expression/src/main/java/org/springframework/expression/MethodExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,13 +17,15 @@ package org.springframework.expression; /** - * MethodExecutors are built by the resolvers and can be cached by the infrastructure to repeat an operation quickly - * without going back to the resolvers. For example, the particular method to run on an object may be discovered by the - * reflection method resolver - it will then build a MethodExecutor that executes that method and the MethodExecutor can - * be reused without needing to go back to the resolver to discover the method again. + * MethodExecutors are built by the resolvers and can be cached by the infrastructure to + * repeat an operation quickly without going back to the resolvers. For example, the + * particular method to run on an object may be discovered by the reflection method + * resolver - it will then build a MethodExecutor that executes that method and the + * MethodExecutor can be reused without needing to go back to the resolver to discover the + * method again. * - *

        They can become stale, and in that case should throw an AccessException - this will cause the infrastructure to go - * back to the resolvers to ask for a new one. + *

        They can become stale, and in that case should throw an AccessException - this will + * cause the infrastructure to go back to the resolvers to ask for a new one. * * @author Andy Clement * @since 3.0 @@ -31,13 +33,15 @@ package org.springframework.expression; public interface MethodExecutor { /** - * Execute a command using the specified arguments, and using the specified expression state. + * Execute a command using the specified arguments, and using the specified expression + * state. * @param context the evaluation context in which the command is being executed * @param target the target object of the call - null for static methods - * @param arguments the arguments to the executor, should match (in terms of number and type) whatever the - * command will need to run + * @param arguments the arguments to the executor, should match (in terms of number + * and type) whatever the command will need to run * @return the value returned from execution - * @throws AccessException if there is a problem executing the command or the MethodExecutor is no longer valid + * @throws AccessException if there is a problem executing the command or the + * MethodExecutor is no longer valid */ TypedValue execute(EvaluationContext context, Object target, Object... arguments) throws AccessException; diff --git a/spring-expression/src/main/java/org/springframework/expression/MethodFilter.java b/spring-expression/src/main/java/org/springframework/expression/MethodFilter.java index 50992dd798..d7c6e20cbd 100644 --- a/spring-expression/src/main/java/org/springframework/expression/MethodFilter.java +++ b/spring-expression/src/main/java/org/springframework/expression/MethodFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,18 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.expression; import java.lang.reflect.Method; import java.util.List; /** - * MethodFilter instances allow SpEL users to fine tune the behaviour of the method resolution - * process. Method resolution (which translates from a method name in an expression to a real - * method to invoke) will normally retrieve candidate methods for invocation via a simple call - * to 'Class.getMethods()' and will choose the first one that is suitable for the - * input parameters. By registering a MethodFilter the user can receive a callback - * and change the methods that will be considered suitable. + * MethodFilter instances allow SpEL users to fine tune the behaviour of the method + * resolution process. Method resolution (which translates from a method name in an + * expression to a real method to invoke) will normally retrieve candidate methods for + * invocation via a simple call to 'Class.getMethods()' and will choose the first one that + * is suitable for the input parameters. By registering a MethodFilter the user can + * receive a callback and change the methods that will be considered suitable. * * @author Andy Clement * @since 3.0.1 @@ -32,12 +33,11 @@ import java.util.List; public interface MethodFilter { /** - * Called by the method resolver to allow the SpEL user to organize the list of candidate - * methods that may be invoked. The filter can remove methods that should not be - * considered candidates and it may sort the results. The resolver will then search - * through the methods as returned from the filter when looking for a suitable + * Called by the method resolver to allow the SpEL user to organize the list of + * candidate methods that may be invoked. The filter can remove methods that should + * not be considered candidates and it may sort the results. The resolver will then + * search through the methods as returned from the filter when looking for a suitable * candidate to invoke. - * * @param methods the full list of methods the resolver was going to choose from * @return a possible subset of input methods that may be sorted by order of relevance */ diff --git a/spring-expression/src/main/java/org/springframework/expression/MethodResolver.java b/spring-expression/src/main/java/org/springframework/expression/MethodResolver.java index 75cdc5eab8..d3bdb78c96 100644 --- a/spring-expression/src/main/java/org/springframework/expression/MethodResolver.java +++ b/spring-expression/src/main/java/org/springframework/expression/MethodResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,8 +21,9 @@ import java.util.List; import org.springframework.core.convert.TypeDescriptor; /** - * A method resolver attempts locate a method and returns a command executor that can be used to invoke that method. - * The command executor will be cached but if it 'goes stale' the resolvers will be called again. + * A method resolver attempts locate a method and returns a command executor that can be + * used to invoke that method. The command executor will be cached but if it 'goes stale' + * the resolvers will be called again. * * @author Andy Clement * @since 3.0 @@ -30,13 +31,14 @@ import org.springframework.core.convert.TypeDescriptor; public interface MethodResolver { /** - * Within the supplied context determine a suitable method on the supplied object that can handle the - * specified arguments. Return a MethodExecutor that can be used to invoke that method - * (or {@code null} if no method could be found). + * Within the supplied context determine a suitable method on the supplied object that + * can handle the specified arguments. Return a MethodExecutor that can be used to + * invoke that method (or {@code null} if no method could be found). * @param context the current evaluation context * @param targetObject the object upon which the method is being called * @param argumentTypes the arguments that the constructor must be able to handle - * @return a MethodExecutor that can invoke the method, or null if the method cannot be found + * @return a MethodExecutor that can invoke the method, or null if the method cannot + * be found */ MethodExecutor resolve(EvaluationContext context, Object targetObject, String name, List argumentTypes) throws AccessException; diff --git a/spring-expression/src/main/java/org/springframework/expression/Operation.java b/spring-expression/src/main/java/org/springframework/expression/Operation.java index 01b805905b..f4b9673704 100644 --- a/spring-expression/src/main/java/org/springframework/expression/Operation.java +++ b/spring-expression/src/main/java/org/springframework/expression/Operation.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,13 +17,24 @@ package org.springframework.expression; /** - * Supported operations that an {@link OperatorOverloader} can implement for any pair of operands. + * Supported operations that an {@link OperatorOverloader} can implement for any pair of + * operands. * * @author Andy Clement * @since 3.0 */ public enum Operation { - ADD, SUBTRACT, DIVIDE, MULTIPLY, MODULUS, POWER + ADD, + + SUBTRACT, + + DIVIDE, + + MULTIPLY, + + MODULUS, + + POWER } diff --git a/spring-expression/src/main/java/org/springframework/expression/OperatorOverloader.java b/spring-expression/src/main/java/org/springframework/expression/OperatorOverloader.java index 29c165111b..7291575c9c 100644 --- a/spring-expression/src/main/java/org/springframework/expression/OperatorOverloader.java +++ b/spring-expression/src/main/java/org/springframework/expression/OperatorOverloader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,9 @@ package org.springframework.expression; /** - * By default the mathematical operators {@link Operation} support simple types like numbers. By providing an - * implementation of OperatorOverloader, a user of the expression language can support these operations on other types. + * By default the mathematical operators {@link Operation} support simple types like + * numbers. By providing an implementation of OperatorOverloader, a user of the expression + * language can support these operations on other types. * * @author Andy Clement * @since 3.0 @@ -26,20 +27,21 @@ package org.springframework.expression; public interface OperatorOverloader { /** - * Return true if the operator overloader supports the specified operation - * between the two operands and so should be invoked to handle it. + * Return true if the operator overloader supports the specified operation between the + * two operands and so should be invoked to handle it. * @param operation the operation to be performed * @param leftOperand the left operand * @param rightOperand the right operand - * @return true if the OperatorOverloader supports the specified operation between the two operands + * @return true if the OperatorOverloader supports the specified operation between the + * two operands * @throws EvaluationException if there is a problem performing the operation */ boolean overridesOperation(Operation operation, Object leftOperand, Object rightOperand) throws EvaluationException; /** - * Execute the specified operation on two operands, returning a result. - * See {@link Operation} for supported operations. + * Execute the specified operation on two operands, returning a result. See + * {@link Operation} for supported operations. * @param operation the operation to be performed * @param leftOperand the left operand * @param rightOperand the right operand diff --git a/spring-expression/src/main/java/org/springframework/expression/ParserContext.java b/spring-expression/src/main/java/org/springframework/expression/ParserContext.java index 87a442c6b3..9617feb247 100644 --- a/spring-expression/src/main/java/org/springframework/expression/ParserContext.java +++ b/spring-expression/src/main/java/org/springframework/expression/ParserContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,8 @@ package org.springframework.expression; /** - * Input provided to an expression parser that can influence an expression parsing/compilation routine. + * Input provided to an expression parser that can influence an expression + * parsing/compilation routine. * * @author Keith Donald * @author Andy Clement @@ -26,38 +27,35 @@ package org.springframework.expression; public interface ParserContext { /** - * Whether or not the expression being parsed is a template. A template expression consists of literal text that can - * be mixed with evaluatable blocks. Some examples: - * + * Whether or not the expression being parsed is a template. A template expression + * consists of literal text that can be mixed with evaluatable blocks. Some examples: *

         	 * 	   Some literal text
         	 *     Hello #{name.firstName}!
         	 *     #{3 + 4}
         	 * 
        - * * @return true if the expression is a template, false otherwise */ boolean isTemplate(); /** - * For template expressions, returns the prefix that identifies the start of an expression block within a string. - * For example: "${" - * + * For template expressions, returns the prefix that identifies the start of an + * expression block within a string. For example: "${" * @return the prefix that identifies the start of an expression */ String getExpressionPrefix(); /** - * For template expressions, return the prefix that identifies the end of an expression block within a string. - * For example: "}" - * + * For template expressions, return the prefix that identifies the end of an + * expression block within a string. For example: "}" * @return the suffix that identifies the end of an expression */ String getExpressionSuffix(); + /** - * The default ParserContext implementation that enables template expression parsing mode. - * The expression prefix is #{ and the expression suffix is }. + * The default ParserContext implementation that enables template expression parsing + * mode. The expression prefix is #{ and the expression suffix is }. * @see #isTemplate() */ public static final ParserContext TEMPLATE_EXPRESSION = new ParserContext() { diff --git a/spring-expression/src/main/java/org/springframework/expression/PropertyAccessor.java b/spring-expression/src/main/java/org/springframework/expression/PropertyAccessor.java index e14c30ba68..a8222c25ab 100644 --- a/spring-expression/src/main/java/org/springframework/expression/PropertyAccessor.java +++ b/spring-expression/src/main/java/org/springframework/expression/PropertyAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,13 +18,15 @@ package org.springframework.expression; /** - * A property accessor is able to read (and possibly write) to object properties. The interface places no restrictions - * and so implementors are free to access properties directly as fields or through getters or in any other way they see - * as appropriate. A resolver can optionally specify an array of target classes for which it should be called - but if - * it returns null from getSpecificTargetClasses() then it will be called for all property references and given a chance - * to determine if it can read or write them. Property resolvers are considered to be ordered and each will be called in - * turn. The only rule that affects the call order is that any naming the target class directly in - * getSpecifiedTargetClasses() will be called first, before the general resolvers. + * A property accessor is able to read (and possibly write) to object properties. The + * interface places no restrictions and so implementors are free to access properties + * directly as fields or through getters or in any other way they see as appropriate. A + * resolver can optionally specify an array of target classes for which it should be + * called - but if it returns null from getSpecificTargetClasses() then it will be called + * for all property references and given a chance to determine if it can read or write + * them. Property resolvers are considered to be ordered and each will be called in turn. + * The only rule that affects the call order is that any naming the target class directly + * in getSpecifiedTargetClasses() will be called first, before the general resolvers. * * @author Andy Clement * @since 3.0 @@ -32,19 +34,23 @@ package org.springframework.expression; public interface PropertyAccessor { /** - * Return an array of classes for which this resolver should be called. Returning null indicates this is a general - * resolver that can be called in an attempt to resolve a property on any type. - * @return an array of classes that this resolver is suitable for (or null if a general resolver) + * Return an array of classes for which this resolver should be called. Returning null + * indicates this is a general resolver that can be called in an attempt to resolve a + * property on any type. + * @return an array of classes that this resolver is suitable for (or null if a + * general resolver) */ Class[] getSpecificTargetClasses(); /** - * Called to determine if a resolver instance is able to access a specified property on a specified target object. + * Called to determine if a resolver instance is able to access a specified property + * on a specified target object. * @param context the evaluation context in which the access is being attempted * @param target the target object upon which the property is being accessed * @param name the name of the property being accessed * @return true if this resolver is able to read the property - * @throws AccessException if there is any problem determining whether the property can be read + * @throws AccessException if there is any problem determining whether the property + * can be read */ boolean canRead(EvaluationContext context, Object target, String name) throws AccessException; @@ -59,17 +65,20 @@ public interface PropertyAccessor { TypedValue read(EvaluationContext context, Object target, String name) throws AccessException; /** - * Called to determine if a resolver instance is able to write to a specified property on a specified target object. + * Called to determine if a resolver instance is able to write to a specified property + * on a specified target object. * @param context the evaluation context in which the access is being attempted * @param target the target object upon which the property is being accessed * @param name the name of the property being accessed * @return true if this resolver is able to write to the property - * @throws AccessException if there is any problem determining whether the property can be written to + * @throws AccessException if there is any problem determining whether the property + * can be written to */ boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException; /** - * Called to write to a property on a specified target object. Should only succeed if canWrite() also returns true. + * Called to write to a property on a specified target object. Should only succeed if + * canWrite() also returns true. * @param context the evaluation context in which the access is being attempted * @param target the target object upon which the property is being accessed * @param name the name of the property being accessed diff --git a/spring-expression/src/main/java/org/springframework/expression/TypeComparator.java b/spring-expression/src/main/java/org/springframework/expression/TypeComparator.java index 1dfb41adc7..c939fcab11 100644 --- a/spring-expression/src/main/java/org/springframework/expression/TypeComparator.java +++ b/spring-expression/src/main/java/org/springframework/expression/TypeComparator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,8 @@ package org.springframework.expression; /** - * Instances of a type comparator should be able to compare pairs of objects for equality, the specification of the - * return value is the same as for {@link Comparable}. + * Instances of a type comparator should be able to compare pairs of objects for equality, + * the specification of the return value is the same as for {@link Comparable}. * * @author Andy Clement * @since 3.0 @@ -29,9 +29,10 @@ public interface TypeComparator { * Compare two objects. * @param firstObject the first object * @param secondObject the second object - * @return 0 if they are equal, <0 if the first is smaller than the second, or >0 if the first is larger than the - * second - * @throws EvaluationException if a problem occurs during comparison (or they are not comparable) + * @return 0 if they are equal, <0 if the first is smaller than the second, or >0 if + * the first is larger than the second + * @throws EvaluationException if a problem occurs during comparison (or they are not + * comparable) */ int compare(Object firstObject, Object secondObject) throws EvaluationException; diff --git a/spring-expression/src/main/java/org/springframework/expression/TypeConverter.java b/spring-expression/src/main/java/org/springframework/expression/TypeConverter.java index e6145ab20e..bb7b26b386 100644 --- a/spring-expression/src/main/java/org/springframework/expression/TypeConverter.java +++ b/spring-expression/src/main/java/org/springframework/expression/TypeConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,10 +19,10 @@ package org.springframework.expression; import org.springframework.core.convert.TypeDescriptor; /** - * A type converter can convert values between different types encountered - * during expression evaluation. This is an SPI for the expression parser; - * see {@link org.springframework.core.convert.ConversionService} for the - * primary user API to Spring's conversion facilities. + * A type converter can convert values between different types encountered during + * expression evaluation. This is an SPI for the expression parser; see + * {@link org.springframework.core.convert.ConversionService} for the primary user API to + * Spring's conversion facilities. * * @author Andy Clement * @author Juergen Hoeller @@ -31,7 +31,8 @@ import org.springframework.core.convert.TypeDescriptor; public interface TypeConverter { /** - * Return true if the type converter can convert the specified type to the desired target type. + * Return true if the type converter can convert the specified type to the desired + * target type. * @param sourceType a type descriptor that describes the source type * @param targetType a type descriptor that describes the requested result type * @return true if that conversion can be performed @@ -39,12 +40,15 @@ public interface TypeConverter { boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType); /** - * Convert (may coerce) a value from one type to another, for example from a boolean to a string. - * The typeDescriptor parameter enables support for typed collections - if the caller really wishes they - * can have a List<Integer> for example, rather than simply a List. + * Convert (may coerce) a value from one type to another, for example from a boolean + * to a string. The typeDescriptor parameter enables support for typed collections - + * if the caller really wishes they can have a List<Integer> for example, rather + * than simply a List. * @param value the value to be converted - * @param sourceType a type descriptor that supplies extra information about the source object - * @param targetType a type descriptor that supplies extra information about the requested result type + * @param sourceType a type descriptor that supplies extra information about the + * source object + * @param targetType a type descriptor that supplies extra information about the + * requested result type * @return the converted value * @throws EvaluationException if conversion is not possible */ diff --git a/spring-expression/src/main/java/org/springframework/expression/TypeLocator.java b/spring-expression/src/main/java/org/springframework/expression/TypeLocator.java index 4a22a82c19..7fade5cb7b 100644 --- a/spring-expression/src/main/java/org/springframework/expression/TypeLocator.java +++ b/spring-expression/src/main/java/org/springframework/expression/TypeLocator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,9 +17,11 @@ package org.springframework.expression; /** - * Implementors of this interface are expected to be able to locate types. They may use custom classloaders - * or the and deal with common package prefixes (java.lang, etc) however they wish. See - * {@link org.springframework.expression.spel.support.StandardTypeLocator} for an example implementation. + * Implementors of this interface are expected to be able to locate types. They may use + * custom classloaders or the and deal with common package prefixes (java.lang, etc) + * however they wish. See + * {@link org.springframework.expression.spel.support.StandardTypeLocator} for an example + * implementation. * * @author Andy Clement * @since 3.0 @@ -27,7 +29,8 @@ package org.springframework.expression; public interface TypeLocator { /** - * Find a type by name. The name may or may not be fully qualified (eg. String or java.lang.String) + * Find a type by name. The name may or may not be fully qualified (eg. String or + * java.lang.String) * @param typename the type to be located * @return the class object representing that type * @throws EvaluationException if there is a problem finding it diff --git a/spring-expression/src/main/java/org/springframework/expression/TypedValue.java b/spring-expression/src/main/java/org/springframework/expression/TypedValue.java index 67ecb99ad2..12fb458293 100644 --- a/spring-expression/src/main/java/org/springframework/expression/TypedValue.java +++ b/spring-expression/src/main/java/org/springframework/expression/TypedValue.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,9 +19,9 @@ package org.springframework.expression; import org.springframework.core.convert.TypeDescriptor; /** - * Encapsulates an object and a type descriptor that describes it. - * The type descriptor can hold generic information that would not be - * accessible through a simple {@code getClass()} call on the object. + * Encapsulates an object and a type descriptor that describes it. The type descriptor can + * hold generic information that would not be accessible through a simple + * {@code getClass()} call on the object. * * @author Andy Clement * @author Juergen Hoeller diff --git a/spring-expression/src/main/java/org/springframework/expression/common/CompositeStringExpression.java b/spring-expression/src/main/java/org/springframework/expression/common/CompositeStringExpression.java index a24a545479..1ddd5f9143 100644 --- a/spring-expression/src/main/java/org/springframework/expression/common/CompositeStringExpression.java +++ b/spring-expression/src/main/java/org/springframework/expression/common/CompositeStringExpression.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,18 +20,21 @@ import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationException; import org.springframework.expression.Expression; +import org.springframework.expression.TypedValue; /** - * Represents a template expression broken into pieces. Each piece will be an Expression but pure text parts to the - * template will be represented as LiteralExpression objects. An example of a template expression might be: - * + * Represents a template expression broken into pieces. Each piece will be an Expression + * but pure text parts to the template will be represented as LiteralExpression objects. + * An example of a template expression might be: + * *
        - * "Hello ${getName()}"
        - * - * which will be represented as a CompositeStringExpression of two parts. The first part being a - * LiteralExpression representing 'Hello ' and the second part being a real expression that will - * call {@code getName()} when invoked. - * + * "Hello ${getName()}" + * + * + * which will be represented as a CompositeStringExpression of two parts. The first part + * being a LiteralExpression representing 'Hello ' and the second part being a real + * expression that will call {@code getName()} when invoked. + * * @author Andy Clement * @author Juergen Hoeller * @since 3.0 @@ -131,13 +134,13 @@ public class CompositeStringExpression implements Expression { @Override public T getValue(EvaluationContext context, Class expectedResultType) throws EvaluationException { Object value = getValue(context); - return ExpressionUtils.convert(context, value, expectedResultType); + return ExpressionUtils.convertTypedValue(context, new TypedValue(value), expectedResultType); } @Override public T getValue(Class expectedResultType) throws EvaluationException { Object value = getValue(); - return ExpressionUtils.convert(null, value, expectedResultType); + return ExpressionUtils.convertTypedValue(null, new TypedValue(value), expectedResultType); } @Override @@ -146,21 +149,21 @@ public class CompositeStringExpression implements Expression { } public Expression[] getExpressions() { - return expressions; + return this.expressions; } @Override public T getValue(Object rootObject, Class desiredResultType) throws EvaluationException { Object value = getValue(rootObject); - return ExpressionUtils.convert(null, value, desiredResultType); + return ExpressionUtils.convertTypedValue(null, new TypedValue(value), desiredResultType); } @Override public T getValue(EvaluationContext context, Object rootObject, Class desiredResultType) throws EvaluationException { Object value = getValue(context,rootObject); - return ExpressionUtils.convert(context, value, desiredResultType); + return ExpressionUtils.convertTypedValue(context, new TypedValue(value), desiredResultType); } @Override diff --git a/spring-expression/src/main/java/org/springframework/expression/common/ExpressionUtils.java b/spring-expression/src/main/java/org/springframework/expression/common/ExpressionUtils.java index cc959c799e..696d1eedf9 100644 --- a/spring-expression/src/main/java/org/springframework/expression/common/ExpressionUtils.java +++ b/spring-expression/src/main/java/org/springframework/expression/common/ExpressionUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,29 +33,32 @@ import org.springframework.util.ClassUtils; public abstract class ExpressionUtils { /** - * Determines if there is a type converter available in the specified context and attempts to use it to convert the - * supplied value to the specified type. Throws an exception if conversion is not possible. + * Determines if there is a type converter available in the specified context and + * attempts to use it to convert the supplied value to the specified type. Throws an + * exception if conversion is not possible. * @param context the evaluation context that may define a type converter * @param value the value to convert (may be null) * @param targetType the type to attempt conversion to * @return the converted value - * @throws EvaluationException if there is a problem during conversion or conversion of the value to the specified - * type is not supported + * @throws EvaluationException if there is a problem during conversion or conversion + * of the value to the specified type is not supported + * @deprecated use {@link #convertTypedValue(EvaluationContext, TypedValue, Class)} */ + @Deprecated public static T convert(EvaluationContext context, Object value, Class targetType) throws EvaluationException { - // TODO remove this function over time and use the one it delegates to - return convertTypedValue(context,new TypedValue(value),targetType); + return convertTypedValue(context, new TypedValue(value), targetType); } /** - * Determines if there is a type converter available in the specified context and attempts to use it to convert the - * supplied value to the specified type. Throws an exception if conversion is not possible. + * Determines if there is a type converter available in the specified context and + * attempts to use it to convert the supplied value to the specified type. Throws an + * exception if conversion is not possible. * @param context the evaluation context that may define a type converter * @param typedValue the value to convert and a type descriptor describing it * @param targetType the type to attempt conversion to * @return the converted value - * @throws EvaluationException if there is a problem during conversion or conversion of the value to the specified - * type is not supported + * @throws EvaluationException if there is a problem during conversion or conversion + * of the value to the specified type is not supported */ @SuppressWarnings("unchecked") public static T convertTypedValue(EvaluationContext context, TypedValue typedValue, Class targetType) { diff --git a/spring-expression/src/main/java/org/springframework/expression/common/LiteralExpression.java b/spring-expression/src/main/java/org/springframework/expression/common/LiteralExpression.java index ea6742ae07..c9b1878ff3 100644 --- a/spring-expression/src/main/java/org/springframework/expression/common/LiteralExpression.java +++ b/spring-expression/src/main/java/org/springframework/expression/common/LiteralExpression.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,13 +20,14 @@ import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationException; import org.springframework.expression.Expression; +import org.springframework.expression.TypedValue; /** - * A very simple hardcoded implementation of the Expression interface that represents a string literal. - * It is used with CompositeStringExpression when representing a template expression which is made up - * of pieces - some being real expressions to be handled by an EL implementation like Spel, and some - * being just textual elements. - * + * A very simple hardcoded implementation of the Expression interface that represents a + * string literal. It is used with CompositeStringExpression when representing a template + * expression which is made up of pieces - some being real expressions to be handled by an + * EL implementation like Spel, and some being just textual elements. + * * @author Andy Clement * @since 3.0 */ @@ -78,19 +79,19 @@ public class LiteralExpression implements Expression { @Override public void setValue(EvaluationContext context, Object value) throws EvaluationException { - throw new EvaluationException(literalValue, "Cannot call setValue() on a LiteralExpression"); + throw new EvaluationException(this.literalValue, "Cannot call setValue() on a LiteralExpression"); } @Override public T getValue(EvaluationContext context, Class expectedResultType) throws EvaluationException { Object value = getValue(context); - return ExpressionUtils.convert(context, value, expectedResultType); + return ExpressionUtils.convertTypedValue(context, new TypedValue(value), expectedResultType); } @Override public T getValue(Class expectedResultType) throws EvaluationException { Object value = getValue(); - return ExpressionUtils.convert(null, value, expectedResultType); + return ExpressionUtils.convertTypedValue(null, new TypedValue(value), expectedResultType); } @Override @@ -106,7 +107,7 @@ public class LiteralExpression implements Expression { @Override public T getValue(Object rootObject, Class desiredResultType) throws EvaluationException { Object value = getValue(rootObject); - return ExpressionUtils.convert(null, value, desiredResultType); + return ExpressionUtils.convertTypedValue(null, new TypedValue(value), desiredResultType); } @Override @@ -117,7 +118,7 @@ public class LiteralExpression implements Expression { @Override public T getValue(EvaluationContext context, Object rootObject, Class desiredResultType) throws EvaluationException { Object value = getValue(context, rootObject); - return ExpressionUtils.convert(null, value, desiredResultType); + return ExpressionUtils.convertTypedValue(null, new TypedValue(value), desiredResultType); } @Override @@ -147,7 +148,7 @@ public class LiteralExpression implements Expression { @Override public void setValue(EvaluationContext context, Object rootObject, Object value) throws EvaluationException { - throw new EvaluationException(literalValue, "Cannot call setValue() on a LiteralExpression"); + throw new EvaluationException(this.literalValue, "Cannot call setValue() on a LiteralExpression"); } @Override @@ -157,7 +158,7 @@ public class LiteralExpression implements Expression { @Override public void setValue(Object rootObject, Object value) throws EvaluationException { - throw new EvaluationException(literalValue, "Cannot call setValue() on a LiteralExpression"); + throw new EvaluationException(this.literalValue, "Cannot call setValue() on a LiteralExpression"); } } diff --git a/spring-expression/src/main/java/org/springframework/expression/common/TemplateAwareExpressionParser.java b/spring-expression/src/main/java/org/springframework/expression/common/TemplateAwareExpressionParser.java index 82b6ab07bb..c74371e998 100644 --- a/spring-expression/src/main/java/org/springframework/expression/common/TemplateAwareExpressionParser.java +++ b/spring-expression/src/main/java/org/springframework/expression/common/TemplateAwareExpressionParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,9 +26,9 @@ import org.springframework.expression.ParseException; import org.springframework.expression.ParserContext; /** - * An expression parser that understands templates. It can be subclassed - * by expression parsers that do not offer first class support for templating. - * + * An expression parser that understands templates. It can be subclassed by expression + * parsers that do not offer first class support for templating. + * * @author Keith Donald * @author Juergen Hoeller * @author Andy Clement @@ -40,102 +40,124 @@ public abstract class TemplateAwareExpressionParser implements ExpressionParser * Default ParserContext instance for non-template expressions. */ private static final ParserContext NON_TEMPLATE_PARSER_CONTEXT = new ParserContext() { + @Override public String getExpressionPrefix() { return null; } + @Override public String getExpressionSuffix() { return null; } + @Override public boolean isTemplate() { return false; } }; - @Override public Expression parseExpression(String expressionString) throws ParseException { return parseExpression(expressionString, NON_TEMPLATE_PARSER_CONTEXT); } @Override - public Expression parseExpression(String expressionString, ParserContext context) throws ParseException { + public Expression parseExpression(String expressionString, ParserContext context) + throws ParseException { if (context == null) { context = NON_TEMPLATE_PARSER_CONTEXT; } + if (context.isTemplate()) { return parseTemplate(expressionString, context); - } else { + } + else { return doParseExpression(expressionString, context); } } - private Expression parseTemplate(String expressionString, ParserContext context) throws ParseException { + private Expression parseTemplate(String expressionString, ParserContext context) + throws ParseException { if (expressionString.length() == 0) { return new LiteralExpression(""); } Expression[] expressions = parseExpressions(expressionString, context); if (expressions.length == 1) { return expressions[0]; - } else { + } + else { return new CompositeStringExpression(expressionString, expressions); } } - /** - * Helper that parses given expression string using the configured parser. The expression string can contain any - * number of expressions all contained in "${...}" markers. For instance: "foo${expr0}bar${expr1}". The static - * pieces of text will also be returned as Expressions that just return that static piece of text. As a result, - * evaluating all returned expressions and concatenating the results produces the complete evaluated string. - * Unwrapping is only done of the outermost delimiters found, so the string 'hello ${foo${abc}}' would break into - * the pieces 'hello ' and 'foo${abc}'. This means that expression languages that used ${..} as part of their - * functionality are supported without any problem. - * The parsing is aware of the structure of an embedded expression. It assumes that parentheses '(', - * square brackets '[' and curly brackets '}' must be in pairs within the expression unless they are within a - * string literal and a string literal starts and terminates with a single quote '. - * + * Helper that parses given expression string using the configured parser. The + * expression string can contain any number of expressions all contained in "${...}" + * markers. For instance: "foo${expr0}bar${expr1}". The static pieces of text will + * also be returned as Expressions that just return that static piece of text. As a + * result, evaluating all returned expressions and concatenating the results produces + * the complete evaluated string. Unwrapping is only done of the outermost delimiters + * found, so the string 'hello ${foo${abc}}' would break into the pieces 'hello ' and + * 'foo${abc}'. This means that expression languages that used ${..} as part of their + * functionality are supported without any problem. The parsing is aware of the + * structure of an embedded expression. It assumes that parentheses '(', square + * brackets '[' and curly brackets '}' must be in pairs within the expression unless + * they are within a string literal and a string literal starts and terminates with a + * single quote '. * @param expressionString the expression string * @return the parsed expressions * @throws ParseException when the expressions cannot be parsed */ - private Expression[] parseExpressions(String expressionString, ParserContext context) throws ParseException { + private Expression[] parseExpressions(String expressionString, ParserContext context) + throws ParseException { List expressions = new LinkedList(); String prefix = context.getExpressionPrefix(); String suffix = context.getExpressionSuffix(); int startIdx = 0; while (startIdx < expressionString.length()) { - int prefixIndex = expressionString.indexOf(prefix,startIdx); + int prefixIndex = expressionString.indexOf(prefix, startIdx); if (prefixIndex >= startIdx) { // an inner expression was found - this is a composite if (prefixIndex > startIdx) { - expressions.add(createLiteralExpression(context,expressionString.substring(startIdx, prefixIndex))); + expressions.add(createLiteralExpression(context, + expressionString.substring(startIdx, prefixIndex))); } int afterPrefixIndex = prefixIndex + prefix.length(); - int suffixIndex = skipToCorrectEndSuffix(prefix,suffix,expressionString,afterPrefixIndex); + int suffixIndex = skipToCorrectEndSuffix(prefix, suffix, + expressionString, afterPrefixIndex); + if (suffixIndex == -1) { - throw new ParseException(expressionString, prefixIndex, "No ending suffix '" + suffix + - "' for expression starting at character " + prefixIndex + ": " + - expressionString.substring(prefixIndex)); + throw new ParseException(expressionString, prefixIndex, + "No ending suffix '" + suffix + + "' for expression starting at character " + + prefixIndex + ": " + + expressionString.substring(prefixIndex)); } + if (suffixIndex == afterPrefixIndex) { - throw new ParseException(expressionString, prefixIndex, "No expression defined within delimiter '" + - prefix + suffix + "' at character " + prefixIndex); - } else { - String expr = expressionString.substring(prefixIndex + prefix.length(), suffixIndex); - expr = expr.trim(); - if (expr.length()==0) { - throw new ParseException(expressionString, prefixIndex, "No expression defined within delimiter '" + - prefix + suffix + "' at character " + prefixIndex); - } - expressions.add(doParseExpression(expr, context)); - startIdx = suffixIndex + suffix.length(); + throw new ParseException(expressionString, prefixIndex, + "No expression defined within delimiter '" + prefix + suffix + + "' at character " + prefixIndex); } - } else { + + String expr = expressionString.substring(prefixIndex + prefix.length(), + suffixIndex); + expr = expr.trim(); + + if (expr.length() == 0) { + throw new ParseException(expressionString, prefixIndex, + "No expression defined within delimiter '" + prefix + suffix + + "' at character " + prefixIndex); + } + + expressions.add(doParseExpression(expr, context)); + startIdx = suffixIndex + suffix.length(); + } + else { // no more ${expressions} found in string, add rest as static text - expressions.add(createLiteralExpression(context,expressionString.substring(startIdx))); + expressions.add(createLiteralExpression(context, + expressionString.substring(startIdx))); startIdx = expressionString.length(); } } @@ -147,19 +169,20 @@ public abstract class TemplateAwareExpressionParser implements ExpressionParser } /** - * Return true if the specified suffix can be found at the supplied position in the supplied expression string. + * Return true if the specified suffix can be found at the supplied position in the + * supplied expression string. * @param expressionString the expression string which may contain the suffix * @param pos the start position at which to check for the suffix * @param suffix the suffix string */ - private boolean isSuffixHere(String expressionString,int pos,String suffix) { + private boolean isSuffixHere(String expressionString, int pos, String suffix) { int suffixPosition = 0; - for (int i=0;i stack = new Stack(); - while (posIt also acts as a place for to define common utility routines that the various Ast nodes might need. + *

        It also acts as a place for to define common utility routines that the various AST + * nodes might need. * * @author Andy Clement * @since 3.0 @@ -138,7 +141,8 @@ public class ExpressionState { } public Object convertValue(Object value, TypeDescriptor targetTypeDescriptor) throws EvaluationException { - return this.relatedContext.getTypeConverter().convertValue(value, TypeDescriptor.forObject(value), targetTypeDescriptor); + return this.relatedContext.getTypeConverter().convertValue(value, + TypeDescriptor.forObject(value), targetTypeDescriptor); } public TypeConverter getTypeConverter() { @@ -147,7 +151,8 @@ public class ExpressionState { public Object convertValue(TypedValue value, TypeDescriptor targetTypeDescriptor) throws EvaluationException { Object val = value.getValue(); - return this.relatedContext.getTypeConverter().convertValue(val, TypeDescriptor.forObject(val), targetTypeDescriptor); + return this.relatedContext.getTypeConverter().convertValue(val, + TypeDescriptor.forObject(val), targetTypeDescriptor); } /* @@ -210,6 +215,7 @@ public class ExpressionState { return this.configuration; } + /** * A new scope is entered when a function is called and it is used to hold the parameters to the function call. If the names * of the parameters clash with those in a higher level scope, those in the higher level scope will not be accessible whilst @@ -221,6 +227,7 @@ public class ExpressionState { public VariableScope() { } + public VariableScope(Map arguments) { if (arguments != null) { this.vars.putAll(arguments); @@ -231,6 +238,7 @@ public class ExpressionState { this.vars.put(name,value); } + public Object lookupVariable(String name) { return this.vars.get(name); } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/SpelEvaluationException.java b/spring-expression/src/main/java/org/springframework/expression/spel/SpelEvaluationException.java index 7c7a3f6f1c..80a604f690 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/SpelEvaluationException.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/SpelEvaluationException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,9 +18,9 @@ package org.springframework.expression.spel; import org.springframework.expression.EvaluationException; /** - * Root exception for Spring EL related exceptions. Rather than holding a hard coded string indicating the problem, it - * records a message key and the inserts for the message. See {@link SpelMessage} for the list of all possible messages - * that can occur. + * Root exception for Spring EL related exceptions. Rather than holding a hard coded + * string indicating the problem, it records a message key and the inserts for the + * message. See {@link SpelMessage} for the list of all possible messages that can occur. * * @author Andy Clement * @since 3.0 @@ -28,8 +28,10 @@ import org.springframework.expression.EvaluationException; @SuppressWarnings("serial") public class SpelEvaluationException extends EvaluationException { - private SpelMessage message; - private Object[] inserts; + private final SpelMessage message; + + private final Object[] inserts; + public SpelEvaluationException(SpelMessage message, Object... inserts) { super(message.formatMessage(0, inserts)); // TODO poor position information, can the callers not really supply something? @@ -56,15 +58,18 @@ public class SpelEvaluationException extends EvaluationException { this.inserts = inserts; } + /** * @return a formatted message with inserts applied */ @Override public String getMessage() { - if (message != null) - return message.formatMessage(position, inserts); - else + if (this.message != null) { + return this.message.formatMessage(this.position, this.inserts); + } + else { return super.getMessage(); + } } /** @@ -87,7 +92,7 @@ public class SpelEvaluationException extends EvaluationException { * @return the message inserts */ public Object[] getInserts() { - return inserts; + return this.inserts; } } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java b/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java index c6d95a9413..b61125af33 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java @@ -19,98 +19,249 @@ package org.springframework.expression.spel; import java.text.MessageFormat; /** - * Contains all the messages that can be produced by the Spring Expression Language. Each message has a kind (info, - * warn, error) and a code number. Tests can be written to expect particular code numbers rather than particular text, - * enabling the message text to more easily be modified and the tests to run successfully in different locales. - *

        - * When a message is formatted, it will have this kind of form + * Contains all the messages that can be produced by the Spring Expression Language. Each + * message has a kind (info, warn, error) and a code number. Tests can be written to + * expect particular code numbers rather than particular text, enabling the message text + * to more easily be modified and the tests to run successfully in different locales. + * + *

        When a message is formatted, it will have this kind of form * *

          * EL1004E: (pos 34): Type cannot be found 'String'
          * 
        * - * The prefix captures the code and the error kind, whilst the position is included if it is known. + * The prefix captures the code and the error kind, whilst the position is + * included if it is known. * * @author Andy Clement * @since 3.0 */ public enum SpelMessage { - TYPE_CONVERSION_ERROR(Kind.ERROR, 1001, "Type conversion problem, cannot convert from {0} to {1}"), // - CONSTRUCTOR_NOT_FOUND(Kind.ERROR, 1002, "Constructor call: No suitable constructor found on type {0} for arguments {1}"), // - CONSTRUCTOR_INVOCATION_PROBLEM(Kind.ERROR, 1003, "A problem occurred whilst attempting to construct an object of type ''{0}'' using arguments ''{1}''"), // - METHOD_NOT_FOUND(Kind.ERROR, 1004, "Method call: Method {0} cannot be found on {1} type"), // - TYPE_NOT_FOUND(Kind.ERROR, 1005, "Type cannot be found ''{0}''"), // - FUNCTION_NOT_DEFINED(Kind.ERROR, 1006, "The function ''{0}'' could not be found"), // - PROPERTY_OR_FIELD_NOT_READABLE_ON_NULL(Kind.ERROR, 1007, "Field or property ''{0}'' cannot be found on null"), // - PROPERTY_OR_FIELD_NOT_READABLE(Kind.ERROR, 1008, "Field or property ''{0}'' cannot be found on object of type ''{1}''"), // - PROPERTY_OR_FIELD_NOT_WRITABLE_ON_NULL(Kind.ERROR, 1009, "Field or property ''{0}'' cannot be set on null"), // - PROPERTY_OR_FIELD_NOT_WRITABLE(Kind.ERROR, 1010, "Field or property ''{0}'' cannot be set on object of type ''{1}''"), // - METHOD_CALL_ON_NULL_OBJECT_NOT_ALLOWED(Kind.ERROR, 1011, "Method call: Attempted to call method {0} on null context object"), // - CANNOT_INDEX_INTO_NULL_VALUE(Kind.ERROR, 1012, "Cannot index into a null value"), - NOT_COMPARABLE(Kind.ERROR, 1013, "Cannot compare instances of {0} and {1}"), // - INCORRECT_NUMBER_OF_ARGUMENTS_TO_FUNCTION(Kind.ERROR, 1014, "Incorrect number of arguments for function, {0} supplied but function takes {1}"), // - INVALID_TYPE_FOR_SELECTION(Kind.ERROR, 1015, "Cannot perform selection on input data of type ''{0}''"), // - RESULT_OF_SELECTION_CRITERIA_IS_NOT_BOOLEAN(Kind.ERROR, 1016, "Result of selection criteria is not boolean"), // - BETWEEN_RIGHT_OPERAND_MUST_BE_TWO_ELEMENT_LIST(Kind.ERROR, 1017, "Right operand for the 'between' operator has to be a two-element list"), // - INVALID_PATTERN(Kind.ERROR, 1018, "Pattern is not valid ''{0}''"), // - PROJECTION_NOT_SUPPORTED_ON_TYPE(Kind.ERROR, 1019, "Projection is not supported on the type ''{0}''"), // - ARGLIST_SHOULD_NOT_BE_EVALUATED(Kind.ERROR, 1020, "The argument list of a lambda expression should never have getValue() called upon it"), // - EXCEPTION_DURING_PROPERTY_READ(Kind.ERROR, 1021, "A problem occurred whilst attempting to access the property ''{0}'': ''{1}''"), // - FUNCTION_REFERENCE_CANNOT_BE_INVOKED(Kind.ERROR, 1022, "The function ''{0}'' mapped to an object of type ''{1}'' which cannot be invoked"), // - EXCEPTION_DURING_FUNCTION_CALL(Kind.ERROR, 1023, "A problem occurred whilst attempting to invoke the function ''{0}'': ''{1}''"), // - ARRAY_INDEX_OUT_OF_BOUNDS(Kind.ERROR, 1024, "The array has ''{0}'' elements, index ''{1}'' is invalid"), // - COLLECTION_INDEX_OUT_OF_BOUNDS(Kind.ERROR, 1025, "The collection has ''{0}'' elements, index ''{1}'' is invalid"), // - STRING_INDEX_OUT_OF_BOUNDS(Kind.ERROR, 1026, "The string has ''{0}'' characters, index ''{1}'' is invalid"), // - INDEXING_NOT_SUPPORTED_FOR_TYPE(Kind.ERROR, 1027, "Indexing into type ''{0}'' is not supported"), // - INSTANCEOF_OPERATOR_NEEDS_CLASS_OPERAND(Kind.ERROR, 1028, "The operator 'instanceof' needs the right operand to be a class, not a ''{0}''"), // - EXCEPTION_DURING_METHOD_INVOCATION(Kind.ERROR, 1029, "A problem occurred when trying to execute method ''{0}'' on object of type ''{1}'': ''{2}''"), // - OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES(Kind.ERROR, 1030, "The operator ''{0}'' is not supported between objects of type ''{1}'' and ''{2}''"), // - PROBLEM_LOCATING_METHOD(Kind.ERROR, 1031, "Problem locating method {0} cannot on type {1}"), - SETVALUE_NOT_SUPPORTED( Kind.ERROR, 1032, "setValue(ExpressionState, Object) not supported for ''{0}''"), // - MULTIPLE_POSSIBLE_METHODS(Kind.ERROR, 1033, "Method call of ''{0}'' is ambiguous, supported type conversions allow multiple variants to match"), // - EXCEPTION_DURING_PROPERTY_WRITE(Kind.ERROR, 1034, "A problem occurred whilst attempting to set the property ''{0}'': {1}"), // - NOT_AN_INTEGER(Kind.ERROR, 1035, "The value ''{0}'' cannot be parsed as an int"), // - NOT_A_LONG(Kind.ERROR, 1036, "The value ''{0}'' cannot be parsed as a long"), // - INVALID_FIRST_OPERAND_FOR_MATCHES_OPERATOR(Kind.ERROR, 1037, "First operand to matches operator must be a string. ''{0}'' is not"), // - INVALID_SECOND_OPERAND_FOR_MATCHES_OPERATOR(Kind.ERROR, 1038, "Second operand to matches operator must be a string. ''{0}'' is not"), // - FUNCTION_MUST_BE_STATIC(Kind.ERROR, 1039, "Only static methods can be called via function references. The method ''{0}'' referred to by name ''{1}'' is not static."),// - NOT_A_REAL(Kind.ERROR, 1040, "The value ''{0}'' cannot be parsed as a double"), // - MORE_INPUT(Kind.ERROR,1041, "After parsing a valid expression, there is still more data in the expression: ''{0}''"), - RIGHT_OPERAND_PROBLEM(Kind.ERROR,1042, "Problem parsing right operand"), - NOT_EXPECTED_TOKEN(Kind.ERROR,1043,"Unexpected token. Expected ''{0}'' but was ''{1}''"), - OOD(Kind.ERROR,1044,"Unexpectedly ran out of input"), // - NON_TERMINATING_DOUBLE_QUOTED_STRING(Kind.ERROR,1045,"Cannot find terminating \" for string"),// - NON_TERMINATING_QUOTED_STRING(Kind.ERROR,1046,"Cannot find terminating ' for string"), // - MISSING_LEADING_ZERO_FOR_NUMBER(Kind.ERROR,1047,"A real number must be prefixed by zero, it cannot start with just ''.''"), // - REAL_CANNOT_BE_LONG(Kind.ERROR,1048,"Real number cannot be suffixed with a long (L or l) suffix"),// - UNEXPECTED_DATA_AFTER_DOT(Kind.ERROR,1049,"Unexpected data after ''.'': ''{0}''"),// - MISSING_CONSTRUCTOR_ARGS(Kind.ERROR,1050,"The arguments '(...)' for the constructor call are missing"),// - RUN_OUT_OF_ARGUMENTS(Kind.ERROR,1051,"Unexpected ran out of arguments"),// - UNABLE_TO_GROW_COLLECTION(Kind.ERROR,1052,"Unable to grow collection"),// - UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE(Kind.ERROR,1053,"Unable to grow collection: unable to determine list element type"),// - UNABLE_TO_CREATE_LIST_FOR_INDEXING(Kind.ERROR,1054,"Unable to dynamically create a List to replace a null value"),// - UNABLE_TO_CREATE_MAP_FOR_INDEXING(Kind.ERROR,1055,"Unable to dynamically create a Map to replace a null value"),// - UNABLE_TO_DYNAMICALLY_CREATE_OBJECT(Kind.ERROR,1056,"Unable to dynamically create instance of ''{0}'' to replace a null value"),// - NO_BEAN_RESOLVER_REGISTERED(Kind.ERROR,1057,"No bean resolver registered in the context to resolve access to bean ''{0}''"),// - EXCEPTION_DURING_BEAN_RESOLUTION(Kind.ERROR, 1058, "A problem occurred when trying to resolve bean ''{0}'':''{1}''"), // - INVALID_BEAN_REFERENCE(Kind.ERROR,1059,"@ can only be followed by an identifier or a quoted name"),// + TYPE_CONVERSION_ERROR(Kind.ERROR, 1001, + "Type conversion problem, cannot convert from {0} to {1}"), + + CONSTRUCTOR_NOT_FOUND(Kind.ERROR, 1002, + "Constructor call: No suitable constructor found on type {0} for " + + "arguments {1}"), + + CONSTRUCTOR_INVOCATION_PROBLEM(Kind.ERROR, 1003, + "A problem occurred whilst attempting to construct an object of type " + + "''{0}'' using arguments ''{1}''"), + + METHOD_NOT_FOUND(Kind.ERROR, 1004, + "Method call: Method {0} cannot be found on {1} type"), + + TYPE_NOT_FOUND(Kind.ERROR, 1005, + "Type cannot be found ''{0}''"), + + FUNCTION_NOT_DEFINED(Kind.ERROR, 1006, + "The function ''{0}'' could not be found"), + + PROPERTY_OR_FIELD_NOT_READABLE_ON_NULL(Kind.ERROR, 1007, + "Field or property ''{0}'' cannot be found on null"), + + PROPERTY_OR_FIELD_NOT_READABLE(Kind.ERROR, 1008, + "Field or property ''{0}'' cannot be found on object of type ''{1}''"), + + PROPERTY_OR_FIELD_NOT_WRITABLE_ON_NULL(Kind.ERROR, 1009, + "Field or property ''{0}'' cannot be set on null"), + + PROPERTY_OR_FIELD_NOT_WRITABLE(Kind.ERROR, 1010, + "Field or property ''{0}'' cannot be set on object of type ''{1}''"), + + METHOD_CALL_ON_NULL_OBJECT_NOT_ALLOWED(Kind.ERROR, 1011, + "Method call: Attempted to call method {0} on null context object"), + + CANNOT_INDEX_INTO_NULL_VALUE(Kind.ERROR, 1012, + "Cannot index into a null value"), + + NOT_COMPARABLE(Kind.ERROR, 1013, + "Cannot compare instances of {0} and {1}"), + + INCORRECT_NUMBER_OF_ARGUMENTS_TO_FUNCTION(Kind.ERROR, 1014, + "Incorrect number of arguments for function, {0} supplied but " + + "function takes {1}"), + + INVALID_TYPE_FOR_SELECTION(Kind.ERROR, 1015, + "Cannot perform selection on input data of type ''{0}''"), + + RESULT_OF_SELECTION_CRITERIA_IS_NOT_BOOLEAN(Kind.ERROR, 1016, + "Result of selection criteria is not boolean"), + + BETWEEN_RIGHT_OPERAND_MUST_BE_TWO_ELEMENT_LIST(Kind.ERROR, 1017, + "Right operand for the 'between' operator has to be a two-element list"), + + INVALID_PATTERN(Kind.ERROR, 1018, + "Pattern is not valid ''{0}''"), + + PROJECTION_NOT_SUPPORTED_ON_TYPE(Kind.ERROR, 1019, + "Projection is not supported on the type ''{0}''"), + + ARGLIST_SHOULD_NOT_BE_EVALUATED(Kind.ERROR, 1020, + "The argument list of a lambda expression should never have getValue() " + + "called upon it"), + + EXCEPTION_DURING_PROPERTY_READ(Kind.ERROR, 1021, + "A problem occurred whilst attempting to access the property " + + "''{0}'': ''{1}''"), + + FUNCTION_REFERENCE_CANNOT_BE_INVOKED(Kind.ERROR, 1022, + "The function ''{0}'' mapped to an object of type ''{1}'' which " + + "cannot be invoked"), + + EXCEPTION_DURING_FUNCTION_CALL(Kind.ERROR, 1023, + "A problem occurred whilst attempting to invoke the " + + "function ''{0}'': ''{1}''"), + + ARRAY_INDEX_OUT_OF_BOUNDS(Kind.ERROR, 1024, + "The array has ''{0}'' elements, index ''{1}'' is invalid"), + + COLLECTION_INDEX_OUT_OF_BOUNDS(Kind.ERROR, 1025, + "The collection has ''{0}'' elements, index ''{1}'' is invalid"), + + STRING_INDEX_OUT_OF_BOUNDS(Kind.ERROR, 1026, + "The string has ''{0}'' characters, index ''{1}'' is invalid"), + + INDEXING_NOT_SUPPORTED_FOR_TYPE(Kind.ERROR, 1027, + "Indexing into type ''{0}'' is not supported"), + + INSTANCEOF_OPERATOR_NEEDS_CLASS_OPERAND(Kind.ERROR, 1028, + "The operator 'instanceof' needs the right operand to be a class, " + + "not a ''{0}''"), + + EXCEPTION_DURING_METHOD_INVOCATION(Kind.ERROR, 1029, + "A problem occurred when trying to execute method ''{0}'' on object " + + "of type ''{1}'': ''{2}''"), + + OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES(Kind.ERROR, 1030, + "The operator ''{0}'' is not supported between objects of type " + + "''{1}'' and ''{2}''"), + + PROBLEM_LOCATING_METHOD(Kind.ERROR, 1031, + "Problem locating method {0} cannot on type {1}"), + + SETVALUE_NOT_SUPPORTED( Kind.ERROR, 1032, + "setValue(ExpressionState, Object) not supported for ''{0}''"), + + MULTIPLE_POSSIBLE_METHODS(Kind.ERROR, 1033, + "Method call of ''{0}'' is ambiguous, supported type conversions " + + "allow multiple variants to match"), + + EXCEPTION_DURING_PROPERTY_WRITE(Kind.ERROR, 1034, + "A problem occurred whilst attempting to set the property ''{0}'': {1}"), + + NOT_AN_INTEGER(Kind.ERROR, 1035, + "The value ''{0}'' cannot be parsed as an int"), + + NOT_A_LONG(Kind.ERROR, 1036, + "The value ''{0}'' cannot be parsed as a long"), + + INVALID_FIRST_OPERAND_FOR_MATCHES_OPERATOR(Kind.ERROR, 1037, + "First operand to matches operator must be a string. ''{0}'' is not"), + + INVALID_SECOND_OPERAND_FOR_MATCHES_OPERATOR(Kind.ERROR, 1038, + "Second operand to matches operator must be a string. ''{0}'' is not"), + + FUNCTION_MUST_BE_STATIC(Kind.ERROR, 1039, + "Only static methods can be called via function references. " + + "The method ''{0}'' referred to by name ''{1}'' is not static."), + + NOT_A_REAL(Kind.ERROR, 1040, + "The value ''{0}'' cannot be parsed as a double"), + + MORE_INPUT(Kind.ERROR,1041, + "After parsing a valid expression, there is still more data in " + + "the expression: ''{0}''"), + + RIGHT_OPERAND_PROBLEM(Kind.ERROR, 1042, + "Problem parsing right operand"), + + NOT_EXPECTED_TOKEN(Kind.ERROR, 1043, + "Unexpected token. Expected ''{0}'' but was ''{1}''"), + + OOD(Kind.ERROR, 1044, + "Unexpectedly ran out of input"), + + NON_TERMINATING_DOUBLE_QUOTED_STRING(Kind.ERROR, 1045, + "Cannot find terminating \" for string"), + + NON_TERMINATING_QUOTED_STRING(Kind.ERROR, 1046, + "Cannot find terminating ' for string"), + + MISSING_LEADING_ZERO_FOR_NUMBER(Kind.ERROR, 1047, + "A real number must be prefixed by zero, it cannot start with just ''.''"), + + REAL_CANNOT_BE_LONG(Kind.ERROR, 1048, + "Real number cannot be suffixed with a long (L or l) suffix"), + + UNEXPECTED_DATA_AFTER_DOT(Kind.ERROR, 1049, + "Unexpected data after ''.'': ''{0}''"), + + MISSING_CONSTRUCTOR_ARGS(Kind.ERROR, 1050, + "The arguments '(...)' for the constructor call are missing"), + + RUN_OUT_OF_ARGUMENTS(Kind.ERROR, 1051, + "Unexpected ran out of arguments"), + + UNABLE_TO_GROW_COLLECTION(Kind.ERROR, 1052, + "Unable to grow collection"), + + UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE(Kind.ERROR, 1053, + "Unable to grow collection: unable to determine list element type"), + + UNABLE_TO_CREATE_LIST_FOR_INDEXING(Kind.ERROR, 1054, + "Unable to dynamically create a List to replace a null value"), + + UNABLE_TO_CREATE_MAP_FOR_INDEXING(Kind.ERROR, 1055, + "Unable to dynamically create a Map to replace a null value"), + + UNABLE_TO_DYNAMICALLY_CREATE_OBJECT(Kind.ERROR, 1056, + "Unable to dynamically create instance of ''{0}'' to replace a null value"), + + NO_BEAN_RESOLVER_REGISTERED(Kind.ERROR, 1057, + "No bean resolver registered in the context to resolve access to bean ''{0}''"), + + EXCEPTION_DURING_BEAN_RESOLUTION(Kind.ERROR, 1058, + "A problem occurred when trying to resolve bean ''{0}'':''{1}''"), + + INVALID_BEAN_REFERENCE(Kind.ERROR, 1059, + "@ can only be followed by an identifier or a quoted name"), + TYPE_NAME_EXPECTED_FOR_ARRAY_CONSTRUCTION(Kind.ERROR, 1060, - "Expected the type of the new array to be specified as a String but found ''{0}''"), // + "Expected the type of the new array to be specified as a String but found ''{0}''"), + INCORRECT_ELEMENT_TYPE_FOR_ARRAY(Kind.ERROR, 1061, - "The array of type ''{0}'' cannot have an element of type ''{1}'' inserted"), // + "The array of type ''{0}'' cannot have an element of type ''{1}'' inserted"), + MULTIDIM_ARRAY_INITIALIZER_NOT_SUPPORTED(Kind.ERROR, 1062, - "Using an initializer to build a multi-dimensional array is not currently supported"), // - MISSING_ARRAY_DIMENSION(Kind.ERROR, 1063, "A required array dimension has not been specified"), // - INITIALIZER_LENGTH_INCORRECT( - Kind.ERROR, 1064, "array initializer size does not match array dimensions"), // - UNEXPECTED_ESCAPE_CHAR(Kind.ERROR,1065,"unexpected escape character."), // - OPERAND_NOT_INCREMENTABLE(Kind.ERROR,1066,"the expression component ''{0}'' does not support increment"), // - OPERAND_NOT_DECREMENTABLE(Kind.ERROR,1067,"the expression component ''{0}'' does not support decrement"), // - NOT_ASSIGNABLE(Kind.ERROR,1068,"the expression component ''{0}'' is not assignable"), // - MISSING_CHARACTER(Kind.ERROR,1069,"missing expected character ''{0}''"), - LEFT_OPERAND_PROBLEM(Kind.ERROR,1070, "Problem parsing left operand"), - MISSING_SELECTION_EXPRESSION(Kind.ERROR, 1071, "A required selection expression has not been specified"); + "Using an initializer to build a multi-dimensional array is not currently supported"), + + MISSING_ARRAY_DIMENSION(Kind.ERROR, 1063, + "A required array dimension has not been specified"), + + INITIALIZER_LENGTH_INCORRECT(Kind.ERROR, 1064, + "array initializer size does not match array dimensions"), + + UNEXPECTED_ESCAPE_CHAR(Kind.ERROR, 1065, "unexpected escape character."), + + OPERAND_NOT_INCREMENTABLE(Kind.ERROR, 1066, + "the expression component ''{0}'' does not support increment"), + + OPERAND_NOT_DECREMENTABLE(Kind.ERROR, 1067, + "the expression component ''{0}'' does not support decrement"), + + NOT_ASSIGNABLE(Kind.ERROR, 1068, + "the expression component ''{0}'' is not assignable"), + + MISSING_CHARACTER(Kind.ERROR, 1069, + "missing expected character ''{0}''"), + + LEFT_OPERAND_PROBLEM(Kind.ERROR, 1070, + "Problem parsing left operand"), + + MISSING_SELECTION_EXPRESSION(Kind.ERROR, 1071, + "A required selection expression has not been specified"); private Kind kind; private int code; @@ -134,23 +285,17 @@ public enum SpelMessage { */ public String formatMessage(int pos, Object... inserts) { StringBuilder formattedMessage = new StringBuilder(); - formattedMessage.append("EL").append(code); - switch (kind) { -// case WARNING: -// formattedMessage.append("W"); -// break; -// case INFO: -// formattedMessage.append("I"); -// break; - case ERROR: - formattedMessage.append("E"); - break; + formattedMessage.append("EL").append(this.code); + switch (this.kind) { + case ERROR: + formattedMessage.append("E"); + break; } formattedMessage.append(":"); if (pos != -1) { formattedMessage.append("(pos ").append(pos).append("): "); } - formattedMessage.append(MessageFormat.format(message, inserts)); + formattedMessage.append(MessageFormat.format(this.message, inserts)); return formattedMessage.toString(); } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/SpelNode.java b/spring-expression/src/main/java/org/springframework/expression/spel/SpelNode.java index 6e2ba93f3f..0699445702 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/SpelNode.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/SpelNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,14 +28,16 @@ import org.springframework.expression.TypedValue; public interface SpelNode { /** - * Evaluate the expression node in the context of the supplied expression state and return the value. + * Evaluate the expression node in the context of the supplied expression state and + * return the value. * @param expressionState the current expression state (includes the context) * @return the value of this node evaluated against the specified state */ Object getValue(ExpressionState expressionState) throws EvaluationException; /** - * Evaluate the expression node in the context of the supplied expression state and return the typed value. + * Evaluate the expression node in the context of the supplied expression state and + * return the typed value. * @param expressionState the current expression state (includes the context) * @return the type value of this node evaluated against the specified state */ @@ -51,11 +53,13 @@ public interface SpelNode { boolean isWritable(ExpressionState expressionState) throws EvaluationException; /** - * Evaluate the expression to a node and then set the new value on that node. For example, if the expression - * evaluates to a property reference then the property will be set to the new value. + * Evaluate the expression to a node and then set the new value on that node. For + * example, if the expression evaluates to a property reference then the property will + * be set to the new value. * @param expressionState the current expression state (includes the context) * @param newValue the new value - * @throws EvaluationException if any problem occurs evaluating the expression or setting the new value + * @throws EvaluationException if any problem occurs evaluating the expression or + * setting the new value */ void setValue(ExpressionState expressionState, Object newValue) throws EvaluationException; @@ -78,7 +82,8 @@ public interface SpelNode { /** * Determine the class of the object passed in, unless it is already a class object. * @param obj the object that the caller wants the class of - * @return the class of the object if it is not already a class object, or null if the object is null + * @return the class of the object if it is not already a class object, or null if the + * object is null */ Class getObjectClass(Object obj); diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/SpelParseException.java b/spring-expression/src/main/java/org/springframework/expression/spel/SpelParseException.java index 1935b3ba61..2a623ddccc 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/SpelParseException.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/SpelParseException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,24 +19,20 @@ import org.springframework.expression.ParseException; /** - * Root exception for Spring EL related exceptions. Rather than holding a hard coded string indicating the problem, it - * records a message key and the inserts for the message. See {@link SpelMessage} for the list of all possible messages - * that can occur. - * + * Root exception for Spring EL related exceptions. Rather than holding a hard coded + * string indicating the problem, it records a message key and the inserts for the + * message. See {@link SpelMessage} for the list of all possible messages that can occur. + * * @author Andy Clement * @since 3.0 */ @SuppressWarnings("serial") public class SpelParseException extends ParseException { - private SpelMessage message; - private Object[] inserts; + private final SpelMessage message; + + private final Object[] inserts; -// public SpelParseException(String expressionString, int position, Throwable cause, SpelMessages message, Object... inserts) { -// super(expressionString, position, message.formatMessage(position,inserts), cause); -// this.message = message; -// this.inserts = inserts; -// } public SpelParseException(String expressionString, int position, SpelMessage message, Object... inserts) { super(expressionString, position, message.formatMessage(position,inserts)); @@ -59,36 +55,14 @@ public class SpelParseException extends ParseException { this.inserts = inserts; } -// -// public SpelException(Throwable cause, SpelMessages message, Object... inserts) { -// super(cause); -// this.message = message; -// this.inserts = inserts; -// } -// -// public SpelException(int position, SpelMessages message, Object... inserts) { -// super((Throwable)null); -// this.position = position; -// this.message = message; -// this.inserts = inserts; -// } -// -// public SpelException(SpelMessages message, Object... inserts) { -// super((Throwable)null); -// this.message = message; -// this.inserts = inserts; -// } - /** * @return a formatted message with inserts applied */ @Override public String getMessage() { - if (message != null) - return message.formatMessage(position, inserts); - else - return super.getMessage(); + return (this.message != null ? this.message.formatMessage(this.position, this.inserts) + : super.getMessage()); } /** @@ -102,7 +76,7 @@ public class SpelParseException extends ParseException { * @return the message inserts */ public Object[] getInserts() { - return inserts; + return this.inserts; } } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java b/spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java index ccadb93061..20932fa9b2 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java @@ -30,7 +30,7 @@ public class SpelParserConfiguration { private final boolean autoGrowCollections; - private int maximumAutoGrowSize; + private final int maximumAutoGrowSize; /** diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Assign.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Assign.java index c472ed6393..7d2c045121 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Assign.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Assign.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,8 @@ import org.springframework.expression.TypedValue; import org.springframework.expression.spel.ExpressionState; /** - * Represents assignment. An alternative to calling setValue() for an expression is to use an assign. + * Represents assignment. An alternative to calling setValue() for an expression is to use + * an assign. * *

        Example: 'someNumberProperty=42' * @@ -30,21 +31,23 @@ import org.springframework.expression.spel.ExpressionState; */ public class Assign extends SpelNodeImpl { + public Assign(int pos,SpelNodeImpl... operands) { super(pos,operands); } + @Override public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { - TypedValue newValue = children[1].getValueInternal(state); + TypedValue newValue = this.children[1].getValueInternal(state); getChild(0).setValue(state, newValue.getValue()); return newValue; } @Override public String toStringAST() { - return new StringBuilder().append(getChild(0).toStringAST()).append("=").append(getChild(1).toStringAST()) - .toString(); + return new StringBuilder().append(getChild(0).toStringAST()).append("=").append( + getChild(1).toStringAST()).toString(); } } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/AstUtils.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/AstUtils.java index 92eb3036fa..98cccc4985 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/AstUtils.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/AstUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,13 +30,15 @@ import org.springframework.expression.PropertyAccessor; public class AstUtils { /** - * Determines the set of property resolvers that should be used to try and access a property on the specified target - * type. The resolvers are considered to be in an ordered list, however in the returned list any that are exact - * matches for the input target type (as opposed to 'general' resolvers that could work for any type) are placed at - * the start of the list. In addition, there are specific resolvers that exactly name the class in question and - * resolvers that name a specific class but it is a supertype of the class we have. These are put at the end of the - * specific resolvers set and will be tried after exactly matching accessors but before generic accessors. - * + * Determines the set of property resolvers that should be used to try and access a + * property on the specified target type. The resolvers are considered to be in an + * ordered list, however in the returned list any that are exact matches for the input + * target type (as opposed to 'general' resolvers that could work for any type) are + * placed at the start of the list. In addition, there are specific resolvers that + * exactly name the class in question and resolvers that name a specific class but it + * is a supertype of the class we have. These are put at the end of the specific + * resolvers set and will be tried after exactly matching accessors but before generic + * accessors. * @param targetType the type upon which property access is being attempted * @return a list of resolvers that should be tried in order to access the property */ diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/BeanReference.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/BeanReference.java index 35636fe806..b0be644e1f 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/BeanReference.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/BeanReference.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,25 +31,31 @@ import org.springframework.expression.spel.SpelMessage; */ public class BeanReference extends SpelNodeImpl { - private String beanname; + private final String beanname; + public BeanReference(int pos,String beanname) { super(pos); this.beanname = beanname; } + @Override public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { BeanResolver beanResolver = state.getEvaluationContext().getBeanResolver(); if (beanResolver==null) { - throw new SpelEvaluationException(getStartPosition(),SpelMessage.NO_BEAN_RESOLVER_REGISTERED, beanname); + throw new SpelEvaluationException(getStartPosition(), + SpelMessage.NO_BEAN_RESOLVER_REGISTERED, this.beanname); } + try { - TypedValue bean = new TypedValue(beanResolver.resolve(state.getEvaluationContext(),beanname)); + TypedValue bean = new TypedValue(beanResolver.resolve( + state.getEvaluationContext(), this.beanname)); return bean; - } catch (AccessException ae) { + } + catch (AccessException ae) { throw new SpelEvaluationException( getStartPosition(), ae, SpelMessage.EXCEPTION_DURING_BEAN_RESOLUTION, - beanname, ae.getMessage()); + this.beanname, ae.getMessage()); } } @@ -57,10 +63,11 @@ public class BeanReference extends SpelNodeImpl { public String toStringAST() { StringBuilder sb = new StringBuilder(); sb.append("@"); - if (beanname.indexOf('.')==-1) { - sb.append(beanname); - } else { - sb.append("'").append(beanname).append("'"); + if (this.beanname.indexOf('.') == -1) { + sb.append(this.beanname); + } + else { + sb.append("'").append(this.beanname).append("'"); } return sb.toString(); } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/BooleanLiteral.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/BooleanLiteral.java index 84878ee131..5aa748c8c0 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/BooleanLiteral.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/BooleanLiteral.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,11 +27,13 @@ public class BooleanLiteral extends Literal { private final BooleanTypedValue value; + public BooleanLiteral(String payload, int pos, boolean value) { super(payload, pos); this.value = BooleanTypedValue.forValue(value); } + @Override public BooleanTypedValue getLiteralValue() { return this.value; diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/CompoundExpression.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/CompoundExpression.java index b632adec5e..688670c142 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/CompoundExpression.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/CompoundExpression.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,32 +39,35 @@ public class CompoundExpression extends SpelNodeImpl { @Override protected ValueRef getValueRef(ExpressionState state) throws EvaluationException { - if (getChildCount()==1) { - return children[0].getValueRef(state); + if (getChildCount() == 1) { + return this.children[0].getValueRef(state); } TypedValue result = null; SpelNodeImpl nextNode = null; try { - nextNode = children[0]; + nextNode = this.children[0]; result = nextNode.getValueInternal(state); int cc = getChildCount(); - for (int i = 1; i < cc-1; i++) { + for (int i = 1; i < cc - 1; i++) { try { state.pushActiveContextObject(result); - nextNode = children[i]; + nextNode = this.children[i]; result = nextNode.getValueInternal(state); - } finally { + } + finally { state.popActiveContextObject(); } } try { state.pushActiveContextObject(result); - nextNode = children[cc-1]; + nextNode = this.children[cc-1]; return nextNode.getValueRef(state); - } finally { + } + finally { state.popActiveContextObject(); } - } catch (SpelEvaluationException ee) { + } + catch (SpelEvaluationException ee) { // Correct the position for the error before re-throwing ee.setPosition(nextNode.getStartPosition()); throw ee; @@ -96,7 +99,9 @@ public class CompoundExpression extends SpelNodeImpl { public String toStringAST() { StringBuilder sb = new StringBuilder(); for (int i = 0; i < getChildCount(); i++) { - if (i>0) { sb.append("."); } + if (i > 0) { + sb.append("."); + } sb.append(getChild(i).toStringAST()); } return sb.toString(); diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java index c6a25931fb..a16cc55788 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -132,7 +132,8 @@ public class ConstructorReference extends SpelNodeImpl { Throwable rootCause = ae.getCause().getCause(); if (rootCause instanceof RuntimeException) { throw (RuntimeException) rootCause; - } else { + } + else { String typename = (String) this.children[0].getValueInternal(state).getValue(); throw new SpelEvaluationException(getStartPosition(), rootCause, SpelMessage.CONSTRUCTOR_INVOCATION_PROBLEM, typename, FormatHelper @@ -153,9 +154,9 @@ public class ConstructorReference extends SpelNodeImpl { return executorToUse.execute(state.getEvaluationContext(), arguments); } catch (AccessException ae) { - throw new SpelEvaluationException(getStartPosition(), ae, SpelMessage.CONSTRUCTOR_INVOCATION_PROBLEM, - typename, FormatHelper.formatMethodForMessage("", argumentTypes)); - + throw new SpelEvaluationException(getStartPosition(), ae, + SpelMessage.CONSTRUCTOR_INVOCATION_PROBLEM, typename, + FormatHelper.formatMethodForMessage("", argumentTypes)); } } @@ -168,8 +169,9 @@ public class ConstructorReference extends SpelNodeImpl { * @return a reusable ConstructorExecutor that can be invoked to run the constructor or null * @throws SpelEvaluationException if there is a problem locating the constructor */ - private ConstructorExecutor findExecutorForConstructor(String typename, List argumentTypes, - ExpressionState state) throws SpelEvaluationException { + private ConstructorExecutor findExecutorForConstructor(String typename, + List argumentTypes, ExpressionState state) + throws SpelEvaluationException { EvaluationContext eContext = state.getEvaluationContext(); List cResolvers = eContext.getConstructorResolvers(); @@ -202,8 +204,9 @@ public class ConstructorReference extends SpelNodeImpl { sb.append(getChild(index++).toStringAST()); sb.append("("); for (int i = index; i < getChildCount(); i++) { - if (i > index) + if (i > index) { sb.append(","); + } sb.append(getChild(i).toStringAST()); } sb.append(")"); @@ -221,8 +224,8 @@ public class ConstructorReference extends SpelNodeImpl { Object intendedArrayType = getChild(0).getValue(state); if (!(intendedArrayType instanceof String)) { throw new SpelEvaluationException(getChild(0).getStartPosition(), - SpelMessage.TYPE_NAME_EXPECTED_FOR_ARRAY_CONSTRUCTION, FormatHelper - .formatClassNameForMessage(intendedArrayType.getClass())); + SpelMessage.TYPE_NAME_EXPECTED_FOR_ARRAY_CONSTRUCTION, + FormatHelper.formatClassNameForMessage(intendedArrayType.getClass())); } String type = (String) intendedArrayType; Class componentType; diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Elvis.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Elvis.java index b37548a5a2..7c44d0635c 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Elvis.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Elvis.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,8 +21,8 @@ import org.springframework.expression.TypedValue; import org.springframework.expression.spel.ExpressionState; /** - * Represents the elvis operator ?:. For an expression "a?:b" if a is not null, the value of the expression - * is "a", if a is null then the value of the expression is "b". + * Represents the elvis operator ?:. For an expression "a?:b" if a is not null, the value + * of the expression is "a", if a is null then the value of the expression is "b". * * @author Andy Clement * @since 3.0 @@ -33,25 +33,30 @@ public class Elvis extends SpelNodeImpl { super(pos,args); } + /** - * Evaluate the condition and if not null, return it. If it is null return the other value. + * Evaluate the condition and if not null, return it. If it is null return the other + * value. * @param state the expression state - * @throws EvaluationException if the condition does not evaluate correctly to a boolean or there is a problem - * executing the chosen alternative + * @throws EvaluationException if the condition does not evaluate correctly to a + * boolean or there is a problem executing the chosen alternative */ @Override public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { - TypedValue value = children[0].getValueInternal(state); - if (value.getValue()!=null && !((value.getValue() instanceof String) && ((String)value.getValue()).length()==0)) { + TypedValue value = this.children[0].getValueInternal(state); + if ((value.getValue() != null) && !((value.getValue() instanceof String) && + ((String) value.getValue()).length() == 0)) { return value; - } else { - return children[1].getValueInternal(state); + } + else { + return this.children[1].getValueInternal(state); } } @Override public String toStringAST() { - return new StringBuilder().append(getChild(0).toStringAST()).append(" ?: ").append(getChild(1).toStringAST()).toString(); + return new StringBuilder().append(getChild(0).toStringAST()).append(" ?: ").append( + getChild(1).toStringAST()).toString(); } } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/FloatLiteral.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/FloatLiteral.java index c63ee9a8ce..6d7be2b481 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/FloatLiteral.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/FloatLiteral.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.expression.spel.ast; import org.springframework.expression.TypedValue; @@ -24,6 +25,7 @@ import org.springframework.expression.TypedValue; * @since 3.2 */ public class FloatLiteral extends Literal { + private final TypedValue value; FloatLiteral(String payload, int pos, float value) { @@ -31,6 +33,7 @@ public class FloatLiteral extends Literal { this.value = new TypedValue(value); } + @Override public TypedValue getLiteralValue() { return this.value; diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/FormatHelper.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/FormatHelper.java index 1d3b853e29..8719f49526 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/FormatHelper.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/FormatHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -75,7 +75,8 @@ public class FormatHelper { for (int i = 0; i < dims; i++) { fmtd.append("[]"); } - } else { + } + else { fmtd.append(clazz.getName()); } return fmtd.toString(); diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/FunctionReference.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/FunctionReference.java index db69a05945..642c4497ee 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/FunctionReference.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/FunctionReference.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,14 +31,15 @@ import org.springframework.expression.spel.support.ReflectionHelper; import org.springframework.util.ReflectionUtils; /** - * A function reference is of the form "#someFunction(a,b,c)". Functions may be defined in the context prior to the - * expression being evaluated or within the expression itself using a lambda function definition. For example: Lambda - * function definition in an expression: "(#max = {|x,y|$x>$y?$x:$y};max(2,3))" Calling context defined function: - * "#isEven(37)". Functions may also be static java methods, registered in the context prior to invocation of the - * expression. + * A function reference is of the form "#someFunction(a,b,c)". Functions may be defined in + * the context prior to the expression being evaluated or within the expression itself + * using a lambda function definition. For example: Lambda function definition in an + * expression: "(#max = {|x,y|$x>$y?$x:$y};max(2,3))" Calling context defined function: + * "#isEven(37)". Functions may also be static java methods, registered in the context + * prior to invocation of the expression. * - *

        Functions are very simplistic, the arguments are not part of the definition (right now), - * so the names must be unique. + *

        Functions are very simplistic, the arguments are not part of the definition (right + * now), so the names must be unique. * * @author Andy Clement * @since 3.0 @@ -47,21 +48,23 @@ public class FunctionReference extends SpelNodeImpl { private final String name; + public FunctionReference(String functionName, int pos, SpelNodeImpl... arguments) { super(pos,arguments); - name = functionName; + this.name = functionName; } + @Override public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { - TypedValue o = state.lookupVariable(name); + TypedValue o = state.lookupVariable(this.name); if (o == null) { - throw new SpelEvaluationException(getStartPosition(), SpelMessage.FUNCTION_NOT_DEFINED, name); + throw new SpelEvaluationException(getStartPosition(), SpelMessage.FUNCTION_NOT_DEFINED, this.name); } // Two possibilities: a lambda function or a Java static method registered as a function if (!(o.getValue() instanceof Method)) { - throw new SpelEvaluationException(SpelMessage.FUNCTION_REFERENCE_CANNOT_BE_INVOKED, name, o.getClass()); + throw new SpelEvaluationException(SpelMessage.FUNCTION_REFERENCE_CANNOT_BE_INVOKED, this.name, o.getClass()); } try { return executeFunctionJLRMethod(state, (Method) o.getValue()); @@ -89,9 +92,9 @@ public class FunctionReference extends SpelNodeImpl { } // Only static methods can be called in this way if (!Modifier.isStatic(method.getModifiers())) { - throw new SpelEvaluationException(getStartPosition(), SpelMessage.FUNCTION_MUST_BE_STATIC, method - .getDeclaringClass().getName() - + "." + method.getName(), name); + throw new SpelEvaluationException(getStartPosition(), + SpelMessage.FUNCTION_MUST_BE_STATIC, + method.getDeclaringClass().getName() + "." + method.getName(), this.name); } // Convert arguments if necessary and remap them for varargs if required @@ -100,7 +103,8 @@ public class FunctionReference extends SpelNodeImpl { ReflectionHelper.convertAllArguments(converter, functionArgs, method); } if (method.isVarArgs()) { - functionArgs = ReflectionHelper.setupArgumentsForVarargsInvocation(method.getParameterTypes(), functionArgs); + functionArgs = ReflectionHelper.setupArgumentsForVarargsInvocation( + method.getParameterTypes(), functionArgs); } try { @@ -116,11 +120,12 @@ public class FunctionReference extends SpelNodeImpl { @Override public String toStringAST() { - StringBuilder sb = new StringBuilder("#").append(name); + StringBuilder sb = new StringBuilder("#").append(this.name); sb.append("("); for (int i = 0; i < getChildCount(); i++) { - if (i > 0) + if (i > 0) { sb.append(","); + } sb.append(getChild(i).toStringAST()); } sb.append(")"); @@ -137,7 +142,7 @@ public class FunctionReference extends SpelNodeImpl { // Compute arguments to the function Object[] arguments = new Object[getChildCount()]; for (int i = 0; i < arguments.length; i++) { - arguments[i] = children[i].getValueInternal(state).getValue(); + arguments[i] = this.children[i].getValueInternal(state).getValue(); } return arguments; } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Identifier.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Identifier.java index e174535301..d258fbb993 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Identifier.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Identifier.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,14 +27,16 @@ public class Identifier extends SpelNodeImpl { private final TypedValue id; + public Identifier(String payload,int pos) { super(pos); this.id = new TypedValue(payload); } + @Override public String toStringAST() { - return (String)this.id.getValue(); + return (String) this.id.getValue(); } @Override diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java index 4f919beec2..82e7db4259 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java @@ -33,9 +33,8 @@ import org.springframework.expression.spel.SpelMessage; import org.springframework.expression.spel.support.ReflectivePropertyAccessor; /** - * An Indexer can index into some proceeding structure to access a particular - * piece of it. Supported structures are: strings/collections - * (lists/sets)/arrays + * An Indexer can index into some proceeding structure to access a particular piece of it. + * Supported structures are: strings/collections (lists/sets)/arrays * * @author Andy Clement * @author Phillip Webb @@ -95,26 +94,28 @@ public class Indexer extends SpelNodeImpl { private final Object array; - private final int idx; + private final int index; private final TypeDescriptor typeDescriptor; - ArrayIndexingValueRef(TypeConverter typeConverter, Object array, int idx, TypeDescriptor typeDescriptor) { + + ArrayIndexingValueRef(TypeConverter typeConverter, Object array, int index, TypeDescriptor typeDescriptor) { this.typeConverter = typeConverter; this.array = array; - this.idx = idx; + this.index = index; this.typeDescriptor = typeDescriptor; } + @Override public TypedValue getValue() { - Object arrayElement = accessArrayElement(this.array, this.idx); + Object arrayElement = accessArrayElement(this.array, this.index); return new TypedValue(arrayElement, this.typeDescriptor.elementTypeDescriptor(arrayElement)); } @Override public void setValue(Object newValue) { - setArrayElement(this.typeConverter, this.array, this.idx, newValue, + setArrayElement(this.typeConverter, this.array, this.index, newValue, this.typeDescriptor.getElementTypeDescriptor().getType()); } @@ -136,17 +137,21 @@ public class Indexer extends SpelNodeImpl { private final TypeDescriptor mapEntryTypeDescriptor; - MapIndexingValueRef(TypeConverter typeConverter, Map map, Object key, TypeDescriptor mapEntryTypeDescriptor) { + + MapIndexingValueRef(TypeConverter typeConverter, Map map, Object key, + TypeDescriptor mapEntryTypeDescriptor) { this.typeConverter = typeConverter; this.map = map; this.key = key; this.mapEntryTypeDescriptor = mapEntryTypeDescriptor; } + @Override public TypedValue getValue() { Object value = this.map.get(this.key); - return new TypedValue(value, this.mapEntryTypeDescriptor.getMapValueTypeDescriptor(value)); + return new TypedValue(value, + this.mapEntryTypeDescriptor.getMapValueTypeDescriptor(value)); } @Override @@ -171,71 +176,75 @@ public class Indexer extends SpelNodeImpl { private final String name; - private final EvaluationContext eContext; + private final EvaluationContext evaluationContext; + + private final TypeDescriptor targetObjectTypeDescriptor; - private final TypeDescriptor td; public PropertyIndexingValueRef(Object targetObject, String value, EvaluationContext evaluationContext, TypeDescriptor targetObjectTypeDescriptor) { this.targetObject = targetObject; this.name = value; - this.eContext = evaluationContext; - this.td = targetObjectTypeDescriptor; + this.evaluationContext = evaluationContext; + this.targetObjectTypeDescriptor = targetObjectTypeDescriptor; } + @Override public TypedValue getValue() { - Class targetObjectRuntimeClass = getObjectClass(targetObject); + Class targetObjectRuntimeClass = getObjectClass(this.targetObject); try { - if (cachedReadName != null && cachedReadName.equals(name) && cachedReadTargetType != null && - cachedReadTargetType.equals(targetObjectRuntimeClass)) { + if (Indexer.this.cachedReadName != null && Indexer.this.cachedReadName.equals(this.name) && Indexer.this.cachedReadTargetType != null && + Indexer.this.cachedReadTargetType.equals(targetObjectRuntimeClass)) { // it is OK to use the cached accessor - return cachedReadAccessor.read(this.eContext, this.targetObject, this.name); + return Indexer.this.cachedReadAccessor.read(this.evaluationContext, this.targetObject, this.name); } - List accessorsToTry = - AstUtils.getPropertyAccessorsToTry(targetObjectRuntimeClass, eContext.getPropertyAccessors()); + + List accessorsToTry = AstUtils.getPropertyAccessorsToTry( + targetObjectRuntimeClass, this.evaluationContext.getPropertyAccessors()); + if (accessorsToTry != null) { for (PropertyAccessor accessor : accessorsToTry) { - if (accessor.canRead(this.eContext, this.targetObject, this.name)) { + if (accessor.canRead(this.evaluationContext, this.targetObject, this.name)) { if (accessor instanceof ReflectivePropertyAccessor) { accessor = ((ReflectivePropertyAccessor) accessor).createOptimalAccessor( - this.eContext, this.targetObject, this.name); + this.evaluationContext, this.targetObject, this.name); } - cachedReadAccessor = accessor; - cachedReadName = this.name; - cachedReadTargetType = targetObjectRuntimeClass; - return accessor.read(this.eContext, this.targetObject, this.name); + Indexer.this.cachedReadAccessor = accessor; + Indexer.this.cachedReadName = this.name; + Indexer.this.cachedReadTargetType = targetObjectRuntimeClass; + return accessor.read(this.evaluationContext, this.targetObject, this.name); } } } } catch (AccessException ex) { throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, - this.td.toString()); + this.targetObjectTypeDescriptor.toString()); } throw new SpelEvaluationException(getStartPosition(), SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, - this.td.toString()); + this.targetObjectTypeDescriptor.toString()); } @Override public void setValue(Object newValue) { - Class contextObjectClass = getObjectClass(targetObject); + Class contextObjectClass = getObjectClass(this.targetObject); try { - if (cachedWriteName != null && cachedWriteName.equals(name) && cachedWriteTargetType != null && - cachedWriteTargetType.equals(contextObjectClass)) { + if (Indexer.this.cachedWriteName != null && Indexer.this.cachedWriteName.equals(this.name) && Indexer.this.cachedWriteTargetType != null && + Indexer.this.cachedWriteTargetType.equals(contextObjectClass)) { // it is OK to use the cached accessor - cachedWriteAccessor.write(this.eContext, this.targetObject, this.name, newValue); + Indexer.this.cachedWriteAccessor.write(this.evaluationContext, this.targetObject, this.name, newValue); return; } List accessorsToTry = - AstUtils.getPropertyAccessorsToTry(contextObjectClass, this.eContext.getPropertyAccessors()); + AstUtils.getPropertyAccessorsToTry(contextObjectClass, this.evaluationContext.getPropertyAccessors()); if (accessorsToTry != null) { for (PropertyAccessor accessor : accessorsToTry) { - if (accessor.canWrite(this.eContext, this.targetObject, this.name)) { - cachedWriteName = this.name; - cachedWriteTargetType = contextObjectClass; - cachedWriteAccessor = accessor; - accessor.write(this.eContext, this.targetObject, this.name, newValue); + if (accessor.canWrite(this.evaluationContext, this.targetObject, this.name)) { + Indexer.this.cachedWriteName = this.name; + Indexer.this.cachedWriteTargetType = contextObjectClass; + Indexer.this.cachedWriteAccessor = accessor; + accessor.write(this.evaluationContext, this.targetObject, this.name, newValue); return; } } @@ -267,7 +276,8 @@ public class Indexer extends SpelNodeImpl { private final boolean growCollection; - private int maximumSize; + private final int maximumSize; + CollectionIndexingValueRef(Collection collection, int index, TypeDescriptor collectionEntryTypeDescriptor, TypeConverter typeConverter, boolean growCollection, int maximumSize) { @@ -279,6 +289,7 @@ public class Indexer extends SpelNodeImpl { this.maximumSize = maximumSize; } + @Override public TypedValue getValue() { growCollectionIfNecessary(); @@ -356,19 +367,21 @@ public class Indexer extends SpelNodeImpl { private final int index; - private final TypeDescriptor td; + private final TypeDescriptor typeDescriptor; - public StringIndexingLValue(String target, int index, TypeDescriptor td) { + + public StringIndexingLValue(String target, int index, TypeDescriptor typeDescriptor) { this.target = target; this.index = index; - this.td = td; + this.typeDescriptor = typeDescriptor; } + @Override public TypedValue getValue() { if (this.index >= this.target.length()) { throw new SpelEvaluationException(getStartPosition(), SpelMessage.STRING_INDEX_OUT_OF_BOUNDS, - this.target.length(), index); + this.target.length(), this.index); } return new TypedValue(String.valueOf(this.target.charAt(this.index))); } @@ -376,7 +389,7 @@ public class Indexer extends SpelNodeImpl { @Override public void setValue(Object newValue) { throw new SpelEvaluationException(getStartPosition(), SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, - this.td.toString()); + this.typeDescriptor.toString()); } @Override @@ -387,6 +400,7 @@ public class Indexer extends SpelNodeImpl { @Override protected ValueRef getValueRef(ExpressionState state) throws EvaluationException { + TypedValue context = state.getActiveContextObject(); Object targetObject = context.getValue(); TypeDescriptor targetObjectTypeDescriptor = context.getTypeDescriptor(); diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/InlineList.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/InlineList.java index 1839d203c4..cae9002f40 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/InlineList.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/InlineList.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.expression.spel.ast; import java.util.ArrayList; @@ -35,11 +36,13 @@ public class InlineList extends SpelNodeImpl { // if the list is purely literals, it is a constant value and can be computed and cached TypedValue constant = null; // TODO must be immutable list + public InlineList(int pos, SpelNodeImpl... args) { super(pos, args); checkIfConstant(); } + /** * If all the components of the list are constants, or lists that themselves contain constants, then a constant list * can be built to represent this node. This will speed up later getValue calls and reduce the amount of garbage @@ -55,7 +58,8 @@ public class InlineList extends SpelNodeImpl { if (!inlineList.isConstant()) { isConstant = false; } - } else { + } + else { isConstant = false; } } @@ -67,7 +71,8 @@ public class InlineList extends SpelNodeImpl { SpelNode child = getChild(c); if ((child instanceof Literal)) { constantList.add(((Literal) child).getLiteralValue().getValue()); - } else if (child instanceof InlineList) { + } + else if (child instanceof InlineList) { constantList.add(((InlineList) child).getConstantValue()); } } @@ -77,9 +82,10 @@ public class InlineList extends SpelNodeImpl { @Override public TypedValue getValueInternal(ExpressionState expressionState) throws EvaluationException { - if (constant != null) { - return constant; - } else { + if (this.constant != null) { + return this.constant; + } + else { List returnValue = new ArrayList(); int childcount = getChildCount(); for (int c = 0; c < childcount; c++) { @@ -109,12 +115,12 @@ public class InlineList extends SpelNodeImpl { * @return whether this list is a constant value */ public boolean isConstant() { - return constant != null; + return this.constant != null; } @SuppressWarnings("unchecked") private List getConstantValue() { - return (List) constant.getValue(); + return (List) this.constant.getValue(); } } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/IntLiteral.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/IntLiteral.java index cafbd17d28..a648144901 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/IntLiteral.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/IntLiteral.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,11 +27,13 @@ public class IntLiteral extends Literal { private final TypedValue value; + IntLiteral(String payload, int pos, int value) { super(payload, pos); this.value = new TypedValue(value); } + @Override public TypedValue getLiteralValue() { return this.value; diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/LongLiteral.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/LongLiteral.java index 3d69e3fa10..3404411dbb 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/LongLiteral.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/LongLiteral.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,11 +28,13 @@ public class LongLiteral extends Literal { private final TypedValue value; + LongLiteral(String payload, int pos, long value) { super(payload, pos); this.value = new TypedValue(value); } + @Override public TypedValue getLiteralValue() { return this.value; diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java index 26c5179005..1e88cebc96 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java @@ -18,6 +18,7 @@ package org.springframework.expression.spel.ast; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import org.springframework.core.convert.TypeDescriptor; @@ -33,6 +34,8 @@ import org.springframework.expression.spel.SpelEvaluationException; import org.springframework.expression.spel.SpelMessage; /** + * Expression language AST node that represents a method reference. + * * @author Andy Clement * @author Juergen Hoeller * @since 3.0 @@ -43,7 +46,7 @@ public class MethodReference extends SpelNodeImpl { private final boolean nullSafe; - private volatile MethodExecutor cachedExecutor; + private volatile CachedMethodExecutor cachedExecutor; public MethodReference(boolean nullSafe, String methodName, int pos, SpelNodeImpl... arguments) { @@ -99,21 +102,22 @@ public class MethodReference extends SpelNodeImpl { state.popActiveContextObject(); } } + List argumentTypes = getTypes(arguments); if (currentContext.getValue() == null) { if (this.nullSafe) { return TypedValue.NULL; } else { throw new SpelEvaluationException(getStartPosition(), SpelMessage.METHOD_CALL_ON_NULL_OBJECT_NOT_ALLOWED, - FormatHelper.formatMethodForMessage(this.name, getTypes(arguments))); + FormatHelper.formatMethodForMessage(this.name, argumentTypes)); } } - MethodExecutor executorToUse = this.cachedExecutor; + MethodExecutor executorToUse = getCachedExecutor(argumentTypes); if (executorToUse != null) { try { - return executorToUse.execute( - state.getEvaluationContext(), state.getActiveContextObject().getValue(), arguments); + return executorToUse.execute(state.getEvaluationContext(), + state.getActiveContextObject().getValue(), arguments); } catch (AccessException ae) { // Two reasons this can occur: @@ -134,11 +138,11 @@ public class MethodReference extends SpelNodeImpl { } // either there was no accessor or it no longer existed - executorToUse = findAccessorForMethod(this.name, getTypes(arguments), state); - this.cachedExecutor = executorToUse; + executorToUse = findAccessorForMethod(this.name, argumentTypes, state); + this.cachedExecutor = new CachedMethodExecutor(executorToUse, argumentTypes); try { - return executorToUse.execute( - state.getEvaluationContext(), state.getActiveContextObject().getValue(), arguments); + return executorToUse.execute(state.getEvaluationContext(), + state.getActiveContextObject().getValue(), arguments); } catch (AccessException ae) { // Same unwrapping exception handling as above in above catch block @@ -158,12 +162,10 @@ public class MethodReference extends SpelNodeImpl { if (rootCause instanceof RuntimeException) { throw (RuntimeException) rootCause; } - else { - throw new ExpressionInvocationTargetException(getStartPosition(), - "A problem occurred when trying to execute method '" + this.name + - "' on object of type '" + state.getActiveContextObject().getValue().getClass().getName() + "'", - rootCause); - } + throw new ExpressionInvocationTargetException(getStartPosition(), + "A problem occurred when trying to execute method '" + this.name + + "' on object of type '" + state.getActiveContextObject().getValue().getClass().getName() + "'", + rootCause); } } @@ -172,7 +174,7 @@ public class MethodReference extends SpelNodeImpl { for (Object argument : arguments) { descriptors.add(TypeDescriptor.forObject(argument)); } - return descriptors; + return Collections.unmodifiableList(descriptors); } @Override @@ -180,42 +182,57 @@ public class MethodReference extends SpelNodeImpl { StringBuilder sb = new StringBuilder(); sb.append(this.name).append("("); for (int i = 0; i < getChildCount(); i++) { - if (i > 0) + if (i > 0) { sb.append(","); + } sb.append(getChild(i).toStringAST()); } sb.append(")"); return sb.toString(); } - private MethodExecutor findAccessorForMethod(String name, List argumentTypes, ExpressionState state) + private MethodExecutor findAccessorForMethod(String name, + List argumentTypes, ExpressionState state) throws SpelEvaluationException { - - return findAccessorForMethod(name,argumentTypes,state.getActiveContextObject().getValue(),state.getEvaluationContext()); + return findAccessorForMethod(name, argumentTypes, + state.getActiveContextObject().getValue(), state.getEvaluationContext()); } private MethodExecutor findAccessorForMethod(String name, List argumentTypes, Object contextObject, EvaluationContext eContext) throws SpelEvaluationException { - List mResolvers = eContext.getMethodResolvers(); - if (mResolvers != null) { - for (MethodResolver methodResolver : mResolvers) { + List methodResolvers = eContext.getMethodResolvers(); + if (methodResolvers != null) { + for (MethodResolver methodResolver : methodResolvers) { try { - MethodExecutor cEx = methodResolver.resolve(eContext, contextObject, name, argumentTypes); - if (cEx != null) { - return cEx; + MethodExecutor methodExecutor = methodResolver.resolve(eContext, + contextObject, name, argumentTypes); + if (methodExecutor != null) { + return methodExecutor; } } catch (AccessException ex) { - throw new SpelEvaluationException(getStartPosition(),ex, SpelMessage.PROBLEM_LOCATING_METHOD, - name, contextObject.getClass()); + throw new SpelEvaluationException(getStartPosition(), ex, + SpelMessage.PROBLEM_LOCATING_METHOD, name, + contextObject.getClass()); } } } - throw new SpelEvaluationException(getStartPosition(),SpelMessage.METHOD_NOT_FOUND, + throw new SpelEvaluationException( + getStartPosition(), + SpelMessage.METHOD_NOT_FOUND, FormatHelper.formatMethodForMessage(name, argumentTypes), - FormatHelper.formatClassNameForMessage(contextObject instanceof Class ? ((Class) contextObject) : contextObject.getClass())); + FormatHelper.formatClassNameForMessage(contextObject instanceof Class ? ((Class) contextObject) + : contextObject.getClass())); + } + + private MethodExecutor getCachedExecutor(List argumentTypes) { + if (this.cachedExecutor == null || !this.cachedExecutor.isSuitable(argumentTypes)) { + this.cachedExecutor = null; + return null; + } + return this.cachedExecutor.get(); } @@ -229,16 +246,21 @@ public class MethodReference extends SpelNodeImpl { private final Object[] arguments; + private List argumentTypes; + + MethodValueRef(ExpressionState state, EvaluationContext evaluationContext, Object object, Object[] arguments) { this.state = state; this.evaluationContext = evaluationContext; this.target = object; this.arguments = arguments; + this.argumentTypes = getTypes(this.arguments); } + @Override public TypedValue getValue() { - MethodExecutor executorToUse = cachedExecutor; + MethodExecutor executorToUse = getCachedExecutor(this.argumentTypes); if (executorToUse != null) { try { return executorToUse.execute(this.evaluationContext, this.target, this.arguments); @@ -257,21 +279,23 @@ public class MethodReference extends SpelNodeImpl { throwSimpleExceptionIfPossible(this.state, ae); // at this point we know it wasn't a user problem so worth a retry if a better candidate can be found - cachedExecutor = null; + MethodReference.this.cachedExecutor = null; } } // either there was no accessor or it no longer existed - executorToUse = findAccessorForMethod(name, getTypes(this.arguments), this.target, this.evaluationContext); - cachedExecutor = executorToUse; + executorToUse = findAccessorForMethod(MethodReference.this.name, argumentTypes, this.target, this.evaluationContext); + MethodReference.this.cachedExecutor = new CachedMethodExecutor(executorToUse, this.argumentTypes); try { return executorToUse.execute(this.evaluationContext, this.target, this.arguments); } - catch (AccessException ae) { + catch (AccessException ex) { // Same unwrapping exception handling as above in above catch block - throwSimpleExceptionIfPossible(this.state, ae); - throw new SpelEvaluationException( getStartPosition(), ae, SpelMessage.EXCEPTION_DURING_METHOD_INVOCATION, - name, this.state.getActiveContextObject().getValue().getClass().getName(), ae.getMessage()); + throwSimpleExceptionIfPossible(this.state, ex); + throw new SpelEvaluationException(getStartPosition(), ex, + SpelMessage.EXCEPTION_DURING_METHOD_INVOCATION, + MethodReference.this.name, this.state.getActiveContextObject().getValue().getClass().getName(), + ex.getMessage()); } } @@ -286,4 +310,27 @@ public class MethodReference extends SpelNodeImpl { } } + + private static class CachedMethodExecutor { + + private final MethodExecutor methodExecutor; + + private final List argumentTypes; + + + public CachedMethodExecutor(MethodExecutor methodExecutor, + List argumentTypes) { + this.methodExecutor = methodExecutor; + this.argumentTypes = argumentTypes; + } + + + public boolean isSuitable(List argumentTypes) { + return (this.methodExecutor != null && this.argumentTypes.equals(argumentTypes)); + } + + public MethodExecutor get() { + return this.methodExecutor; + } + } } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/NullLiteral.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/NullLiteral.java index 7ec4c6105a..5f7beb80f8 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/NullLiteral.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/NullLiteral.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,8 @@ package org.springframework.expression.spel.ast; import org.springframework.expression.TypedValue; /** + * Expression language AST node that represents null. + * * @author Andy Clement * @since 3.0 */ @@ -28,6 +30,7 @@ public class NullLiteral extends Literal { super(null,pos); } + @Override public TypedValue getLiteralValue() { return TypedValue.NULL; diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpAnd.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpAnd.java index b2140d11e0..c6c69df23f 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpAnd.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpAnd.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,6 +37,7 @@ public class OpAnd extends Operator { super("and", pos, operands); } + @Override public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { if (getBooleanValue(state, getLeftOperand()) == false) { @@ -52,9 +53,9 @@ public class OpAnd extends Operator { assertValueNotNull(value); return value; } - catch (SpelEvaluationException ee) { - ee.setPosition(operand.getStartPosition()); - throw ee; + catch (SpelEvaluationException ex) { + ex.setPosition(operand.getStartPosition()); + throw ex; } } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpDec.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpDec.java index 7536ddfa2a..0a5a1236d7 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpDec.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpDec.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,7 +33,8 @@ import org.springframework.util.Assert; */ public class OpDec extends Operator { - private boolean postfix; // false means prefix + private final boolean postfix; // false means prefix + public OpDec(int pos, boolean postfix, SpelNodeImpl... operands) { super("--", pos, operands); @@ -41,6 +42,7 @@ public class OpDec extends Operator { this.postfix = postfix; } + @Override public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { SpelNodeImpl operand = getLeftOperand(); @@ -57,26 +59,37 @@ public class OpDec extends Operator { if (operandValue instanceof Number) { Number op1 = (Number) operandValue; if (op1 instanceof Double) { - newValue = new TypedValue(op1.doubleValue() - 1.0d, operandTypedValue.getTypeDescriptor()); - } else if (op1 instanceof Float) { - newValue = new TypedValue(op1.floatValue() - 1.0f, operandTypedValue.getTypeDescriptor()); - } else if (op1 instanceof Long) { - newValue = new TypedValue(op1.longValue() - 1L, operandTypedValue.getTypeDescriptor()); - } else if (op1 instanceof Short) { - newValue = new TypedValue(op1.shortValue() - (short)1, operandTypedValue.getTypeDescriptor()); - } else { - newValue = new TypedValue(op1.intValue() - 1, operandTypedValue.getTypeDescriptor()); + newValue = new TypedValue(op1.doubleValue() - 1.0d, + operandTypedValue.getTypeDescriptor()); + } + else if (op1 instanceof Float) { + newValue = new TypedValue(op1.floatValue() - 1.0f, + operandTypedValue.getTypeDescriptor()); + } + else if (op1 instanceof Long) { + newValue = new TypedValue(op1.longValue() - 1L, + operandTypedValue.getTypeDescriptor()); + } + else if (op1 instanceof Short) { + newValue = new TypedValue(op1.shortValue() - (short) 1, + operandTypedValue.getTypeDescriptor()); + } + else { + newValue = new TypedValue(op1.intValue() - 1, + operandTypedValue.getTypeDescriptor()); } } if (newValue==null) { try { newValue = state.operate(Operation.SUBTRACT, returnValue.getValue(), 1); - } catch (SpelEvaluationException see) { - if (see.getMessageCode()==SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES) { + } + catch (SpelEvaluationException ex) { + if (ex.getMessageCode() == SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES) { // This means the operand is not decrementable throw new SpelEvaluationException(operand.getStartPosition(),SpelMessage.OPERAND_NOT_DECREMENTABLE,operand.toStringAST()); - } else { - throw see; + } + else { + throw ex; } } } @@ -84,16 +97,19 @@ public class OpDec extends Operator { // set the new value try { lvalue.setValue(newValue.getValue()); - } catch (SpelEvaluationException see) { + } + catch (SpelEvaluationException see) { // if unable to set the value the operand is not writable (e.g. 1-- ) - if (see.getMessageCode()==SpelMessage.SETVALUE_NOT_SUPPORTED) { - throw new SpelEvaluationException(operand.getStartPosition(),SpelMessage.OPERAND_NOT_DECREMENTABLE); - } else { + if (see.getMessageCode() == SpelMessage.SETVALUE_NOT_SUPPORTED) { + throw new SpelEvaluationException(operand.getStartPosition(), + SpelMessage.OPERAND_NOT_DECREMENTABLE); + } + else { throw see; } } - if (!postfix) { + if (!this.postfix) { // the return value is the new value, not the original value returnValue = newValue; } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpDivide.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpDivide.java index ca8130dbff..844e2aa1d8 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpDivide.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpDivide.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,6 +34,7 @@ public class OpDivide extends Operator { super("/", pos, operands); } + @Override public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { Object operandOne = getLeftOperand().getValueInternal(state).getValue(); @@ -43,9 +44,11 @@ public class OpDivide extends Operator { Number op2 = (Number) operandTwo; if (op1 instanceof Double || op2 instanceof Double) { return new TypedValue(op1.doubleValue() / op2.doubleValue()); - } else if (op1 instanceof Float || op2 instanceof Float) { + } + else if (op1 instanceof Float || op2 instanceof Float) { return new TypedValue(op1.floatValue() / op2.floatValue()); - } else if (op1 instanceof Long || op2 instanceof Long) { + } + else if (op1 instanceof Long || op2 instanceof Long) { return new TypedValue(op1.longValue() / op2.longValue()); } else { diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpEQ.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpEQ.java index 17f9eb8faf..1121cc840a 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpEQ.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpEQ.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,8 +32,10 @@ public class OpEQ extends Operator { super("==", pos, operands); } + @Override - public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException { + public BooleanTypedValue getValueInternal(ExpressionState state) + throws EvaluationException { Object left = getLeftOperand().getValueInternal(state).getValue(); Object right = getRightOperand().getValueInternal(state).getValue(); if (left instanceof Number && right instanceof Number) { @@ -41,18 +43,23 @@ public class OpEQ extends Operator { Number op2 = (Number) right; if (op1 instanceof Double || op2 instanceof Double) { return BooleanTypedValue.forValue(op1.doubleValue() == op2.doubleValue()); - } else if (op1 instanceof Float || op2 instanceof Float) { - return BooleanTypedValue.forValue(op1.floatValue() == op2.floatValue()); - } else if (op1 instanceof Long || op2 instanceof Long) { + } + else if (op1 instanceof Float || op2 instanceof Float) { + return BooleanTypedValue.forValue(op1.floatValue() == op2.floatValue()); + } + else if (op1 instanceof Long || op2 instanceof Long) { return BooleanTypedValue.forValue(op1.longValue() == op2.longValue()); - } else { + } + else { return BooleanTypedValue.forValue(op1.intValue() == op2.intValue()); } } - if (left!=null && (left instanceof Comparable)) { - return BooleanTypedValue.forValue(state.getTypeComparator().compare(left, right) == 0); - } else { - return BooleanTypedValue.forValue(left==right); + if (left != null && (left instanceof Comparable)) { + return BooleanTypedValue.forValue(state.getTypeComparator().compare(left, + right) == 0); + } + else { + return BooleanTypedValue.forValue(left == right); } } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpGE.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpGE.java index a267b7037e..6e1294e461 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpGE.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpGE.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,6 +31,7 @@ public class OpGE extends Operator { super(">=", pos, operands); } + @Override public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException { Object left = getLeftOperand().getValueInternal(state).getValue(); @@ -40,11 +41,14 @@ public class OpGE extends Operator { Number rightNumber = (Number) right; if (leftNumber instanceof Double || rightNumber instanceof Double) { return BooleanTypedValue.forValue(leftNumber.doubleValue() >= rightNumber.doubleValue()); - } else if (leftNumber instanceof Float || rightNumber instanceof Float) { + } + else if (leftNumber instanceof Float || rightNumber instanceof Float) { return BooleanTypedValue.forValue(leftNumber.floatValue() >= rightNumber.floatValue()); - } else if (leftNumber instanceof Long || rightNumber instanceof Long) { - return BooleanTypedValue.forValue( leftNumber.longValue() >= rightNumber.longValue()); - } else { + } + else if (leftNumber instanceof Long || rightNumber instanceof Long) { + return BooleanTypedValue.forValue(leftNumber.longValue() >= rightNumber.longValue()); + } + else { return BooleanTypedValue.forValue(leftNumber.intValue() >= rightNumber.intValue()); } } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpGT.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpGT.java index e2b088a2f5..3d751c976d 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpGT.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpGT.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,6 +32,7 @@ public class OpGT extends Operator { super(">", pos, operands); } + @Override public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException { Object left = getLeftOperand().getValueInternal(state).getValue(); @@ -41,9 +42,11 @@ public class OpGT extends Operator { Number rightNumber = (Number) right; if (leftNumber instanceof Double || rightNumber instanceof Double) { return BooleanTypedValue.forValue(leftNumber.doubleValue() > rightNumber.doubleValue()); - } else if (leftNumber instanceof Long || rightNumber instanceof Long) { + } + else if (leftNumber instanceof Long || rightNumber instanceof Long) { return BooleanTypedValue.forValue(leftNumber.longValue() > rightNumber.longValue()); - } else { + } + else { return BooleanTypedValue.forValue(leftNumber.intValue() > rightNumber.intValue()); } } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpInc.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpInc.java index eb829e608a..43937ca953 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpInc.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpInc.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,7 +33,8 @@ import org.springframework.util.Assert; */ public class OpInc extends Operator { - private boolean postfix; // false means prefix + private final boolean postfix; // false means prefix + public OpInc(int pos, boolean postfix, SpelNodeImpl... operands) { super("++", pos, operands); @@ -41,6 +42,7 @@ public class OpInc extends Operator { this.postfix = postfix; } + @Override public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { SpelNodeImpl operand = getLeftOperand(); @@ -55,43 +57,56 @@ public class OpInc extends Operator { if (operandValue instanceof Number) { Number op1 = (Number) operandValue; if (op1 instanceof Double) { - newValue = new TypedValue(op1.doubleValue() + 1.0d, operandTypedValue.getTypeDescriptor()); - } else if (op1 instanceof Float) { - newValue = new TypedValue(op1.floatValue() + 1.0f, operandTypedValue.getTypeDescriptor()); - } else if (op1 instanceof Long) { - newValue = new TypedValue(op1.longValue() + 1L, operandTypedValue.getTypeDescriptor()); - } else if (op1 instanceof Short) { - newValue = new TypedValue(op1.shortValue() + (short)1, operandTypedValue.getTypeDescriptor()); - } else { - newValue = new TypedValue(op1.intValue() + 1, operandTypedValue.getTypeDescriptor()); + newValue = new TypedValue(op1.doubleValue() + 1.0d, + operandTypedValue.getTypeDescriptor()); + } + else if (op1 instanceof Float) { + newValue = new TypedValue(op1.floatValue() + 1.0f, + operandTypedValue.getTypeDescriptor()); + } + else if (op1 instanceof Long) { + newValue = new TypedValue(op1.longValue() + 1L, + operandTypedValue.getTypeDescriptor()); + } + else if (op1 instanceof Short) { + newValue = new TypedValue(op1.shortValue() + (short) 1, + operandTypedValue.getTypeDescriptor()); + } + else { + newValue = new TypedValue(op1.intValue() + 1, + operandTypedValue.getTypeDescriptor()); } } - if (newValue==null) { + if (newValue == null) { try { newValue = state.operate(Operation.ADD, returnValue.getValue(), 1); - } catch (SpelEvaluationException see) { - if (see.getMessageCode()==SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES) { + } + catch (SpelEvaluationException ex) { + if (ex.getMessageCode() == SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES) { // This means the operand is not incrementable - throw new SpelEvaluationException(operand.getStartPosition(),SpelMessage.OPERAND_NOT_INCREMENTABLE,operand.toStringAST()); - } else { - throw see; + throw new SpelEvaluationException(operand.getStartPosition(), + SpelMessage.OPERAND_NOT_INCREMENTABLE, operand.toStringAST()); } + throw ex; } } // set the name value try { lvalue.setValue(newValue.getValue()); - } catch (SpelEvaluationException see) { + } + catch (SpelEvaluationException see) { // if unable to set the value the operand is not writable (e.g. 1++ ) - if (see.getMessageCode()==SpelMessage.SETVALUE_NOT_SUPPORTED) { - throw new SpelEvaluationException(operand.getStartPosition(),SpelMessage.OPERAND_NOT_INCREMENTABLE); - } else { + if (see.getMessageCode() == SpelMessage.SETVALUE_NOT_SUPPORTED) { + throw new SpelEvaluationException(operand.getStartPosition(), + SpelMessage.OPERAND_NOT_INCREMENTABLE); + } + else { throw see; } } - if (!postfix) { + if (!this.postfix) { // the return value is the new value, not the original value returnValue = newValue; } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpLE.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpLE.java index a09c7c7fb7..47424db204 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpLE.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpLE.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,8 +32,10 @@ public class OpLE extends Operator { super("<=", pos, operands); } + @Override - public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException { + public BooleanTypedValue getValueInternal(ExpressionState state) + throws EvaluationException { Object left = getLeftOperand().getValueInternal(state).getValue(); Object right = getRightOperand().getValueInternal(state).getValue(); if (left instanceof Number && right instanceof Number) { @@ -41,15 +43,18 @@ public class OpLE extends Operator { Number rightNumber = (Number) right; if (leftNumber instanceof Double || rightNumber instanceof Double) { return BooleanTypedValue.forValue(leftNumber.doubleValue() <= rightNumber.doubleValue()); - } else if (leftNumber instanceof Float || rightNumber instanceof Float) { + } + else if (leftNumber instanceof Float || rightNumber instanceof Float) { return BooleanTypedValue.forValue(leftNumber.floatValue() <= rightNumber.floatValue()); - } else if (leftNumber instanceof Long || rightNumber instanceof Long) { + } + else if (leftNumber instanceof Long || rightNumber instanceof Long) { return BooleanTypedValue.forValue(leftNumber.longValue() <= rightNumber.longValue()); - } else { + } + else { return BooleanTypedValue.forValue(leftNumber.intValue() <= rightNumber.intValue()); } } - return BooleanTypedValue.forValue( state.getTypeComparator().compare(left, right) <= 0); + return BooleanTypedValue.forValue(state.getTypeComparator().compare(left, right) <= 0); } } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpLT.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpLT.java index 0f0e12d045..dc8916dda8 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpLT.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpLT.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,8 +32,10 @@ public class OpLT extends Operator { super("<", pos, operands); } + @Override - public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException { + public BooleanTypedValue getValueInternal(ExpressionState state) + throws EvaluationException { Object left = getLeftOperand().getValueInternal(state).getValue(); Object right = getRightOperand().getValueInternal(state).getValue(); // TODO could leave all of these to the comparator - just seems quicker to do some here @@ -42,11 +44,14 @@ public class OpLT extends Operator { Number rightNumber = (Number) right; if (leftNumber instanceof Double || rightNumber instanceof Double) { return BooleanTypedValue.forValue(leftNumber.doubleValue() < rightNumber.doubleValue()); - } else if (leftNumber instanceof Float || rightNumber instanceof Float) { + } + else if (leftNumber instanceof Float || rightNumber instanceof Float) { return BooleanTypedValue.forValue(leftNumber.floatValue() < rightNumber.floatValue()); - } else if (leftNumber instanceof Long || rightNumber instanceof Long) { + } + else if (leftNumber instanceof Long || rightNumber instanceof Long) { return BooleanTypedValue.forValue(leftNumber.longValue() < rightNumber.longValue()); - } else { + } + else { return BooleanTypedValue.forValue(leftNumber.intValue() < rightNumber.intValue()); } } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpMinus.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpMinus.java index 1afb3b28c4..143dfd9014 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpMinus.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpMinus.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,11 +27,12 @@ import org.springframework.expression.spel.ExpressionState; *
      • subtraction of doubles (floats are represented as doubles) *
      • subtraction of longs *
      • subtraction of integers - *
      • subtraction of an int from a string of one character (effectively decreasing that character), so 'd'-3='a' + *
      • subtraction of an int from a string of one character (effectively decreasing that + * character), so 'd'-3='a' * - * It can be used as a unary operator for numbers (double/long/int). The standard promotions are performed - * when the operand types vary (double-int=double). - * For other options it defers to the registered overloader. + * It can be used as a unary operator for numbers (double/long/int). The standard + * promotions are performed when the operand types vary (double-int=double). For other + * options it defers to the registered overloader. * * @author Andy Clement * @since 3.0 @@ -44,46 +45,61 @@ public class OpMinus extends Operator { @Override public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { + SpelNodeImpl leftOp = getLeftOperand(); SpelNodeImpl rightOp = getRightOperand(); + if (rightOp == null) {// If only one operand, then this is unary minus Object operand = leftOp.getValueInternal(state).getValue(); if (operand instanceof Number) { Number n = (Number) operand; if (operand instanceof Double) { return new TypedValue(0 - n.doubleValue()); - } else if (operand instanceof Float) { + } + + if (operand instanceof Float) { return new TypedValue(0 - n.floatValue()); - } else if (operand instanceof Long) { + } + + if (operand instanceof Long) { return new TypedValue(0 - n.longValue()); - } else { - return new TypedValue(0 - n.intValue()); } + return new TypedValue(0 - n.intValue()); } + return state.operate(Operation.SUBTRACT, operand, null); - } else { - Object left = leftOp.getValueInternal(state).getValue(); - Object right = rightOp.getValueInternal(state).getValue(); - if (left instanceof Number && right instanceof Number) { - Number op1 = (Number) left; - Number op2 = (Number) right; - if (op1 instanceof Double || op2 instanceof Double) { - return new TypedValue(op1.doubleValue() - op2.doubleValue()); - } else if (op1 instanceof Float || op2 instanceof Float) { - return new TypedValue(op1.floatValue() - op2.floatValue()); - } else if (op1 instanceof Long || op2 instanceof Long) { - return new TypedValue(op1.longValue() - op2.longValue()); - } else { - return new TypedValue(op1.intValue() - op2.intValue()); - } - } else if (left instanceof String && right instanceof Integer && ((String)left).length()==1) { - String theString = (String) left; - Integer theInteger = (Integer) right; - // implements character - int (ie. b - 1 = a) - return new TypedValue(Character.toString((char) (theString.charAt(0) - theInteger))); - } - return state.operate(Operation.SUBTRACT, left, right); } + + Object left = leftOp.getValueInternal(state).getValue(); + Object right = rightOp.getValueInternal(state).getValue(); + + if (left instanceof Number && right instanceof Number) { + Number op1 = (Number) left; + Number op2 = (Number) right; + if (op1 instanceof Double || op2 instanceof Double) { + return new TypedValue(op1.doubleValue() - op2.doubleValue()); + } + + if (op1 instanceof Float || op2 instanceof Float) { + return new TypedValue(op1.floatValue() - op2.floatValue()); + } + + if (op1 instanceof Long || op2 instanceof Long) { + return new TypedValue(op1.longValue() - op2.longValue()); + } + + return new TypedValue(op1.intValue() - op2.intValue()); + } + else if (left instanceof String && right instanceof Integer + && ((String) left).length() == 1) { + String theString = (String) left; + Integer theInteger = (Integer) right; + // implements character - int (ie. b - 1 = a) + return new TypedValue(Character.toString((char) + (theString.charAt(0) - theInteger))); + } + + return state.operate(Operation.SUBTRACT, left, right); } @Override @@ -95,8 +111,8 @@ public class OpMinus extends Operator { } @Override public SpelNodeImpl getRightOperand() { - if (children.length<2) {return null;} - return children[1]; + if (this.children.length<2) {return null;} + return this.children[1]; } } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpModulus.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpModulus.java index 559c43b55e..48b511229d 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpModulus.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpModulus.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,6 +33,7 @@ public class OpModulus extends Operator { super("%", pos, operands); } + @Override public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { Object operandOne = getLeftOperand().getValueInternal(state).getValue(); @@ -42,11 +43,14 @@ public class OpModulus extends Operator { Number op2 = (Number) operandTwo; if (op1 instanceof Double || op2 instanceof Double) { return new TypedValue(op1.doubleValue() % op2.doubleValue()); - } else if (op1 instanceof Float || op2 instanceof Float) { + } + else if (op1 instanceof Float || op2 instanceof Float) { return new TypedValue(op1.floatValue() % op2.floatValue()); - } else if (op1 instanceof Long || op2 instanceof Long) { + } + else if (op1 instanceof Long || op2 instanceof Long) { return new TypedValue(op1.longValue() % op2.longValue()); - } else { + } + else { return new TypedValue(op1.intValue() % op2.intValue()); } } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpMultiply.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpMultiply.java index 42b0259416..e795dff8b8 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpMultiply.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpMultiply.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,10 +25,11 @@ import org.springframework.expression.spel.ExpressionState; * Implements the {@code multiply} operator. * *

        Conversions and promotions are handled as defined in - * Section 5.6.2 - * of the Java Language Specification: + * Section + * 5.6.2 of the Java Language Specification: * - *

        If any of the operands is of a reference type, unboxing conversion (Section 5.1.8) is performed. Then:
        + *

        If any of the operands is of a reference type, unboxing conversion (Section 5.1.8) + * is performed. Then:
        * If either operand is of type double, the other is converted to double.
        * Otherwise, if either operand is of type float, the other is converted to float.
        * Otherwise, if either operand is of type long, the other is converted to long.
        @@ -44,6 +45,7 @@ public class OpMultiply extends Operator { super("*", pos, operands); } + /** * Implements the {@code multiply} operator directly here for certain types * of supported operands and otherwise delegates to any registered overloader @@ -58,23 +60,27 @@ public class OpMultiply extends Operator { */ @Override public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { + Object operandOne = getLeftOperand().getValueInternal(state).getValue(); Object operandTwo = getRightOperand().getValueInternal(state).getValue(); + if (operandOne instanceof Number && operandTwo instanceof Number) { Number leftNumber = (Number) operandOne; Number rightNumber = (Number) operandTwo; if (leftNumber instanceof Double || rightNumber instanceof Double) { - return new TypedValue(leftNumber.doubleValue() * rightNumber.doubleValue()); + return new TypedValue(leftNumber.doubleValue() + * rightNumber.doubleValue()); } - else if (leftNumber instanceof Float || rightNumber instanceof Float) { + + if (leftNumber instanceof Float || rightNumber instanceof Float) { return new TypedValue(leftNumber.floatValue() * rightNumber.floatValue()); } - else if (leftNumber instanceof Long || rightNumber instanceof Long) { + + if (leftNumber instanceof Long || rightNumber instanceof Long) { return new TypedValue(leftNumber.longValue() * rightNumber.longValue()); } - else { - return new TypedValue(leftNumber.intValue() * rightNumber.intValue()); - } + + return new TypedValue(leftNumber.intValue() * rightNumber.intValue()); } else if (operandOne instanceof String && operandTwo instanceof Integer) { int repeats = (Integer) operandTwo; @@ -84,6 +90,7 @@ public class OpMultiply extends Operator { } return new TypedValue(result.toString()); } + return state.operate(Operation.MULTIPLY, operandOne, operandTwo); } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpNE.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpNE.java index 558ccb9763..fdf65c24d7 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpNE.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpNE.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,29 +32,38 @@ public class OpNE extends Operator { super("!=", pos, operands); } + @Override public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException { + Object left = getLeftOperand().getValueInternal(state).getValue(); Object right = getRightOperand().getValueInternal(state).getValue(); + if (left instanceof Number && right instanceof Number) { Number op1 = (Number) left; Number op2 = (Number) right; + if (op1 instanceof Double || op2 instanceof Double) { return BooleanTypedValue.forValue(op1.doubleValue() != op2.doubleValue()); - } else if (op1 instanceof Float || op2 instanceof Float) { - return BooleanTypedValue.forValue(op1.floatValue() != op2.floatValue()); - } else if (op1 instanceof Long || op2 instanceof Long) { - return BooleanTypedValue.forValue(op1.longValue() != op2.longValue()); - } else { - return BooleanTypedValue.forValue(op1.intValue() != op2.intValue()); } + + if (op1 instanceof Float || op2 instanceof Float) { + return BooleanTypedValue.forValue(op1.floatValue() != op2.floatValue()); + } + + if (op1 instanceof Long || op2 instanceof Long) { + return BooleanTypedValue.forValue(op1.longValue() != op2.longValue()); + } + + return BooleanTypedValue.forValue(op1.intValue() != op2.intValue()); } - if (left!=null && (left instanceof Comparable)) { - return BooleanTypedValue.forValue(state.getTypeComparator().compare(left, right) != 0); - } else { - return BooleanTypedValue.forValue(left!=right); + if (left != null && (left instanceof Comparable)) { + return BooleanTypedValue.forValue(state.getTypeComparator().compare(left, + right) != 0); } + + return BooleanTypedValue.forValue(left != right); } } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpOr.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpOr.java index c4d3cd4cf4..ce21a1cb9a 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpOr.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpOr.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,6 +36,7 @@ public class OpOr extends Operator { super("or", pos, operands); } + @Override public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException { if (getBooleanValue(state, getLeftOperand())) { diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpPlus.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpPlus.java index 7443dd141c..53dad0ff2b 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpPlus.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpPlus.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,8 +32,9 @@ import org.springframework.util.Assert; *

      • add integers *
      • concatenate strings * - * It can be used as a unary operator for numbers (double/long/int). The standard promotions are performed - * when the operand types vary (double+int=double). For other options it defers to the registered overloader. + * It can be used as a unary operator for numbers (double/long/int). The standard + * promotions are performed when the operand types vary (double+int=double). For other + * options it defers to the registered overloader. * * @author Andy Clement * @author Ivo Smid @@ -46,72 +47,86 @@ public class OpPlus extends Operator { Assert.notEmpty(operands); } + @Override public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { SpelNodeImpl leftOp = getLeftOperand(); SpelNodeImpl rightOp = getRightOperand(); + if (rightOp == null) { // If only one operand, then this is unary plus Object operandOne = leftOp.getValueInternal(state).getValue(); if (operandOne instanceof Number) { if (operandOne instanceof Double || operandOne instanceof Long) { return new TypedValue(operandOne); - } else if (operandOne instanceof Float) { - return new TypedValue(((Number) operandOne).floatValue()); - } else { - return new TypedValue(((Number) operandOne).intValue()); } + if (operandOne instanceof Float) { + return new TypedValue(((Number) operandOne).floatValue()); + } + return new TypedValue(((Number) operandOne).intValue()); } return state.operate(Operation.ADD, operandOne, null); } - else { - final TypedValue operandOneValue = leftOp.getValueInternal(state); - final Object operandOne = operandOneValue.getValue(); - final TypedValue operandTwoValue = rightOp.getValueInternal(state); - final Object operandTwo = operandTwoValue.getValue(); + final TypedValue operandOneValue = leftOp.getValueInternal(state); + final Object operandOne = operandOneValue.getValue(); - if (operandOne instanceof Number && operandTwo instanceof Number) { - Number op1 = (Number) operandOne; - Number op2 = (Number) operandTwo; - if (op1 instanceof Double || op2 instanceof Double) { - return new TypedValue(op1.doubleValue() + op2.doubleValue()); - } else if (op1 instanceof Float || op2 instanceof Float) { - return new TypedValue(op1.floatValue() + op2.floatValue()); - } else if (op1 instanceof Long || op2 instanceof Long) { - return new TypedValue(op1.longValue() + op2.longValue()); - } else { // TODO what about overflow? - return new TypedValue(op1.intValue() + op2.intValue()); - } - } else if (operandOne instanceof String && operandTwo instanceof String) { - return new TypedValue(new StringBuilder((String) operandOne).append((String) operandTwo).toString()); - } else if (operandOne instanceof String) { - StringBuilder result = new StringBuilder((String) operandOne); - result.append((operandTwo == null ? "null" : convertTypedValueToString(operandTwoValue, state))); - return new TypedValue(result.toString()); - } else if (operandTwo instanceof String) { - StringBuilder result = new StringBuilder((operandOne == null ? "null" : convertTypedValueToString( - operandOneValue, state))); - result.append((String) operandTwo); - return new TypedValue(result.toString()); + final TypedValue operandTwoValue = rightOp.getValueInternal(state); + final Object operandTwo = operandTwoValue.getValue(); + + if (operandOne instanceof Number && operandTwo instanceof Number) { + Number op1 = (Number) operandOne; + Number op2 = (Number) operandTwo; + if (op1 instanceof Double || op2 instanceof Double) { + return new TypedValue(op1.doubleValue() + op2.doubleValue()); } - return state.operate(Operation.ADD, operandOne, operandTwo); + if (op1 instanceof Float || op2 instanceof Float) { + return new TypedValue(op1.floatValue() + op2.floatValue()); + } + if (op1 instanceof Long || op2 instanceof Long) { + return new TypedValue(op1.longValue() + op2.longValue()); + } + // TODO what about overflow? + return new TypedValue(op1.intValue() + op2.intValue()); } + + if (operandOne instanceof String && operandTwo instanceof String) { + return new TypedValue(new StringBuilder((String) operandOne).append( + (String) operandTwo).toString()); + } + + if (operandOne instanceof String) { + StringBuilder result = new StringBuilder((String) operandOne); + result.append((operandTwo == null ? "null" : convertTypedValueToString( + operandTwoValue, state))); + return new TypedValue(result.toString()); + } + + if (operandTwo instanceof String) { + StringBuilder result = new StringBuilder((operandOne == null ? "null" + : convertTypedValueToString(operandOneValue, state))); + result.append((String) operandTwo); + return new TypedValue(result.toString()); + } + + return state.operate(Operation.ADD, operandOne, operandTwo); } @Override public String toStringAST() { - if (children.length<2) { // unary plus + if (this.children.length<2) { // unary plus return new StringBuilder().append("+").append(getLeftOperand().toStringAST()).toString(); } + return super.toStringAST(); } @Override public SpelNodeImpl getRightOperand() { - if (children.length < 2) { + if (this.children.length < 2) { return null; } - return children[1]; + + return this.children[1]; } /** @@ -127,11 +142,12 @@ public class OpPlus extends Operator { final TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(String.class); if (typeConverter.canConvert(value.getTypeDescriptor(), typeDescriptor)) { - final Object obj = typeConverter.convertValue(value.getValue(), value.getTypeDescriptor(), typeDescriptor); + final Object obj = typeConverter.convertValue(value.getValue(), + value.getTypeDescriptor(), typeDescriptor); return String.valueOf(obj); - } else { - return String.valueOf(value.getValue()); } + + return String.valueOf(value.getValue()); } } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Operator.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Operator.java index 863f1a218f..637ba71185 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Operator.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Operator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,10 +16,10 @@ package org.springframework.expression.spel.ast; - /** - * Common supertype for operators that operate on either one or two operands. In the case of multiply or divide there - * would be two operands, but for unary plus or minus, there is only one. + * Common supertype for operators that operate on either one or two operands. In the case + * of multiply or divide there would be two operands, but for unary plus or minus, there + * is only one. * * @author Andy Clement * @since 3.0 @@ -28,21 +28,23 @@ public abstract class Operator extends SpelNodeImpl { String operatorName; + public Operator(String payload,int pos,SpelNodeImpl... operands) { super(pos, operands); this.operatorName = payload; } + public SpelNodeImpl getLeftOperand() { - return children[0]; + return this.children[0]; } public SpelNodeImpl getRightOperand() { - return children[1]; + return this.children[1]; } public final String getOperatorName() { - return operatorName; + return this.operatorName; } /** diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorBetween.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorBetween.java index d18695275e..7f38830d7a 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorBetween.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorBetween.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,9 +26,10 @@ import org.springframework.expression.spel.SpelMessage; import org.springframework.expression.spel.support.BooleanTypedValue; /** - * Represents the between operator. The left operand to between must be a single value and the right operand must be a - * list - this operator returns true if the left operand is between (using the registered comparator) the two elements - * in the list. The definition of between being inclusive follows the SQL BETWEEN definition. + * Represents the between operator. The left operand to between must be a single value and + * the right operand must be a list - this operator returns true if the left operand is + * between (using the registered comparator) the two elements in the list. The definition + * of between being inclusive follows the SQL BETWEEN definition. * * @author Andy Clement * @since 3.0 @@ -40,8 +41,9 @@ public class OperatorBetween extends Operator { } /** - * Returns a boolean based on whether a value is in the range expressed. The first operand is any value whilst the - * second is a list of two values - those two values being the bounds allowed for the first operand (inclusive). + * Returns a boolean based on whether a value is in the range expressed. The first + * operand is any value whilst the second is a list of two values - those two values + * being the bounds allowed for the first operand (inclusive). * @param state the expression state * @return true if the left operand is in the range specified, false otherwise * @throws EvaluationException if there is a problem evaluating the expression @@ -59,8 +61,10 @@ public class OperatorBetween extends Operator { Object high = l.get(1); TypeComparator comparator = state.getTypeComparator(); try { - return BooleanTypedValue.forValue((comparator.compare(left, low) >= 0 && comparator.compare(left, high) <= 0)); - } catch (SpelEvaluationException ex) { + return BooleanTypedValue.forValue((comparator.compare(left, low) >= 0 && + comparator.compare(left, high) <= 0)); + } + catch (SpelEvaluationException ex) { ex.setPosition(getStartPosition()); throw ex; } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorInstanceof.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorInstanceof.java index bf7d0edc91..2f50e86c82 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorInstanceof.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorInstanceof.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,8 +24,8 @@ import org.springframework.expression.spel.SpelMessage; import org.springframework.expression.spel.support.BooleanTypedValue; /** - * The operator 'instanceof' checks if an object is of the class specified in the right hand operand, - * in the same way that {@code instanceof} does in Java. + * The operator 'instanceof' checks if an object is of the class specified in the right + * hand operand, in the same way that {@code instanceof} does in Java. * * @author Andy Clement * @since 3.0 @@ -36,11 +36,13 @@ public class OperatorInstanceof extends Operator { super("instanceof", pos, operands); } + /** - * Compare the left operand to see it is an instance of the type specified as the right operand. - * The right operand must be a class. + * Compare the left operand to see it is an instance of the type specified as the + * right operand. The right operand must be a class. * @param state the expression state - * @return true if the left operand is an instanceof of the right operand, otherwise false + * @return true if the left operand is an instanceof of the right operand, otherwise + * false * @throws EvaluationException if there is a problem evaluating the expression */ @Override diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorMatches.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorMatches.java index 24b7ea46de..573c80de42 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorMatches.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorMatches.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,8 +27,9 @@ import org.springframework.expression.spel.SpelMessage; import org.springframework.expression.spel.support.BooleanTypedValue; /** - * Implements the matches operator. Matches takes two operands. The first is a string and the second is a java regex. It - * will return true when getValue() is called if the first operand matches the regex. + * Implements the matches operator. Matches takes two operands. The first is a string and + * the second is a java regex. It will return true when getValue() is called if the first + * operand matches the regex. * * @author Andy Clement * @since 3.0 @@ -39,11 +40,14 @@ public class OperatorMatches extends Operator { super("matches", pos, operands); } + /** * Check the first operand matches the regex specified as the second operand. * @param state the expression state - * @return true if the first operand matches the regex specified as the second operand, otherwise false - * @throws EvaluationException if there is a problem evaluating the expression (e.g. the regex is invalid) + * @return true if the first operand matches the regex specified as the second + * operand, otherwise false + * @throws EvaluationException if there is a problem evaluating the expression (e.g. + * the regex is invalid) */ @Override public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException { diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorNot.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorNot.java index 33ae9e0d97..3e5be7217a 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorNot.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorNot.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,18 +36,19 @@ public class OperatorNot extends SpelNodeImpl { // Not is a unary operator so do super(pos, operand); } + @Override public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException { try { - Boolean value = children[0].getValue(state, Boolean.class); + Boolean value = this.children[0].getValue(state, Boolean.class); if (value == null) { throw new SpelEvaluationException(SpelMessage.TYPE_CONVERSION_ERROR, "null", "boolean"); } return BooleanTypedValue.forValue(!value); } - catch (SpelEvaluationException see) { - see.setPosition(getChild(0).getStartPosition()); - throw see; + catch (SpelEvaluationException ex) { + ex.setPosition(getChild(0).getStartPosition()); + throw ex; } } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorPower.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorPower.java index 0c41e6a8dc..f05e1b7a88 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorPower.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorPower.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,6 +33,7 @@ public class OperatorPower extends Operator { super("^", pos, operands); } + @Override public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { SpelNodeImpl leftOp = getLeftOperand(); @@ -44,18 +45,22 @@ public class OperatorPower extends Operator { Number op1 = (Number) operandOne; Number op2 = (Number) operandTwo; if (op1 instanceof Double || op2 instanceof Double) { - return new TypedValue(Math.pow(op1.doubleValue(),op2.doubleValue())); - } else if (op1 instanceof Float || op2 instanceof Float) { + return new TypedValue(Math.pow(op1.doubleValue(), op2.doubleValue())); + } + else if (op1 instanceof Float || op2 instanceof Float) { return new TypedValue(Math.pow(op1.floatValue(), op2.floatValue())); - } else if (op1 instanceof Long || op2 instanceof Long) { - double d= Math.pow(op1.longValue(), op2.longValue()); - return new TypedValue((long)d); - } else { - double d= Math.pow(op1.longValue(), op2.longValue()); + } + else if (op1 instanceof Long || op2 instanceof Long) { + double d = Math.pow(op1.longValue(), op2.longValue()); + return new TypedValue((long) d); + } + else { + double d = Math.pow(op1.longValue(), op2.longValue()); if (d > Integer.MAX_VALUE) { - return new TypedValue((long)d); - } else { - return new TypedValue((int)d); + return new TypedValue((long) d); + } + else { + return new TypedValue((int) d); } } } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Projection.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Projection.java index 226063cf7c..d7fcb505d1 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Projection.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Projection.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,8 +32,8 @@ import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; /** - * Represents projection, where a given operation is performed on all elements in some input sequence, returning - * a new sequence of the same size. For example: + * Represents projection, where a given operation is performed on all elements in some + * input sequence, returning a new sequence of the same size. For example: * "{1,2,3,4,5,6,7,8,9,10}.!{#isEven(#this)}" returns "[n, y, n, y, n, y, n, y, n, y]" * * @author Andy Clement @@ -49,6 +49,7 @@ public class Projection extends SpelNodeImpl { this.nullSafe = nullSafe; } + @Override public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { return getValueRef(state).getValue(); @@ -81,7 +82,8 @@ public class Projection extends SpelNodeImpl { } return new ValueRef.TypedValueHolderValueRef(new TypedValue(result),this); // TODO unable to build correct type descriptor } - else if (operand instanceof Collection || operandIsArray) { + + if (operand instanceof Collection || operandIsArray) { Collection data = (operand instanceof Collection ? (Collection) operand : Arrays.asList(ObjectUtils.toObjectArray(operand))); List result = new ArrayList(); @@ -91,7 +93,7 @@ public class Projection extends SpelNodeImpl { try { state.pushActiveContextObject(new TypedValue(element)); state.enterScope("index", idx); - Object value = children[0].getValueInternal(state).getValue(); + Object value = this.children[0].getValueInternal(state).getValue(); if (value != null && operandIsArray) { arrayElementType = determineCommonType(arrayElementType, value.getClass()); } @@ -113,21 +115,17 @@ public class Projection extends SpelNodeImpl { } return new ValueRef.TypedValueHolderValueRef(new TypedValue(result),this); } - else { - if (operand==null) { - if (this.nullSafe) { - return ValueRef.NullValueRef.instance; - } - else { - throw new SpelEvaluationException(getStartPosition(), - SpelMessage.PROJECTION_NOT_SUPPORTED_ON_TYPE, "null"); - } - } - else { - throw new SpelEvaluationException(getStartPosition(), - SpelMessage.PROJECTION_NOT_SUPPORTED_ON_TYPE, operand.getClass().getName()); + + if (operand==null) { + if (this.nullSafe) { + return ValueRef.NullValueRef.instance; } + throw new SpelEvaluationException(getStartPosition(), + SpelMessage.PROJECTION_NOT_SUPPORTED_ON_TYPE, "null"); } + + throw new SpelEvaluationException(getStartPosition(), + SpelMessage.PROJECTION_NOT_SUPPORTED_ON_TYPE, operand.getClass().getName()); } @Override diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java index 043a57e6fd..93c16bf3e4 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -68,10 +68,10 @@ public class PropertyOrFieldReference extends SpelNodeImpl { static class AccessorLValue implements ValueRef { - private PropertyOrFieldReference ref; - private TypedValue contextObject; - private EvaluationContext eContext; - private boolean isAutoGrowNullReferences; + private final PropertyOrFieldReference ref; + private final TypedValue contextObject; + private final EvaluationContext eContext; + private final boolean isAutoGrowNullReferences; public AccessorLValue( PropertyOrFieldReference propertyOrFieldReference, @@ -85,12 +85,12 @@ public class PropertyOrFieldReference extends SpelNodeImpl { @Override public TypedValue getValue() { - return ref.getValueInternal(contextObject,eContext,isAutoGrowNullReferences); + return this.ref.getValueInternal(this.contextObject,this.eContext,this.isAutoGrowNullReferences); } @Override public void setValue(Object newValue) { - ref.writeProperty(contextObject,eContext, ref.name, newValue); + this.ref.writeProperty(this.contextObject,this.eContext, this.ref.name, newValue); } @Override @@ -142,7 +142,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl { try { if (isWritableProperty(this.name,contextObject,eContext)) { Map newMap = HashMap.class.newInstance(); - writeProperty(contextObject, eContext, name, newMap); + writeProperty(contextObject, eContext, this.name, newMap); result = readProperty(contextObject, eContext, this.name); } } @@ -161,7 +161,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl { try { if (isWritableProperty(this.name,contextObject,eContext)) { Object newObject = result.getTypeDescriptor().getType().newInstance(); - writeProperty(contextObject, eContext, name, newObject); + writeProperty(contextObject, eContext, this.name, newObject); result = readProperty(contextObject, eContext, this.name); } } @@ -253,7 +253,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl { private void writeProperty(TypedValue contextObject, EvaluationContext eContext, String name, Object newValue) throws SpelEvaluationException { - if (contextObject.getValue() == null && nullSafe) { + if (contextObject.getValue() == null && this.nullSafe) { return; } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/QualifiedIdentifier.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/QualifiedIdentifier.java index 427e33b767..99b4b13f53 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/QualifiedIdentifier.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/QualifiedIdentifier.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,8 @@ import org.springframework.expression.TypedValue; import org.springframework.expression.spel.ExpressionState; /** - * Represents a dot separated sequence of strings that indicate a package qualified type reference. + * Represents a dot separated sequence of strings that indicate a package qualified type + * reference. * *

        Example: "java.lang.String" as in the expression "new java.lang.String('hello')" * @@ -33,17 +34,19 @@ public class QualifiedIdentifier extends SpelNodeImpl { // TODO safe to cache? dont think so private TypedValue value; + public QualifiedIdentifier(int pos,SpelNodeImpl... operands) { super(pos,operands); } + @Override public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { // Cache the concatenation of child identifiers if (this.value == null) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < getChildCount(); i++) { - Object value = children[i].getValueInternal(state).getValue(); + Object value = this.children[i].getValueInternal(state).getValue(); if (i > 0 && !value.toString().startsWith("$")) { sb.append("."); } @@ -59,7 +62,8 @@ public class QualifiedIdentifier extends SpelNodeImpl { StringBuilder sb = new StringBuilder(); if (this.value != null) { sb.append(this.value.getValue()); - } else { + } + else { for (int i = 0; i < getChildCount(); i++) { if (i > 0) { sb.append("."); diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/RealLiteral.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/RealLiteral.java index df2c110319..2a7f3d8744 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/RealLiteral.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/RealLiteral.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,8 @@ package org.springframework.expression.spel.ast; import org.springframework.expression.TypedValue; /** + * Expression language AST node that represents a real literal. + * * @author Andy Clement * @since 3.0 */ @@ -26,11 +28,13 @@ public class RealLiteral extends Literal { private final TypedValue value; + public RealLiteral(String payload, int pos, double value) { super(payload, pos); this.value = new TypedValue(value); } + @Override public TypedValue getLiteralValue() { return this.value; diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Selection.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Selection.java index 710e975921..dfb838042f 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Selection.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Selection.java @@ -47,14 +47,18 @@ import org.springframework.util.ObjectUtils; */ public class Selection extends SpelNodeImpl { - public final static int ALL = 0; // ?[] - public final static int FIRST = 1; // ^[] - public final static int LAST = 2; // $[] + public static final int ALL = 0; // ?[] + + public static final int FIRST = 1; // ^[] + + public static final int LAST = 2; // $[] private final int variant; + private final boolean nullSafe; - public Selection(boolean nullSafe, int variant,int pos,SpelNodeImpl expression) { + + public Selection(boolean nullSafe, int variant, int pos, SpelNodeImpl expression) { super(pos, expression != null ? new SpelNodeImpl[] { expression } : new SpelNodeImpl[] {}); Assert.notNull(expression, "Expression must not be null"); @@ -72,7 +76,7 @@ public class Selection extends SpelNodeImpl { TypedValue op = state.getActiveContextObject(); Object operand = op.getValue(); - SpelNodeImpl selectionCriteria = children[0]; + SpelNodeImpl selectionCriteria = this.children[0]; if (operand instanceof Map) { Map mapdata = (Map) operand; // TODO don't lose generic info for the new map @@ -85,32 +89,38 @@ public class Selection extends SpelNodeImpl { Object o = selectionCriteria.getValueInternal(state).getValue(); if (o instanceof Boolean) { if (((Boolean) o).booleanValue() == true) { - if (variant == FIRST) { + if (this.variant == FIRST) { result.put(entry.getKey(),entry.getValue()); return new ValueRef.TypedValueHolderValueRef(new TypedValue(result),this); } result.put(entry.getKey(),entry.getValue()); lastKey = entry.getKey(); } - } else { + } + else { throw new SpelEvaluationException(selectionCriteria.getStartPosition(), SpelMessage.RESULT_OF_SELECTION_CRITERIA_IS_NOT_BOOLEAN);// ,selectionCriteria.stringifyAST()); } - } finally { + } + finally { state.popActiveContextObject(); } } - if ((variant == FIRST || variant == LAST) && result.size() == 0) { + if ((this.variant == FIRST || this.variant == LAST) && result.size() == 0) { return new ValueRef.TypedValueHolderValueRef(new TypedValue(null),this); } - if (variant == LAST) { + + if (this.variant == LAST) { Map resultMap = new HashMap(); Object lastValue = result.get(lastKey); resultMap.put(lastKey,lastValue); return new ValueRef.TypedValueHolderValueRef(new TypedValue(resultMap),this); } + return new ValueRef.TypedValueHolderValueRef(new TypedValue(result),this); - } else if ((operand instanceof Collection) || ObjectUtils.isArray(operand)) { + } + + if ((operand instanceof Collection) || ObjectUtils.isArray(operand)) { List data = new ArrayList(); Collection c = (operand instanceof Collection) ? (Collection) operand : Arrays.asList(ObjectUtils.toObjectArray(operand)); @@ -124,64 +134,64 @@ public class Selection extends SpelNodeImpl { Object o = selectionCriteria.getValueInternal(state).getValue(); if (o instanceof Boolean) { if (((Boolean) o).booleanValue() == true) { - if (variant == FIRST) { + if (this.variant == FIRST) { return new ValueRef.TypedValueHolderValueRef(new TypedValue(element),this); } result.add(element); } - } else { + } + else { throw new SpelEvaluationException(selectionCriteria.getStartPosition(), SpelMessage.RESULT_OF_SELECTION_CRITERIA_IS_NOT_BOOLEAN);// ,selectionCriteria.stringifyAST()); } idx++; - } finally { + } + finally { state.exitScope(); state.popActiveContextObject(); } } - if ((variant == FIRST || variant == LAST) && result.size() == 0) { + + if ((this.variant == FIRST || this.variant == LAST) && result.size() == 0) { return ValueRef.NullValueRef.instance; } - if (variant == LAST) { + + if (this.variant == LAST) { return new ValueRef.TypedValueHolderValueRef(new TypedValue(result.get(result.size() - 1)),this); } + if (operand instanceof Collection) { return new ValueRef.TypedValueHolderValueRef(new TypedValue(result),this); } - else { - Class elementType = ClassUtils.resolvePrimitiveIfNecessary(op.getTypeDescriptor().getElementTypeDescriptor().getType()); - Object resultArray = Array.newInstance(elementType, result.size()); - System.arraycopy(result.toArray(), 0, resultArray, 0, result.size()); - return new ValueRef.TypedValueHolderValueRef(new TypedValue(resultArray),this); - } - } else { - if (operand==null) { - if (nullSafe) { - return ValueRef.NullValueRef.instance; - } else { - throw new SpelEvaluationException(getStartPosition(), SpelMessage.INVALID_TYPE_FOR_SELECTION, - "null"); - } - } else { - throw new SpelEvaluationException(getStartPosition(), SpelMessage.INVALID_TYPE_FOR_SELECTION, - operand.getClass().getName()); - } + Class elementType = ClassUtils.resolvePrimitiveIfNecessary(op.getTypeDescriptor().getElementTypeDescriptor().getType()); + Object resultArray = Array.newInstance(elementType, result.size()); + System.arraycopy(result.toArray(), 0, resultArray, 0, result.size()); + return new ValueRef.TypedValueHolderValueRef(new TypedValue(resultArray),this); } + if (operand==null) { + if (this.nullSafe) { + return ValueRef.NullValueRef.instance; + } + throw new SpelEvaluationException(getStartPosition(), + SpelMessage.INVALID_TYPE_FOR_SELECTION, "null"); + } + throw new SpelEvaluationException(getStartPosition(), + SpelMessage.INVALID_TYPE_FOR_SELECTION, operand.getClass().getName()); } @Override public String toStringAST() { StringBuilder sb = new StringBuilder(); - switch (variant) { - case ALL: - sb.append("?["); - break; - case FIRST: - sb.append("^["); - break; - case LAST: - sb.append("$["); - break; + switch (this.variant) { + case ALL: + sb.append("?["); + break; + case FIRST: + sb.append("^["); + break; + case LAST: + sb.append("$["); + break; } return sb.append(getChild(0).toStringAST()).append("]").toString(); } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java index 7964824a3c..f913035d0c 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,8 @@ import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.util.Assert; /** - * The common supertype of all AST nodes in a parsed Spring Expression Language format expression. + * The common supertype of all AST nodes in a parsed Spring Expression Language format + * expression. * * @author Andy Clement * @since 3.0 @@ -37,9 +38,12 @@ public abstract class SpelNodeImpl implements SpelNode { private static SpelNodeImpl[] NO_CHILDREN = new SpelNodeImpl[0]; protected int pos; // start = top 16bits, end = bottom 16bits + protected SpelNodeImpl[] children = SpelNodeImpl.NO_CHILDREN; + private SpelNodeImpl parent; + public SpelNodeImpl(int pos, SpelNodeImpl... operands) { this.pos = pos; // pos combines start and end so can never be zero because tokens cannot be zero length @@ -52,11 +56,14 @@ public abstract class SpelNodeImpl implements SpelNode { } } + protected SpelNodeImpl getPreviousChild() { SpelNodeImpl result = null; - if (parent != null) { - for (SpelNodeImpl child : parent.children) { - if (this==child) break; + if (this.parent != null) { + for (SpelNodeImpl child : this.parent.children) { + if (this==child) { + break; + } result = child; } } @@ -67,21 +74,22 @@ public abstract class SpelNodeImpl implements SpelNode { * @return true if the next child is one of the specified classes */ protected boolean nextChildIs(Class... clazzes) { - if (parent!=null) { - SpelNodeImpl[] peers = parent.children; - for (int i=0,max=peers.length;i=max) { - return false; - } else { - Class clazz = peers[i+1].getClass(); - for (Class desiredClazz: clazzes) { - if (clazz.equals(desiredClazz)) { - return true; - } - } + if (this.parent != null) { + SpelNodeImpl[] peers = this.parent.children; + for (int i = 0, max = peers.length; i < max; i++) { + if (peers[i] == this) { + if ((i + 1) >= max) { return false; } + + Class clazz = peers[i + 1].getClass(); + for (Class desiredClazz : clazzes) { + if (clazz.equals(desiredClazz)) { + return true; + } + } + + return false; } } } @@ -92,7 +100,8 @@ public abstract class SpelNodeImpl implements SpelNode { public final Object getValue(ExpressionState expressionState) throws EvaluationException { if (expressionState != null) { return getValueInternal(expressionState).getValue(); - } else { + } + else { // configuration not set - does that matter? return getValue(new ExpressionState(new StandardEvaluationContext())); } @@ -102,7 +111,8 @@ public abstract class SpelNodeImpl implements SpelNode { public final TypedValue getTypedValue(ExpressionState expressionState) throws EvaluationException { if (expressionState != null) { return getValueInternal(expressionState); - } else { + } + else { // configuration not set - does that matter? return getTypedValue(new ExpressionState(new StandardEvaluationContext())); } @@ -116,17 +126,18 @@ public abstract class SpelNodeImpl implements SpelNode { @Override public void setValue(ExpressionState expressionState, Object newValue) throws EvaluationException { - throw new SpelEvaluationException(getStartPosition(), SpelMessage.SETVALUE_NOT_SUPPORTED, getClass()); + throw new SpelEvaluationException(getStartPosition(), + SpelMessage.SETVALUE_NOT_SUPPORTED, getClass()); } @Override public SpelNode getChild(int index) { - return children[index]; + return this.children[index]; } @Override public int getChildCount() { - return children.length; + return this.children.length; } @Override @@ -148,15 +159,15 @@ public abstract class SpelNodeImpl implements SpelNode { @Override public int getStartPosition() { - return (pos>>16); + return (this.pos>>16); } @Override public int getEndPosition() { - return (pos&0xffff); + return (this.pos&0xffff); } protected ValueRef getValueRef(ExpressionState state) throws EvaluationException { - throw new SpelEvaluationException(pos,SpelMessage.NOT_ASSIGNABLE,toStringAST()); + throw new SpelEvaluationException(this.pos,SpelMessage.NOT_ASSIGNABLE,toStringAST()); } } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/StringLiteral.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/StringLiteral.java index efcd829726..3755a86284 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/StringLiteral.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/StringLiteral.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,8 @@ package org.springframework.expression.spel.ast; import org.springframework.expression.TypedValue; /** + * Expression language AST node that represents a string literal. + * * @author Andy Clement * @author Juergen Hoeller * @since 3.0 @@ -27,6 +29,7 @@ public class StringLiteral extends Literal { private final TypedValue value; + public StringLiteral(String payload, int pos, String value) { super(payload,pos); // TODO should these have been skipped being created by the parser rules? or not? @@ -34,6 +37,7 @@ public class StringLiteral extends Literal { this.value = new TypedValue(value.replaceAll("''", "'").replaceAll("\"\"", "\"")); } + @Override public TypedValue getLiteralValue() { return this.value; diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Ternary.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Ternary.java index 3ee71fc88f..c637adfb86 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Ternary.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Ternary.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,22 +37,24 @@ public class Ternary extends SpelNodeImpl { } /** - * Evaluate the condition and if true evaluate the first alternative, otherwise evaluate the second alternative. + * Evaluate the condition and if true evaluate the first alternative, otherwise + * evaluate the second alternative. * @param state the expression state - * @throws EvaluationException if the condition does not evaluate correctly to a boolean or there is a problem - * executing the chosen alternative + * @throws EvaluationException if the condition does not evaluate correctly to a + * boolean or there is a problem executing the chosen alternative */ @Override public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { - Boolean value = children[0].getValue(state, Boolean.class); + Boolean value = this.children[0].getValue(state, Boolean.class); if (value == null) { throw new SpelEvaluationException(getChild(0).getStartPosition(), SpelMessage.TYPE_CONVERSION_ERROR, "null", "boolean"); } if (value.booleanValue()) { - return children[1].getValueInternal(state); - } else { - return children[2].getValueInternal(state); + return this.children[1].getValueInternal(state); + } + else { + return this.children[2].getValueInternal(state); } } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/TypeCode.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/TypeCode.java index e8a2ac231a..6a477af880 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/TypeCode.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/TypeCode.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,26 +16,45 @@ package org.springframework.expression.spel.ast; /** - * Captures primitive types and their corresponding class objects, plus one special entry that represents all reference - * (non-primitive) types. + * Captures primitive types and their corresponding class objects, plus one special entry + * that represents all reference (non-primitive) types. * * @author Andy Clement */ public enum TypeCode { - OBJECT(Object.class), BOOLEAN(Boolean.TYPE), BYTE(Byte.TYPE), CHAR(Character.TYPE), // - SHORT(Short.TYPE), INT(Integer.TYPE), LONG(Long.TYPE), FLOAT(Float.TYPE), DOUBLE(Double.TYPE); + OBJECT(Object.class), + + BOOLEAN(Boolean.TYPE), + + BYTE(Byte.TYPE), + + CHAR(Character.TYPE), + + SHORT(Short.TYPE), + + INT(Integer.TYPE), + + LONG(Long.TYPE), + + FLOAT(Float.TYPE), + + DOUBLE(Double.TYPE); + private Class type; + TypeCode(Class type) { this.type = type; } + public Class getType() { - return type; + return this.type; } + public static TypeCode forName(String name) { String searchingFor = name.toUpperCase(); TypeCode[] tcs = values(); diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/TypeReference.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/TypeReference.java index 9f9bb0af59..9471486338 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/TypeReference.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/TypeReference.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,21 +29,23 @@ import org.springframework.expression.spel.ExpressionState; */ public class TypeReference extends SpelNodeImpl { - private int dimensions; + private final int dimensions; - public TypeReference(int pos,SpelNodeImpl qualifiedId) { + + public TypeReference(int pos, SpelNodeImpl qualifiedId) { this(pos,qualifiedId,0); } - public TypeReference(int pos,SpelNodeImpl qualifiedId,int dims) { + public TypeReference(int pos, SpelNodeImpl qualifiedId, int dims) { super(pos,qualifiedId); this.dimensions = dims; } + @Override public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { // TODO possible optimization here if we cache the discovered type reference, but can we do that? - String typename = (String) children[0].getValueInternal(state).getValue(); + String typename = (String) this.children[0].getValueInternal(state).getValue(); if (typename.indexOf(".") == -1 && Character.isLowerCase(typename.charAt(0))) { TypeCode tc = TypeCode.valueOf(typename.toUpperCase()); if (tc != TypeCode.OBJECT) { @@ -59,8 +61,8 @@ public class TypeReference extends SpelNodeImpl { } private Class makeArrayIfNecessary(Class clazz) { - if (dimensions!=0) { - for (int i=0;i constructedNodes = new Stack(); + private final Stack constructedNodes = new Stack(); - private SpelParserConfiguration configuration; + private final SpelParserConfiguration configuration; /** @@ -77,16 +78,16 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { this.expressionString = expressionString; Tokenizer tokenizer = new Tokenizer(expressionString); tokenizer.process(); - tokenStream = tokenizer.getTokens(); - tokenStreamLength = tokenStream.size(); - tokenStreamPointer = 0; - constructedNodes.clear(); + this.tokenStream = tokenizer.getTokens(); + this.tokenStreamLength = this.tokenStream.size(); + this.tokenStreamPointer = 0; + this.constructedNodes.clear(); SpelNodeImpl ast = eatExpression(); if (moreTokens()) { throw new SpelParseException(peekToken().startpos,SpelMessage.MORE_INPUT,toString(nextToken())); } - Assert.isTrue(constructedNodes.isEmpty()); - return new SpelExpression(expressionString, ast, configuration); + Assert.isTrue(this.constructedNodes.isEmpty()); + return new SpelExpression(expressionString, ast, this.configuration); } catch (InternalParseException ipe) { throw ipe.getCause(); @@ -110,7 +111,9 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { nextToken(); SpelNodeImpl assignedValue = eatLogicalOrExpression(); return new Assign(toPos(t),expr,assignedValue); - } else if (t.kind==TokenKind.ELVIS) { // a?:b (a if it isn't null, otherwise b) + } + + if (t.kind==TokenKind.ELVIS) { // a?:b (a if it isn't null, otherwise b) if (expr==null) { expr = new NullLiteral(toPos(t.startpos-1,t.endpos-2)); } @@ -120,7 +123,9 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { valueIfNull = new NullLiteral(toPos(t.startpos+1,t.endpos+1)); } return new Elvis(toPos(t),expr,valueIfNull); - } else if (t.kind==TokenKind.QMARK) { // a?b:c + } + + if (t.kind==TokenKind.QMARK) { // a?b:c if (expr==null) { expr = new NullLiteral(toPos(t.startpos-1,t.endpos-1)); } @@ -167,31 +172,38 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { SpelNodeImpl rhExpr = eatSumExpression(); checkOperands(t,expr,rhExpr); TokenKind tk = relationalOperatorToken.kind; + if (relationalOperatorToken.isNumericRelationalOperator()) { int pos = toPos(t); - if (tk==TokenKind.GT) { - return new OpGT(pos,expr,rhExpr); - } else if (tk==TokenKind.LT) { - return new OpLT(pos,expr,rhExpr); - } else if (tk==TokenKind.LE) { - return new OpLE(pos,expr,rhExpr); - } else if (tk==TokenKind.GE) { - return new OpGE(pos,expr,rhExpr); - } else if (tk == TokenKind.EQ) { - return new OpEQ(pos,expr,rhExpr); - } else { - Assert.isTrue(tk == TokenKind.NE); - return new OpNE(pos,expr,rhExpr); + if (tk == TokenKind.GT) { + return new OpGT(pos, expr, rhExpr); } + if (tk == TokenKind.LT) { + return new OpLT(pos, expr, rhExpr); + } + if (tk == TokenKind.LE) { + return new OpLE(pos, expr, rhExpr); + } + if (tk == TokenKind.GE) { + return new OpGE(pos, expr, rhExpr); + } + if (tk == TokenKind.EQ) { + return new OpEQ(pos, expr, rhExpr); + } + Assert.isTrue(tk == TokenKind.NE); + return new OpNE(pos, expr, rhExpr); } - if (tk==TokenKind.INSTANCEOF) { - return new OperatorInstanceof(toPos(t),expr,rhExpr); - } else if (tk==TokenKind.MATCHES) { - return new OperatorMatches(toPos(t),expr,rhExpr); - } else { - Assert.isTrue(tk==TokenKind.BETWEEN); - return new org.springframework.expression.spel.ast.OperatorBetween(toPos(t),expr,rhExpr); + + if (tk == TokenKind.INSTANCEOF) { + return new OperatorInstanceof(toPos(t), expr, rhExpr); } + + if (tk == TokenKind.MATCHES) { + return new OperatorMatches(toPos(t), expr, rhExpr); + } + + Assert.isTrue(tk == TokenKind.BETWEEN); + return new OperatorBetween(toPos(t), expr, rhExpr); } return expr; } @@ -199,14 +211,15 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { //sumExpression: productExpression ( (PLUS^ | MINUS^) productExpression)*; private SpelNodeImpl eatSumExpression() { SpelNodeImpl expr = eatProductExpression(); - while (peekToken(TokenKind.PLUS,TokenKind.MINUS,TokenKind.INC)) { + while (peekToken(TokenKind.PLUS, TokenKind.MINUS, TokenKind.INC)) { Token t = nextToken();//consume PLUS or MINUS or INC SpelNodeImpl rhExpr = eatProductExpression(); checkRightOperand(t,rhExpr); - if (t.kind==TokenKind.PLUS) { - expr = new OpPlus(toPos(t),expr,rhExpr); - } else if (t.kind==TokenKind.MINUS) { - expr = new OpMinus(toPos(t),expr,rhExpr); + if (t.kind == TokenKind.PLUS) { + expr = new OpPlus(toPos(t), expr, rhExpr); + } + else if (t.kind == TokenKind.MINUS) { + expr = new OpMinus(toPos(t), expr, rhExpr); } } return expr; @@ -215,17 +228,19 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { // productExpression: powerExpr ((STAR^ | DIV^| MOD^) powerExpr)* ; private SpelNodeImpl eatProductExpression() { SpelNodeImpl expr = eatPowerIncDecExpression(); - while (peekToken(TokenKind.STAR,TokenKind.DIV,TokenKind.MOD)) { + while (peekToken(TokenKind.STAR, TokenKind.DIV, TokenKind.MOD)) { Token t = nextToken(); // consume STAR/DIV/MOD SpelNodeImpl rhExpr = eatPowerIncDecExpression(); checkOperands(t,expr,rhExpr); - if (t.kind==TokenKind.STAR) { - expr = new OpMultiply(toPos(t),expr,rhExpr); - } else if (t.kind==TokenKind.DIV) { - expr = new OpDivide(toPos(t),expr,rhExpr); - } else { - Assert.isTrue(t.kind==TokenKind.MOD); - expr = new OpModulus(toPos(t),expr,rhExpr); + if (t.kind == TokenKind.STAR) { + expr = new OpMultiply(toPos(t), expr, rhExpr); + } + else if (t.kind == TokenKind.DIV) { + expr = new OpDivide(toPos(t), expr, rhExpr); + } + else { + Assert.isTrue(t.kind == TokenKind.MOD); + expr = new OpModulus(toPos(t), expr, rhExpr); } } return expr; @@ -239,41 +254,45 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { SpelNodeImpl rhExpr = eatUnaryExpression(); checkRightOperand(t,rhExpr); return new OperatorPower(toPos(t),expr, rhExpr); - } else if (expr!=null && peekToken(TokenKind.INC,TokenKind.DEC)) { + } + + if (expr!=null && peekToken(TokenKind.INC,TokenKind.DEC)) { Token t = nextToken();//consume INC/DEC if (t.getKind()==TokenKind.INC) { return new OpInc(toPos(t),true,expr); - } else { - return new OpDec(toPos(t),true,expr); } + return new OpDec(toPos(t),true,expr); } + return expr; } // unaryExpression: (PLUS^ | MINUS^ | BANG^ | INC^ | DEC^) unaryExpression | primaryExpression ; private SpelNodeImpl eatUnaryExpression() { - if (peekToken(TokenKind.PLUS,TokenKind.MINUS,TokenKind.NOT)) { + if (peekToken(TokenKind.PLUS, TokenKind.MINUS, TokenKind.NOT)) { Token t = nextToken(); SpelNodeImpl expr = eatUnaryExpression(); - if (t.kind==TokenKind.NOT) { - return new OperatorNot(toPos(t),expr); - } else if (t.kind==TokenKind.PLUS) { - return new OpPlus(toPos(t),expr); - } else { - Assert.isTrue(t.kind==TokenKind.MINUS); - return new OpMinus(toPos(t),expr); + if (t.kind == TokenKind.NOT) { + return new OperatorNot(toPos(t), expr); } - } else if (peekToken(TokenKind.INC,TokenKind.DEC)) { - Token t = nextToken(); - SpelNodeImpl expr = eatUnaryExpression(); - if (t.getKind()==TokenKind.INC) { - return new OpInc(toPos(t),false,expr); - } else { - return new OpDec(toPos(t),false,expr); + + if (t.kind == TokenKind.PLUS) { + return new OpPlus(toPos(t), expr); } - } else { - return eatPrimaryExpression(); + Assert.isTrue(t.kind == TokenKind.MINUS); + return new OpMinus(toPos(t), expr); + } + if (peekToken(TokenKind.INC, TokenKind.DEC)) { + Token t = nextToken(); + SpelNodeImpl expr = eatUnaryExpression(); + if (t.getKind() == TokenKind.INC) { + return new OpInc(toPos(t), false, expr); + } + return new OpDec(toPos(t), false, expr); + } + + return eatPrimaryExpression(); } // primaryExpression : startNode (node)? -> ^(EXPRESSION startNode (node)?); @@ -284,11 +303,12 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { while (maybeEatNode()) { nodes.add(pop()); } - if (nodes.size()==1) { + if (nodes.size() == 1) { return nodes.get(0); - } else { - return new CompoundExpression(toPos(start.getStartPosition(),nodes.get(nodes.size()-1).getEndPosition()),nodes.toArray(new SpelNodeImpl[nodes.size()])); } + return new CompoundExpression(toPos(start.getStartPosition(), + nodes.get(nodes.size() - 1).getEndPosition()), + nodes.toArray(new SpelNodeImpl[nodes.size()])); } // node : ((DOT dottedNode) | (SAFE_NAVI dottedNode) | nonDottedNode)+; @@ -299,6 +319,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { } else { expr = maybeEatNonDottedNode(); } + if (expr==null) { return false; } else { @@ -328,15 +349,19 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { // ; private SpelNodeImpl eatDottedNode() { Token t = nextToken();// it was a '.' or a '?.' - boolean nullSafeNavigation = t.kind==TokenKind.SAFE_NAVI; - if (maybeEatMethodOrProperty(nullSafeNavigation) || maybeEatFunctionOrVar() || maybeEatProjection(nullSafeNavigation) || maybeEatSelection(nullSafeNavigation)) { + boolean nullSafeNavigation = t.kind == TokenKind.SAFE_NAVI; + if (maybeEatMethodOrProperty(nullSafeNavigation) || maybeEatFunctionOrVar() + || maybeEatProjection(nullSafeNavigation) + || maybeEatSelection(nullSafeNavigation)) { return pop(); } - if (peekToken()==null) { + if (peekToken() == null) { // unexpectedly ran out of data - raiseInternalException(t.startpos,SpelMessage.OOD); - } else { - raiseInternalException(t.startpos,SpelMessage.UNEXPECTED_DATA_AFTER_DOT,toString(peekToken())); + raiseInternalException(t.startpos, SpelMessage.OOD); + } + else { + raiseInternalException(t.startpos, SpelMessage.UNEXPECTED_DATA_AFTER_DOT, + toString(peekToken())); } return null; } @@ -354,13 +379,15 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { Token t = nextToken(); Token functionOrVariableName = eatToken(TokenKind.IDENTIFIER); SpelNodeImpl[] args = maybeEatMethodArgs(); - if (args==null) { - push(new VariableReference(functionOrVariableName.data,toPos(t.startpos,functionOrVariableName.endpos))); - return true; - } else { - push(new FunctionReference(functionOrVariableName.data,toPos(t.startpos,functionOrVariableName.endpos),args)); + if (args == null) { + push(new VariableReference(functionOrVariableName.data, toPos(t.startpos, + functionOrVariableName.endpos))); return true; } + + push(new FunctionReference(functionOrVariableName.data, toPos(t.startpos, + functionOrVariableName.endpos), args)); + return true; } // methodArgs : LPAREN! (argument (COMMA! argument)* (COMMA!)?)? RPAREN!; @@ -376,7 +403,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { private void eatConstructorArgs(List accumulatedArguments) { if (!peekToken(TokenKind.LPAREN)) { - throw new InternalParseException(new SpelParseException(expressionString,positionOf(peekToken()),SpelMessage.MISSING_CONSTRUCTOR_ARGS)); + throw new InternalParseException(new SpelParseException(this.expressionString,positionOf(peekToken()),SpelMessage.MISSING_CONSTRUCTOR_ARGS)); } consumeArguments(accumulatedArguments); eatToken(TokenKind.RPAREN); @@ -391,27 +418,28 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { do { nextToken();// consume ( (first time through) or comma (subsequent times) Token t = peekToken(); - if (t==null) { - raiseInternalException(pos,SpelMessage.RUN_OUT_OF_ARGUMENTS); + if (t == null) { + raiseInternalException(pos, SpelMessage.RUN_OUT_OF_ARGUMENTS); } - if (t.kind!=TokenKind.RPAREN) { + if (t.kind != TokenKind.RPAREN) { accumulatedArguments.add(eatExpression()); } next = peekToken(); - } while (next!=null && next.kind==TokenKind.COMMA); - if (next==null) { - raiseInternalException(pos,SpelMessage.RUN_OUT_OF_ARGUMENTS); + } + while (next != null && next.kind == TokenKind.COMMA); + + if (next == null) { + raiseInternalException(pos, SpelMessage.RUN_OUT_OF_ARGUMENTS); } } private int positionOf(Token t) { - if (t==null) { + if (t == null) { // if null assume the problem is because the right token was // not found at the end of the expression - return expressionString.length(); - } else { - return t.startpos; + return this.expressionString.length(); } + return t.startpos; } //startNode @@ -428,17 +456,26 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { private SpelNodeImpl eatStartNode() { if (maybeEatLiteral()) { return pop(); - } else if (maybeEatParenExpression()) { + } + else if (maybeEatParenExpression()) { return pop(); - } else if (maybeEatTypeReference() || maybeEatNullReference() || maybeEatConstructorReference() || maybeEatMethodOrProperty(false) || maybeEatFunctionOrVar()) { + } + else if (maybeEatTypeReference() || maybeEatNullReference() + || maybeEatConstructorReference() || maybeEatMethodOrProperty(false) + || maybeEatFunctionOrVar()) { return pop(); - } else if (maybeEatBeanReference()) { + } + else if (maybeEatBeanReference()) { return pop(); - } else if (maybeEatProjection(false) || maybeEatSelection(false) || maybeEatIndexer()) { + } + else if (maybeEatProjection(false) || maybeEatSelection(false) + || maybeEatIndexer()) { return pop(); - } else if (maybeEatInlineList()) { + } + else if (maybeEatInlineList()) { return pop(); - } else { + } + else { return null; } } @@ -453,16 +490,19 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { if (peekToken(TokenKind.IDENTIFIER)) { beanNameToken = eatToken(TokenKind.IDENTIFIER); beanname = beanNameToken.data; - } else if (peekToken(TokenKind.LITERAL_STRING)) { + } + else if (peekToken(TokenKind.LITERAL_STRING)) { beanNameToken = eatToken(TokenKind.LITERAL_STRING); beanname = beanNameToken.stringValue(); beanname = beanname.substring(1, beanname.length() - 1); - } else { - raiseInternalException(beanRefToken.startpos,SpelMessage.INVALID_BEAN_REFERENCE); + } + else { + raiseInternalException(beanRefToken.startpos, + SpelMessage.INVALID_BEAN_REFERENCE); } BeanReference beanReference = new BeanReference(toPos(beanNameToken),beanname); - constructedNodes.push(beanReference); + this.constructedNodes.push(beanReference); return true; } return false; @@ -485,7 +525,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { dims++; } eatToken(TokenKind.RPAREN); - constructedNodes.push(new TypeReference(toPos(typeName),node,dims)); + this.constructedNodes.push(new TypeReference(toPos(typeName),node,dims)); return true; } return false; @@ -498,7 +538,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { return false; } nextToken(); - constructedNodes.push(new NullLiteral(toPos(nullToken))); + this.constructedNodes.push(new NullLiteral(toPos(nullToken))); return true; } return false; @@ -507,46 +547,48 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { //projection: PROJECT^ expression RCURLY!; private boolean maybeEatProjection(boolean nullSafeNavigation) { Token t = peekToken(); - if (!peekToken(TokenKind.PROJECT,true)) { + if (!peekToken(TokenKind.PROJECT, true)) { return false; } SpelNodeImpl expr = eatExpression(); eatToken(TokenKind.RSQUARE); - constructedNodes.push(new Projection(nullSafeNavigation, toPos(t), expr)); + this.constructedNodes.push(new Projection(nullSafeNavigation, toPos(t), expr)); return true; } // list = LCURLY (element (COMMA element)*) RCURLY private boolean maybeEatInlineList() { Token t = peekToken(); - if (!peekToken(TokenKind.LCURLY,true)) { + if (!peekToken(TokenKind.LCURLY, true)) { return false; } SpelNodeImpl expr = null; Token closingCurly = peekToken(); - if (peekToken(TokenKind.RCURLY,true)) { + if (peekToken(TokenKind.RCURLY, true)) { // empty list '[]' expr = new InlineList(toPos(t.startpos,closingCurly.endpos)); - } else { + } + else { List listElements = new ArrayList(); do { listElements.add(eatExpression()); } while (peekToken(TokenKind.COMMA,true)); + closingCurly = eatToken(TokenKind.RCURLY); expr = new InlineList(toPos(t.startpos,closingCurly.endpos),listElements.toArray(new SpelNodeImpl[listElements.size()])); } - constructedNodes.push(expr); + this.constructedNodes.push(expr); return true; } private boolean maybeEatIndexer() { Token t = peekToken(); - if (!peekToken(TokenKind.LSQUARE,true)) { + if (!peekToken(TokenKind.LSQUARE, true)) { return false; } SpelNodeImpl expr = eatExpression(); eatToken(TokenKind.RSQUARE); - constructedNodes.push(new Indexer(toPos(t),expr)); + this.constructedNodes.push(new Indexer(toPos(t),expr)); return true; } @@ -561,12 +603,14 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { raiseInternalException(toPos(t), SpelMessage.MISSING_SELECTION_EXPRESSION); } eatToken(TokenKind.RSQUARE); - if (t.kind==TokenKind.SELECT_FIRST) { - constructedNodes.push(new Selection(nullSafeNavigation,Selection.FIRST,toPos(t),expr)); - } else if (t.kind==TokenKind.SELECT_LAST) { - constructedNodes.push(new Selection(nullSafeNavigation,Selection.LAST,toPos(t),expr)); - } else { - constructedNodes.push(new Selection(nullSafeNavigation,Selection.ALL,toPos(t),expr)); + if (t.kind == TokenKind.SELECT_FIRST) { + this.constructedNodes.push(new Selection(nullSafeNavigation, Selection.FIRST, toPos(t), expr)); + } + else if (t.kind == TokenKind.SELECT_LAST) { + this.constructedNodes.push(new Selection(nullSafeNavigation, Selection.LAST, toPos(t), expr)); + } + else { + this.constructedNodes.push(new Selection(nullSafeNavigation, Selection.ALL, toPos(t), expr)); } return true; } @@ -587,7 +631,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { } if(qualifiedIdPieces.isEmpty()) { if(node == null) { - raiseInternalException( expressionString.length(), SpelMessage.OOD); + raiseInternalException( this.expressionString.length(), SpelMessage.OOD); } raiseInternalException(node.startpos, SpelMessage.NOT_EXPECTED_TOKEN, "qualified ID", node.getKind().toString().toLowerCase()); @@ -617,13 +661,13 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { // property push(new PropertyOrFieldReference(nullSafeNavigation, methodOrPropertyName.data,toPos(methodOrPropertyName))); return true; - } else { - // methodreference - push(new MethodReference(nullSafeNavigation, methodOrPropertyName.data,toPos(methodOrPropertyName),args)); - // TODO what is the end position for a method reference? the name or the last arg? - return true; } + // methodreference + push(new MethodReference(nullSafeNavigation, methodOrPropertyName.data,toPos(methodOrPropertyName),args)); + // TODO what is the end position for a method reference? the name or the last arg? + return true; } + return false; } @@ -642,7 +686,8 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { while (peekToken(TokenKind.LSQUARE,true)) { if (!peekToken(TokenKind.RSQUARE)) { dimensions.add(eatExpression()); - } else { + } + else { dimensions.add(null); } eatToken(TokenKind.RSQUARE); @@ -652,11 +697,13 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { } push(new ConstructorReference(toPos(newToken), dimensions.toArray(new SpelNodeImpl[dimensions.size()]), nodes.toArray(new SpelNodeImpl[nodes.size()]))); - } else { + } + else { // regular constructor invocation eatConstructorArgs(nodes); // TODO correct end position? - push(new ConstructorReference(toPos(newToken), nodes.toArray(new SpelNodeImpl[nodes.size()]))); + push(new ConstructorReference(toPos(newToken), + nodes.toArray(new SpelNodeImpl[nodes.size()]))); } return true; } @@ -664,11 +711,11 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { } private void push(SpelNodeImpl newNode) { - constructedNodes.push(newNode); + this.constructedNodes.push(newNode); } private SpelNodeImpl pop() { - return constructedNodes.pop(); + return this.constructedNodes.pop(); } // literal @@ -684,25 +731,34 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { if (t==null) { return false; } - if (t.kind==TokenKind.LITERAL_INT) { + if (t.kind == TokenKind.LITERAL_INT) { push(Literal.getIntLiteral(t.data, toPos(t), 10)); - } else if (t.kind==TokenKind.LITERAL_LONG) { + } + else if (t.kind == TokenKind.LITERAL_LONG) { push(Literal.getLongLiteral(t.data, toPos(t), 10)); - } else if (t.kind==TokenKind.LITERAL_HEXINT) { + } + else if (t.kind == TokenKind.LITERAL_HEXINT) { push(Literal.getIntLiteral(t.data, toPos(t), 16)); - } else if (t.kind==TokenKind.LITERAL_HEXLONG) { + } + else if (t.kind == TokenKind.LITERAL_HEXLONG) { push(Literal.getLongLiteral(t.data, toPos(t), 16)); - } else if (t.kind==TokenKind.LITERAL_REAL) { - push(Literal.getRealLiteral(t.data, toPos(t),false)); - } else if (t.kind==TokenKind.LITERAL_REAL_FLOAT) { + } + else if (t.kind == TokenKind.LITERAL_REAL) { + push(Literal.getRealLiteral(t.data, toPos(t), false)); + } + else if (t.kind == TokenKind.LITERAL_REAL_FLOAT) { push(Literal.getRealLiteral(t.data, toPos(t), true)); - } else if (peekIdentifierToken("true")) { - push(new BooleanLiteral(t.data,toPos(t),true)); - } else if (peekIdentifierToken("false")) { - push(new BooleanLiteral(t.data,toPos(t),false)); - } else if (t.kind==TokenKind.LITERAL_STRING) { - push(new StringLiteral(t.data,toPos(t),t.data)); - } else { + } + else if (peekIdentifierToken("true")) { + push(new BooleanLiteral(t.data, toPos(t), true)); + } + else if (peekIdentifierToken("false")) { + push(new BooleanLiteral(t.data, toPos(t), false)); + } + else if (t.kind == TokenKind.LITERAL_STRING) { + push(new StringLiteral(t.data, toPos(t), t.data)); + } + else { return false; } nextToken(); @@ -717,7 +773,8 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { eatToken(TokenKind.RPAREN); push(expr); return true; - } else { + } + else { return false; } } @@ -737,9 +794,11 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { String idString = t.stringValue(); if (idString.equalsIgnoreCase("instanceof")) { return t.asInstanceOfToken(); - } else if (idString.equalsIgnoreCase("matches")) { + } + if (idString.equalsIgnoreCase("matches")) { return t.asMatchesToken(); - } else if (idString.equalsIgnoreCase("between")) { + } + if (idString.equalsIgnoreCase("between")) { return t.asBetweenToken(); } } @@ -749,7 +808,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { private Token eatToken(TokenKind expectedKind) { Token t = nextToken(); if (t==null) { - raiseInternalException( expressionString.length(), SpelMessage.OOD); + raiseInternalException( this.expressionString.length(), SpelMessage.OOD); } if (t.kind!=expectedKind) { raiseInternalException(t.startpos,SpelMessage.NOT_EXPECTED_TOKEN, expectedKind.toString().toLowerCase(),t.getKind().toString().toLowerCase()); @@ -768,32 +827,36 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { Token t = peekToken(); if (t.kind==desiredTokenKind) { if (consumeIfMatched) { - tokenStreamPointer++; + this.tokenStreamPointer++; } return true; - } else { - if (desiredTokenKind == TokenKind.IDENTIFIER) { - // might be one of the textual forms of the operators (e.g. NE for != ) - in which case we can treat it as an identifier - // The list is represented here: Tokenizer.alternativeOperatorNames and those ones are in order in the TokenKind enum - if (t.kind.ordinal()>=TokenKind.DIV.ordinal() && t.kind.ordinal()<=TokenKind.NOT.ordinal() && t.data!=null) { - // if t.data were null, we'd know it wasn't the textual form, it was the symbol form - return true; - } - } - return false; } + + if (desiredTokenKind == TokenKind.IDENTIFIER) { + // might be one of the textual forms of the operators (e.g. NE for != ) - in which case we can treat it as an identifier + // The list is represented here: Tokenizer.alternativeOperatorNames and those ones are in order in the TokenKind enum + if (t.kind.ordinal()>=TokenKind.DIV.ordinal() && t.kind.ordinal()<=TokenKind.NOT.ordinal() && t.data!=null) { + // if t.data were null, we'd know it wasn't the textual form, it was the symbol form + return true; + } + } + return false; } private boolean peekToken(TokenKind possible1,TokenKind possible2) { - if (!moreTokens()) return false; + if (!moreTokens()) { + return false; + } Token t = peekToken(); - return t.kind==possible1 || t.kind==possible2; + return t.kind == possible1 || t.kind == possible2; } private boolean peekToken(TokenKind possible1,TokenKind possible2, TokenKind possible3) { - if (!moreTokens()) return false; + if (!moreTokens()) { + return false; + } Token t = peekToken(); - return t.kind==possible1 || t.kind==possible2 || t.kind==possible3; + return t.kind == possible1 || t.kind == possible2 || t.kind == possible3; } private boolean peekIdentifierToken(String identifierString) { @@ -805,39 +868,42 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { } private boolean peekSelectToken() { - if (!moreTokens()) return false; + if (!moreTokens()) { + return false; + } Token t = peekToken(); - return t.kind==TokenKind.SELECT || t.kind==TokenKind.SELECT_FIRST || t.kind==TokenKind.SELECT_LAST; + return t.kind == TokenKind.SELECT || t.kind == TokenKind.SELECT_FIRST + || t.kind == TokenKind.SELECT_LAST; } private boolean moreTokens() { - return tokenStreamPointer=tokenStreamLength) { + if (this.tokenStreamPointer >= this.tokenStreamLength) { return null; } - return tokenStream.get(tokenStreamPointer++); + return this.tokenStream.get(this.tokenStreamPointer++); } private Token peekToken() { - if (tokenStreamPointer>=tokenStreamLength) { + if (this.tokenStreamPointer >= this.tokenStreamLength) { return null; } - return tokenStream.get(tokenStreamPointer); + return this.tokenStream.get(this.tokenStreamPointer); } - private void raiseInternalException(int pos, SpelMessage message,Object... inserts) { - throw new InternalParseException(new SpelParseException(expressionString,pos,message,inserts)); + private void raiseInternalException(int pos, SpelMessage message, Object... inserts) { + throw new InternalParseException(new SpelParseException(this.expressionString, + pos, message, inserts)); } public String toString(Token t) { if (t.getKind().hasPayload()) { return t.stringValue(); - } else { - return t.kind.toString().toLowerCase(); } + return t.kind.toString().toLowerCase(); } private void checkOperands(Token token, SpelNodeImpl left, SpelNodeImpl right) { diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpression.java b/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpression.java index dfefb5516d..18afa0be62 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpression.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpression.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,10 +30,10 @@ import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.util.Assert; /** - * A {@code SpelExpression} represents a parsed (valid) expression that is ready - * to be evaluated in a specified context. An expression can be evaluated - * standalone or in a specified context. During expression evaluation the context - * may be asked to resolve references to types, beans, properties, and methods. + * A {@code SpelExpression} represents a parsed (valid) expression that is ready to be + * evaluated in a specified context. An expression can be evaluated standalone or in a + * specified context. During expression evaluation the context may be asked to resolve + * references to types, beans, properties, and methods. * * @author Andy Clement * @since 3.0 @@ -64,51 +64,51 @@ public class SpelExpression implements Expression { @Override public Object getValue() throws EvaluationException { - ExpressionState expressionState = new ExpressionState(getEvaluationContext(), configuration); - return ast.getValue(expressionState); + ExpressionState expressionState = new ExpressionState(getEvaluationContext(), this.configuration); + return this.ast.getValue(expressionState); } @Override public Object getValue(Object rootObject) throws EvaluationException { - ExpressionState expressionState = new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), configuration); - return ast.getValue(expressionState); + ExpressionState expressionState = new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), this.configuration); + return this.ast.getValue(expressionState); } @Override public T getValue(Class expectedResultType) throws EvaluationException { - ExpressionState expressionState = new ExpressionState(getEvaluationContext(), configuration); - TypedValue typedResultValue = ast.getTypedValue(expressionState); + ExpressionState expressionState = new ExpressionState(getEvaluationContext(), this.configuration); + TypedValue typedResultValue = this.ast.getTypedValue(expressionState); return ExpressionUtils.convertTypedValue(expressionState.getEvaluationContext(), typedResultValue, expectedResultType); } @Override public T getValue(Object rootObject, Class expectedResultType) throws EvaluationException { - ExpressionState expressionState = new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), configuration); - TypedValue typedResultValue = ast.getTypedValue(expressionState); + ExpressionState expressionState = new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), this.configuration); + TypedValue typedResultValue = this.ast.getTypedValue(expressionState); return ExpressionUtils.convertTypedValue(expressionState.getEvaluationContext(), typedResultValue, expectedResultType); } @Override public Object getValue(EvaluationContext context) throws EvaluationException { Assert.notNull(context, "The EvaluationContext is required"); - return ast.getValue(new ExpressionState(context, configuration)); + return this.ast.getValue(new ExpressionState(context, this.configuration)); } @Override public Object getValue(EvaluationContext context, Object rootObject) throws EvaluationException { Assert.notNull(context, "The EvaluationContext is required"); - return ast.getValue(new ExpressionState(context, toTypedValue(rootObject), configuration)); + return this.ast.getValue(new ExpressionState(context, toTypedValue(rootObject), this.configuration)); } @Override public T getValue(EvaluationContext context, Class expectedResultType) throws EvaluationException { - TypedValue typedResultValue = ast.getTypedValue(new ExpressionState(context, configuration)); + TypedValue typedResultValue = this.ast.getTypedValue(new ExpressionState(context, this.configuration)); return ExpressionUtils.convertTypedValue(context, typedResultValue, expectedResultType); } @Override public T getValue(EvaluationContext context, Object rootObject, Class expectedResultType) throws EvaluationException { - TypedValue typedResultValue = ast.getTypedValue(new ExpressionState(context, toTypedValue(rootObject), configuration)); + TypedValue typedResultValue = this.ast.getTypedValue(new ExpressionState(context, toTypedValue(rootObject), this.configuration)); return ExpressionUtils.convertTypedValue(context, typedResultValue, expectedResultType); } @@ -125,15 +125,15 @@ public class SpelExpression implements Expression { @Override public Class getValueType(EvaluationContext context) throws EvaluationException { Assert.notNull(context, "The EvaluationContext is required"); - ExpressionState eState = new ExpressionState(context, configuration); - TypeDescriptor typeDescriptor = ast.getValueInternal(eState).getTypeDescriptor(); + ExpressionState eState = new ExpressionState(context, this.configuration); + TypeDescriptor typeDescriptor = this.ast.getValueInternal(eState).getTypeDescriptor(); return typeDescriptor != null ? typeDescriptor.getType() : null; } @Override public Class getValueType(EvaluationContext context, Object rootObject) throws EvaluationException { - ExpressionState eState = new ExpressionState(context, toTypedValue(rootObject), configuration); - TypeDescriptor typeDescriptor = ast.getValueInternal(eState).getTypeDescriptor(); + ExpressionState eState = new ExpressionState(context, toTypedValue(rootObject), this.configuration); + TypeDescriptor typeDescriptor = this.ast.getValueInternal(eState).getTypeDescriptor(); return typeDescriptor != null ? typeDescriptor.getType() : null; } @@ -144,61 +144,61 @@ public class SpelExpression implements Expression { @Override public TypeDescriptor getValueTypeDescriptor(Object rootObject) throws EvaluationException { - ExpressionState eState = new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), configuration); - return ast.getValueInternal(eState).getTypeDescriptor(); + ExpressionState eState = new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), this.configuration); + return this.ast.getValueInternal(eState).getTypeDescriptor(); } @Override public TypeDescriptor getValueTypeDescriptor(EvaluationContext context) throws EvaluationException { Assert.notNull(context, "The EvaluationContext is required"); - ExpressionState eState = new ExpressionState(context, configuration); - return ast.getValueInternal(eState).getTypeDescriptor(); + ExpressionState eState = new ExpressionState(context, this.configuration); + return this.ast.getValueInternal(eState).getTypeDescriptor(); } @Override public TypeDescriptor getValueTypeDescriptor(EvaluationContext context, Object rootObject) throws EvaluationException { Assert.notNull(context, "The EvaluationContext is required"); - ExpressionState eState = new ExpressionState(context, toTypedValue(rootObject), configuration); - return ast.getValueInternal(eState).getTypeDescriptor(); + ExpressionState eState = new ExpressionState(context, toTypedValue(rootObject), this.configuration); + return this.ast.getValueInternal(eState).getTypeDescriptor(); } @Override public String getExpressionString() { - return expression; + return this.expression; } @Override public boolean isWritable(EvaluationContext context) throws EvaluationException { Assert.notNull(context, "The EvaluationContext is required"); - return ast.isWritable(new ExpressionState(context, configuration)); + return this.ast.isWritable(new ExpressionState(context, this.configuration)); } @Override public boolean isWritable(Object rootObject) throws EvaluationException { - return ast.isWritable(new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), configuration)); + return this.ast.isWritable(new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), this.configuration)); } @Override public boolean isWritable(EvaluationContext context, Object rootObject) throws EvaluationException { Assert.notNull(context, "The EvaluationContext is required"); - return ast.isWritable(new ExpressionState(context, toTypedValue(rootObject), configuration)); + return this.ast.isWritable(new ExpressionState(context, toTypedValue(rootObject), this.configuration)); } @Override public void setValue(EvaluationContext context, Object value) throws EvaluationException { Assert.notNull(context, "The EvaluationContext is required"); - ast.setValue(new ExpressionState(context, configuration), value); + this.ast.setValue(new ExpressionState(context, this.configuration), value); } @Override public void setValue(Object rootObject, Object value) throws EvaluationException { - ast.setValue(new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), configuration), value); + this.ast.setValue(new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), this.configuration), value); } @Override public void setValue(EvaluationContext context, Object rootObject, Object value) throws EvaluationException { Assert.notNull(context, "The EvaluationContext is required"); - ast.setValue(new ExpressionState(context, toTypedValue(rootObject), configuration), value); + this.ast.setValue(new ExpressionState(context, toTypedValue(rootObject), this.configuration), value); } // impl only @@ -207,7 +207,7 @@ public class SpelExpression implements Expression { * @return return the Abstract Syntax Tree for the expression */ public SpelNode getAST() { - return ast; + return this.ast; } /** @@ -217,7 +217,7 @@ public class SpelExpression implements Expression { * @return the string representation of the AST */ public String toStringAST() { - return ast.toStringAST(); + return this.ast.toStringAST(); } /** @@ -225,10 +225,10 @@ public class SpelExpression implements Expression { * @return the default evaluation context */ public EvaluationContext getEvaluationContext() { - if (defaultContext == null) { - defaultContext = new StandardEvaluationContext(); + if (this.defaultContext == null) { + this.defaultContext = new StandardEvaluationContext(); } - return defaultContext; + return this.defaultContext; } /** diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/standard/Token.java b/spring-expression/src/main/java/org/springframework/expression/spel/standard/Token.java index 12b71d6b6d..69e4d3a625 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/standard/Token.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/standard/Token.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,8 @@ package org.springframework.expression.spel.standard; /** - * Holder for a kind of token, the associated data and its position in the input data stream (start/end). + * Holder for a kind of token, the associated data and its position in the input data + * stream (start/end). * * @author Andy Clement * @since 3.0 @@ -25,12 +26,17 @@ package org.springframework.expression.spel.standard; class Token { TokenKind kind; + String data; + int startpos; // index of first character + int endpos; // index of char after the last character + /** - * Constructor for use when there is no particular data for the token (eg. TRUE or '+') + * Constructor for use when there is no particular data for the token (eg. TRUE or + * '+') * @param startpos the exact start * @param endpos the index to the last character */ @@ -47,42 +53,42 @@ class Token { public TokenKind getKind() { - return kind; + return this.kind; } @Override public String toString() { StringBuilder s = new StringBuilder(); - s.append("[").append(kind.toString()); - if (kind.hasPayload()) { - s.append(":").append(data); + s.append("[").append(this.kind.toString()); + if (this.kind.hasPayload()) { + s.append(":").append(this.data); } s.append("]"); - s.append("(").append(startpos).append(",").append(endpos).append(")"); + s.append("(").append(this.startpos).append(",").append(this.endpos).append(")"); return s.toString(); } public boolean isIdentifier() { - return kind==TokenKind.IDENTIFIER; + return this.kind==TokenKind.IDENTIFIER; } public boolean isNumericRelationalOperator() { - return kind==TokenKind.GT || kind==TokenKind.GE || kind==TokenKind.LT || kind==TokenKind.LE || kind==TokenKind.EQ || kind==TokenKind.NE; + return this.kind==TokenKind.GT || this.kind==TokenKind.GE || this.kind==TokenKind.LT || this.kind==TokenKind.LE || this.kind==TokenKind.EQ || this.kind==TokenKind.NE; } public String stringValue() { - return data; + return this.data; } public Token asInstanceOfToken() { - return new Token(TokenKind.INSTANCEOF,startpos,endpos); + return new Token(TokenKind.INSTANCEOF,this.startpos,this.endpos); } public Token asMatchesToken() { - return new Token(TokenKind.MATCHES,startpos,endpos); + return new Token(TokenKind.MATCHES,this.startpos,this.endpos); } public Token asBetweenToken() { - return new Token(TokenKind.BETWEEN,startpos,endpos); + return new Token(TokenKind.BETWEEN,this.startpos,this.endpos); } } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/standard/TokenKind.java b/spring-expression/src/main/java/org/springframework/expression/spel/standard/TokenKind.java index d0b42deea2..c50ac090ba 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/standard/TokenKind.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/standard/TokenKind.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,44 +17,135 @@ package org.springframework.expression.spel.standard; /** + * Token Kinds. + * * @author Andy Clement * @since 3.0 */ enum TokenKind { + // ordered by priority - operands first - LITERAL_INT, LITERAL_LONG, LITERAL_HEXINT, LITERAL_HEXLONG, LITERAL_STRING, LITERAL_REAL, LITERAL_REAL_FLOAT, - LPAREN("("), RPAREN(")"), COMMA(","), IDENTIFIER, - COLON(":"),HASH("#"),RSQUARE("]"), LSQUARE("["), - LCURLY("{"),RCURLY("}"), - DOT("."), PLUS("+"), STAR("*"), MINUS("-"), SELECT_FIRST("^["), SELECT_LAST("$["), QMARK("?"), PROJECT("!["), - DIV("/"), GE(">="), GT(">"), LE("<="), LT("<"), EQ("=="), NE("!="), - MOD("%"), NOT("!"), ASSIGN("="), INSTANCEOF("instanceof"), MATCHES("matches"), BETWEEN("between"), - SELECT("?["), POWER("^"), - ELVIS("?:"), SAFE_NAVI("?."), BEAN_REF("@"), SYMBOLIC_OR("||"), SYMBOLIC_AND("&&"), INC("++"), DEC("--") - ; + + LITERAL_INT, + + LITERAL_LONG, + + LITERAL_HEXINT, + + LITERAL_HEXLONG, + + LITERAL_STRING, + + LITERAL_REAL, + + LITERAL_REAL_FLOAT, + + LPAREN("("), + + RPAREN(")"), + + COMMA(","), + + IDENTIFIER, + + COLON(":"), + + HASH("#"), + + RSQUARE("]"), + + LSQUARE("["), + + LCURLY("{"), + + RCURLY("}"), + + DOT("."), + + PLUS("+"), + + STAR("*"), + + MINUS("-"), + + SELECT_FIRST("^["), + + SELECT_LAST("$["), + + QMARK("?"), + + PROJECT("!["), + + DIV("/"), + + GE(">="), + + GT(">"), + + LE("<="), + + LT("<"), + + EQ("=="), + + NE("!="), + + MOD("%"), + + NOT("!"), + + ASSIGN("="), + + INSTANCEOF("instanceof"), + + MATCHES("matches"), + + BETWEEN("between"), + + SELECT("?["), + + POWER("^"), + + ELVIS("?:"), + + SAFE_NAVI("?."), + + BEAN_REF("@"), + + SYMBOLIC_OR("||"), + + SYMBOLIC_AND("&&"), + + INC("++"), + + DEC("--"); + char[] tokenChars; + private boolean hasPayload; // is there more to this token than simply the kind + private TokenKind(String tokenString) { - tokenChars = tokenString.toCharArray(); - hasPayload = tokenChars.length==0; + this.tokenChars = tokenString.toCharArray(); + this.hasPayload = this.tokenChars.length==0; } private TokenKind() { this(""); } + @Override public String toString() { - return this.name()+(tokenChars.length!=0?"("+new String(tokenChars)+")":""); + return this.name()+(this.tokenChars.length!=0?"("+new String(this.tokenChars)+")":""); } public boolean hasPayload() { - return hasPayload; + return this.hasPayload; } public int getLength() { - return tokenChars.length; + return this.tokenChars.length; } } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/standard/Tokenizer.java b/spring-expression/src/main/java/org/springframework/expression/spel/standard/Tokenizer.java index 4223a40542..cd2c6128b0 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/standard/Tokenizer.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/standard/Tokenizer.java @@ -34,276 +34,337 @@ import org.springframework.util.Assert; */ class Tokenizer { + // if this is changed, it must remain sorted + private static final String[] ALTERNATIVE_OPERATOR_NAMES = { "DIV", "EQ", "GE", "GT", + "LE", "LT", "MOD", "NE", "NOT" }; + + private static final byte FLAGS[] = new byte[256]; + + private static final byte IS_DIGIT = 0x01; + + private static final byte IS_HEXDIGIT = 0x02; + + private static final byte IS_ALPHA = 0x04; + + static { + for (int ch = '0'; ch <= '9'; ch++) { + FLAGS[ch] |= IS_DIGIT | IS_HEXDIGIT; + } + for (int ch = 'A'; ch <= 'F'; ch++) { + FLAGS[ch] |= IS_HEXDIGIT; + } + for (int ch = 'a'; ch <= 'f'; ch++) { + FLAGS[ch] |= IS_HEXDIGIT; + } + for (int ch = 'A'; ch <= 'Z'; ch++) { + FLAGS[ch] |= IS_ALPHA; + } + for (int ch = 'a'; ch <= 'z'; ch++) { + FLAGS[ch] |= IS_ALPHA; + } + } + + String expressionString; + char[] toProcess; + int pos; + int max; + List tokens = new ArrayList(); + public Tokenizer(String inputdata) { this.expressionString = inputdata; - this.toProcess = (inputdata+"\0").toCharArray(); - this.max = toProcess.length; + this.toProcess = (inputdata + "\0").toCharArray(); + this.max = this.toProcess.length; this.pos = 0; process(); } + public void process() { - while (pos': - if (isTwoCharToken(TokenKind.GE)) { - pushPairToken(TokenKind.GE); - } else { - pushCharToken(TokenKind.GT); - } - break; - case '<': - if (isTwoCharToken(TokenKind.LE)) { - pushPairToken(TokenKind.LE); - } else { - pushCharToken(TokenKind.LT); - } - break; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - lexNumericLiteral(ch=='0'); - break; - case ' ': - case '\t': - case '\r': - case '\n': - // drift over white space - pos++; - break; - case '\'': - lexQuotedStringLiteral(); - break; - case '"': - lexDoubleQuotedStringLiteral(); - break; - case 0: - // hit sentinel at end of value - pos++; // will take us to the end - break; - case '\\': - throw new InternalParseException(new SpelParseException(expressionString,pos,SpelMessage.UNEXPECTED_ESCAPE_CHAR)); - default: - throw new IllegalStateException("Cannot handle ("+Integer.valueOf(ch)+") '"+ch+"'"); + break; + case '-': + if (isTwoCharToken(TokenKind.DEC)) { + pushPairToken(TokenKind.DEC); + } + else { + pushCharToken(TokenKind.MINUS); + } + break; + case ':': + pushCharToken(TokenKind.COLON); + break; + case '.': + pushCharToken(TokenKind.DOT); + break; + case ',': + pushCharToken(TokenKind.COMMA); + break; + case '*': + pushCharToken(TokenKind.STAR); + break; + case '/': + pushCharToken(TokenKind.DIV); + break; + case '%': + pushCharToken(TokenKind.MOD); + break; + case '(': + pushCharToken(TokenKind.LPAREN); + break; + case ')': + pushCharToken(TokenKind.RPAREN); + break; + case '[': + pushCharToken(TokenKind.LSQUARE); + break; + case '#': + pushCharToken(TokenKind.HASH); + break; + case ']': + pushCharToken(TokenKind.RSQUARE); + break; + case '{': + pushCharToken(TokenKind.LCURLY); + break; + case '}': + pushCharToken(TokenKind.RCURLY); + break; + case '@': + pushCharToken(TokenKind.BEAN_REF); + break; + case '^': + if (isTwoCharToken(TokenKind.SELECT_FIRST)) { + pushPairToken(TokenKind.SELECT_FIRST); + } + else { + pushCharToken(TokenKind.POWER); + } + break; + case '!': + if (isTwoCharToken(TokenKind.NE)) { + pushPairToken(TokenKind.NE); + } + else if (isTwoCharToken(TokenKind.PROJECT)) { + pushPairToken(TokenKind.PROJECT); + } + else { + pushCharToken(TokenKind.NOT); + } + break; + case '=': + if (isTwoCharToken(TokenKind.EQ)) { + pushPairToken(TokenKind.EQ); + } + else { + pushCharToken(TokenKind.ASSIGN); + } + break; + case '&': + if (!isTwoCharToken(TokenKind.SYMBOLIC_AND)) { + throw new InternalParseException(new SpelParseException( + this.expressionString, this.pos, SpelMessage.MISSING_CHARACTER, + "&")); + } + pushPairToken(TokenKind.SYMBOLIC_AND); + break; + case '|': + if (!isTwoCharToken(TokenKind.SYMBOLIC_OR)) { + throw new InternalParseException(new SpelParseException( + this.expressionString, this.pos, SpelMessage.MISSING_CHARACTER, + "|")); + } + pushPairToken(TokenKind.SYMBOLIC_OR); + break; + case '?': + if (isTwoCharToken(TokenKind.SELECT)) { + pushPairToken(TokenKind.SELECT); + } + else if (isTwoCharToken(TokenKind.ELVIS)) { + pushPairToken(TokenKind.ELVIS); + } + else if (isTwoCharToken(TokenKind.SAFE_NAVI)) { + pushPairToken(TokenKind.SAFE_NAVI); + } + else { + pushCharToken(TokenKind.QMARK); + } + break; + case '$': + if (isTwoCharToken(TokenKind.SELECT_LAST)) { + pushPairToken(TokenKind.SELECT_LAST); + } + else { + lexIdentifier(); + } + break; + case '>': + if (isTwoCharToken(TokenKind.GE)) { + pushPairToken(TokenKind.GE); + } + else { + pushCharToken(TokenKind.GT); + } + break; + case '<': + if (isTwoCharToken(TokenKind.LE)) { + pushPairToken(TokenKind.LE); + } + else { + pushCharToken(TokenKind.LT); + } + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + lexNumericLiteral(ch == '0'); + break; + case ' ': + case '\t': + case '\r': + case '\n': + // drift over white space + this.pos++; + break; + case '\'': + lexQuotedStringLiteral(); + break; + case '"': + lexDoubleQuotedStringLiteral(); + break; + case 0: + // hit sentinel at end of value + this.pos++; // will take us to the end + break; + case '\\': + throw new InternalParseException( + new SpelParseException(this.expressionString, this.pos, + SpelMessage.UNEXPECTED_ESCAPE_CHAR)); + default: + throw new IllegalStateException("Cannot handle (" + + Integer.valueOf(ch) + ") '" + ch + "'"); } } } } public List getTokens() { - return tokens; + return this.tokens; } // STRING_LITERAL: '\''! (APOS|~'\'')* '\''!; private void lexQuotedStringLiteral() { - int start = pos; + int start = this.pos; boolean terminated = false; while (!terminated) { - pos++; - char ch = toProcess[pos]; - if (ch=='\'') { + this.pos++; + char ch = this.toProcess[this.pos]; + if (ch == '\'') { // may not be the end if the char after is also a ' - if (toProcess[pos+1]=='\'') { - pos++; // skip over that too, and continue - } else { + if (this.toProcess[this.pos + 1] == '\'') { + this.pos++; // skip over that too, and continue + } + else { terminated = true; } } - if (ch==0) { - throw new InternalParseException(new SpelParseException(expressionString,start,SpelMessage.NON_TERMINATING_QUOTED_STRING)); + if (ch == 0) { + throw new InternalParseException(new SpelParseException(this.expressionString, + start, SpelMessage.NON_TERMINATING_QUOTED_STRING)); } } - pos++; - tokens.add(new Token(TokenKind.LITERAL_STRING, subarray(start,pos), start, pos)); + this.pos++; + this.tokens.add(new Token(TokenKind.LITERAL_STRING, subarray(start, this.pos), start, this.pos)); } - // DQ_STRING_LITERAL: '"'! (~'"')* '"'!; + // DQ_STRING_LITERAL: '"'! (~'"')* '"'!; private void lexDoubleQuotedStringLiteral() { - int start = pos; + int start = this.pos; boolean terminated = false; while (!terminated) { - pos++; - char ch = toProcess[pos]; - if (ch=='"') { + this.pos++; + char ch = this.toProcess[this.pos]; + if (ch == '"') { // may not be the end if the char after is also a " - if (toProcess[pos+1]=='"') { - pos++; // skip over that too, and continue - } else { + if (this.toProcess[this.pos + 1] == '"') { + this.pos++; // skip over that too, and continue + } + else { terminated = true; } } - if (ch==0) { - throw new InternalParseException(new SpelParseException(expressionString,start,SpelMessage.NON_TERMINATING_DOUBLE_QUOTED_STRING)); + if (ch == 0) { + throw new InternalParseException(new SpelParseException(this.expressionString, + start, SpelMessage.NON_TERMINATING_DOUBLE_QUOTED_STRING)); } } - pos++; - tokens.add(new Token(TokenKind.LITERAL_STRING, subarray(start,pos), start, pos)); + this.pos++; + this.tokens.add(new Token(TokenKind.LITERAL_STRING, subarray(start, this.pos), start, this.pos)); } -// REAL_LITERAL : -// ('.' (DECIMAL_DIGIT)+ (EXPONENT_PART)? (REAL_TYPE_SUFFIX)?) | -// ((DECIMAL_DIGIT)+ '.' (DECIMAL_DIGIT)+ (EXPONENT_PART)? (REAL_TYPE_SUFFIX)?) | -// ((DECIMAL_DIGIT)+ (EXPONENT_PART) (REAL_TYPE_SUFFIX)?) | -// ((DECIMAL_DIGIT)+ (REAL_TYPE_SUFFIX)); -// fragment INTEGER_TYPE_SUFFIX : ( 'L' | 'l' ); -// fragment HEX_DIGIT : '0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'|'A'|'B'|'C'|'D'|'E'|'F'|'a'|'b'|'c'|'d'|'e'|'f'; -// -// fragment EXPONENT_PART : 'e' (SIGN)* (DECIMAL_DIGIT)+ | 'E' (SIGN)* (DECIMAL_DIGIT)+ ; -// fragment SIGN : '+' | '-' ; -// fragment REAL_TYPE_SUFFIX : 'F' | 'f' | 'D' | 'd'; -// INTEGER_LITERAL -// : (DECIMAL_DIGIT)+ (INTEGER_TYPE_SUFFIX)?; + // REAL_LITERAL : + // ('.' (DECIMAL_DIGIT)+ (EXPONENT_PART)? (REAL_TYPE_SUFFIX)?) | + // ((DECIMAL_DIGIT)+ '.' (DECIMAL_DIGIT)+ (EXPONENT_PART)? (REAL_TYPE_SUFFIX)?) | + // ((DECIMAL_DIGIT)+ (EXPONENT_PART) (REAL_TYPE_SUFFIX)?) | + // ((DECIMAL_DIGIT)+ (REAL_TYPE_SUFFIX)); + // fragment INTEGER_TYPE_SUFFIX : ( 'L' | 'l' ); + // fragment HEX_DIGIT : + // '0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'|'A'|'B'|'C'|'D'|'E'|'F'|'a'|'b'|'c'|'d'|'e'|'f'; + // + // fragment EXPONENT_PART : 'e' (SIGN)* (DECIMAL_DIGIT)+ | 'E' (SIGN)* + // (DECIMAL_DIGIT)+ ; + // fragment SIGN : '+' | '-' ; + // fragment REAL_TYPE_SUFFIX : 'F' | 'f' | 'D' | 'd'; + // INTEGER_LITERAL + // : (DECIMAL_DIGIT)+ (INTEGER_TYPE_SUFFIX)?; private void lexNumericLiteral(boolean firstCharIsZero) { boolean isReal = false; - int start = pos; - char ch = toProcess[pos+1]; - boolean isHex = ch=='x' || ch=='X'; + int start = this.pos; + char ch = this.toProcess[this.pos + 1]; + boolean isHex = ch == 'x' || ch == 'X'; // deal with hexadecimal if (firstCharIsZero && isHex) { - pos=pos+1; + this.pos = this.pos + 1; do { - pos++; - } while (isHexadecimalDigit(toProcess[pos])); - if (isChar('L','l')) { - pushHexIntToken(subarray(start+2,pos),true, start, pos); - pos++; - } else { - pushHexIntToken(subarray(start+2,pos),false, start, pos); + this.pos++; + } + while (isHexadecimalDigit(this.toProcess[this.pos])); + if (isChar('L', 'l')) { + pushHexIntToken(subarray(start + 2, this.pos), true, start, this.pos); + this.pos++; + } + else { + pushHexIntToken(subarray(start + 2, this.pos), false, start, this.pos); } return; } @@ -312,134 +373,150 @@ class Tokenizer { // Consume first part of number do { - pos++; - } while (isDigit(toProcess[pos])); + this.pos++; + } + while (isDigit(this.toProcess[this.pos])); // a '.' indicates this number is a real - ch = toProcess[pos]; - if (ch=='.') { + ch = this.toProcess[this.pos]; + if (ch == '.') { isReal = true; - int dotpos = pos; + int dotpos = this.pos; // carry on consuming digits do { - pos++; - } while (isDigit(toProcess[pos])); - if (pos == dotpos + 1) { + this.pos++; + } + while (isDigit(this.toProcess[this.pos])); + if (this.pos == dotpos + 1) { // the number is something like '3.'. It is really an int but may be // part of something like '3.toString()'. In this case process it as // an int and leave the dot as a separate token. - pos = dotpos; - pushIntToken(subarray(start, pos), false, start, pos); + this.pos = dotpos; + pushIntToken(subarray(start, this.pos), false, start, this.pos); return; } } - int endOfNumber = pos; + int endOfNumber = this.pos; // Now there may or may not be an exponent // is it a long ? - if (isChar('L','l')) { + if (isChar('L', 'l')) { if (isReal) { // 3.4L - not allowed - throw new InternalParseException(new SpelParseException(expressionString,start,SpelMessage.REAL_CANNOT_BE_LONG)); + throw new InternalParseException(new SpelParseException(this.expressionString, + start, SpelMessage.REAL_CANNOT_BE_LONG)); } pushIntToken(subarray(start, endOfNumber), true, start, endOfNumber); - pos++; - } else if (isExponentChar(toProcess[pos])) { + this.pos++; + } + else if (isExponentChar(this.toProcess[this.pos])) { isReal = true; // if it wasn't before, it is now - pos++; - char possibleSign = toProcess[pos]; + this.pos++; + char possibleSign = this.toProcess[this.pos]; if (isSign(possibleSign)) { - pos++; + this.pos++; } // exponent digits do { - pos++; - } while (isDigit(toProcess[pos])); - boolean isFloat = false; - if (isFloatSuffix(toProcess[pos])) { - isFloat = true; - endOfNumber = ++pos; - } else if (isDoubleSuffix(toProcess[pos])) { - endOfNumber = ++pos; + this.pos++; } - pushRealToken(subarray(start,pos), isFloat, start, pos); - } else { - ch = toProcess[pos]; + while (isDigit(this.toProcess[this.pos])); + boolean isFloat = false; + if (isFloatSuffix(this.toProcess[this.pos])) { + isFloat = true; + endOfNumber = ++this.pos; + } + else if (isDoubleSuffix(this.toProcess[this.pos])) { + endOfNumber = ++this.pos; + } + pushRealToken(subarray(start, this.pos), isFloat, start, this.pos); + } + else { + ch = this.toProcess[this.pos]; boolean isFloat = false; if (isFloatSuffix(ch)) { isReal = true; isFloat = true; - endOfNumber = ++pos; - } else if (isDoubleSuffix(ch)) { + endOfNumber = ++this.pos; + } + else if (isDoubleSuffix(ch)) { isReal = true; - endOfNumber = ++pos; + endOfNumber = ++this.pos; } if (isReal) { - pushRealToken(subarray(start,endOfNumber), isFloat, start, endOfNumber); - } else { - pushIntToken(subarray(start,endOfNumber), false, start, endOfNumber); + pushRealToken(subarray(start, endOfNumber), isFloat, start, endOfNumber); + } + else { + pushIntToken(subarray(start, endOfNumber), false, start, endOfNumber); } } } - // if this is changed, it must remain sorted - private static final String[] alternativeOperatorNames = { "DIV","EQ","GE","GT","LE","LT","MOD","NE","NOT"}; - private void lexIdentifier() { - int start = pos; + int start = this.pos; do { - pos++; - } while (isIdentifier(toProcess[pos])); - char[] subarray = subarray(start,pos); + this.pos++; + } + while (isIdentifier(this.toProcess[this.pos])); + char[] subarray = subarray(start, this.pos); - // Check if this is the alternative (textual) representation of an operator (see alternativeOperatorNames) - if ((pos-start)==2 || (pos-start)==3) { + // Check if this is the alternative (textual) representation of an operator (see + // alternativeOperatorNames) + if ((this.pos - start) == 2 || (this.pos - start) == 3) { String asString = new String(subarray).toUpperCase(); - int idx = Arrays.binarySearch(alternativeOperatorNames,asString); - if (idx>=0) { - pushOneCharOrTwoCharToken(TokenKind.valueOf(asString),start,subarray); + int idx = Arrays.binarySearch(ALTERNATIVE_OPERATOR_NAMES, asString); + if (idx >= 0) { + pushOneCharOrTwoCharToken(TokenKind.valueOf(asString), start, subarray); return; } } - tokens.add(new Token(TokenKind.IDENTIFIER,subarray,start,pos)); + this.tokens.add(new Token(TokenKind.IDENTIFIER, subarray, start, this.pos)); } - private void pushIntToken(char[] data,boolean isLong, int start, int end) { + private void pushIntToken(char[] data, boolean isLong, int start, int end) { if (isLong) { - tokens.add(new Token(TokenKind.LITERAL_LONG,data, start, end)); - } else { - tokens.add(new Token(TokenKind.LITERAL_INT,data, start, end)); + this.tokens.add(new Token(TokenKind.LITERAL_LONG, data, start, end)); + } + else { + this.tokens.add(new Token(TokenKind.LITERAL_INT, data, start, end)); } } - private void pushHexIntToken(char[] data,boolean isLong, int start, int end) { - if (data.length==0) { + private void pushHexIntToken(char[] data, boolean isLong, int start, int end) { + if (data.length == 0) { if (isLong) { - throw new InternalParseException(new SpelParseException(expressionString,start,SpelMessage.NOT_A_LONG,expressionString.substring(start,end+1))); - } else { - throw new InternalParseException(new SpelParseException(expressionString,start,SpelMessage.NOT_AN_INTEGER,expressionString.substring(start,end))); + throw new InternalParseException(new SpelParseException(this.expressionString, + start, SpelMessage.NOT_A_LONG, this.expressionString.substring(start, + end + 1))); + } + else { + throw new InternalParseException(new SpelParseException(this.expressionString, + start, SpelMessage.NOT_AN_INTEGER, this.expressionString.substring( + start, end))); } } if (isLong) { - tokens.add(new Token(TokenKind.LITERAL_HEXLONG, data, start, end)); - } else { - tokens.add(new Token(TokenKind.LITERAL_HEXINT, data, start, end)); + this.tokens.add(new Token(TokenKind.LITERAL_HEXLONG, data, start, end)); + } + else { + this.tokens.add(new Token(TokenKind.LITERAL_HEXINT, data, start, end)); } } private void pushRealToken(char[] data, boolean isFloat, int start, int end) { if (isFloat) { - tokens.add(new Token(TokenKind.LITERAL_REAL_FLOAT, data, start, end)); - } else { - tokens.add(new Token(TokenKind.LITERAL_REAL, data, start, end)); + this.tokens.add(new Token(TokenKind.LITERAL_REAL_FLOAT, data, start, end)); + } + else { + this.tokens.add(new Token(TokenKind.LITERAL_REAL, data, start, end)); } } private char[] subarray(int start, int end) { char[] result = new char[end - start]; - System.arraycopy(toProcess, start, result, 0, end - start); + System.arraycopy(this.toProcess, start, result, 0, end - start); return result; } @@ -448,98 +525,74 @@ class Tokenizer { */ private boolean isTwoCharToken(TokenKind kind) { Assert.isTrue(kind.tokenChars.length == 2); - Assert.isTrue(toProcess[pos] == kind.tokenChars[0]); - return toProcess[pos+1] == kind.tokenChars[1]; + Assert.isTrue(this.toProcess[this.pos] == kind.tokenChars[0]); + return this.toProcess[this.pos + 1] == kind.tokenChars[1]; } /** * Push a token of just one character in length. */ private void pushCharToken(TokenKind kind) { - tokens.add(new Token(kind,pos,pos+1)); - pos++; + this.tokens.add(new Token(kind, this.pos, this.pos + 1)); + this.pos++; } /** * Push a token of two characters in length. */ private void pushPairToken(TokenKind kind) { - tokens.add(new Token(kind,pos,pos+2)); - pos+=2; + this.tokens.add(new Token(kind, this.pos, this.pos + 2)); + this.pos += 2; } private void pushOneCharOrTwoCharToken(TokenKind kind, int pos, char[] data) { - tokens.add(new Token(kind,data,pos,pos+kind.getLength())); + this.tokens.add(new Token(kind, data, pos, pos + kind.getLength())); } - // ID: ('a'..'z'|'A'..'Z'|'_'|'$') ('a'..'z'|'A'..'Z'|'_'|'$'|'0'..'9'|DOT_ESCAPED)*; + // ID: ('a'..'z'|'A'..'Z'|'_'|'$') ('a'..'z'|'A'..'Z'|'_'|'$'|'0'..'9'|DOT_ESCAPED)*; private boolean isIdentifier(char ch) { - return isAlphabetic(ch) || isDigit(ch) || ch=='_' || ch=='$'; + return isAlphabetic(ch) || isDigit(ch) || ch == '_' || ch == '$'; } - private boolean isChar(char a,char b) { - char ch = toProcess[pos]; - return ch==a || ch==b; + private boolean isChar(char a, char b) { + char ch = this.toProcess[this.pos]; + return ch == a || ch == b; } private boolean isExponentChar(char ch) { - return ch=='e' || ch=='E'; + return ch == 'e' || ch == 'E'; } private boolean isFloatSuffix(char ch) { - return ch=='f' || ch=='F'; + return ch == 'f' || ch == 'F'; } private boolean isDoubleSuffix(char ch) { - return ch=='d' || ch=='D'; + return ch == 'd' || ch == 'D'; } private boolean isSign(char ch) { - return ch=='+' || ch=='-'; + return ch == '+' || ch == '-'; } private boolean isDigit(char ch) { - if (ch>255) { + if (ch > 255) { return false; } - return (flags[ch] & IS_DIGIT)!=0; + return (FLAGS[ch] & IS_DIGIT) != 0; } private boolean isAlphabetic(char ch) { - if (ch>255) { + if (ch > 255) { return false; } - return (flags[ch] & IS_ALPHA)!=0; + return (FLAGS[ch] & IS_ALPHA) != 0; } private boolean isHexadecimalDigit(char ch) { - if (ch>255) { + if (ch > 255) { return false; } - return (flags[ch] & IS_HEXDIGIT)!=0; + return (FLAGS[ch] & IS_HEXDIGIT) != 0; } - - private static final byte flags[] = new byte[256]; - private static final byte IS_DIGIT=0x01; - private static final byte IS_HEXDIGIT=0x02; - private static final byte IS_ALPHA=0x04; - - static { - for (int ch='0';ch<='9';ch++) { - flags[ch]|=IS_DIGIT | IS_HEXDIGIT; - } - for (int ch='A';ch<='F';ch++) { - flags[ch]|= IS_HEXDIGIT; - } - for (int ch='a';ch<='f';ch++) { - flags[ch]|= IS_HEXDIGIT; - } - for (int ch='A';ch<='Z';ch++) { - flags[ch]|= IS_ALPHA; - } - for (int ch='a';ch<='z';ch++) { - flags[ch]|= IS_ALPHA; - } - } - } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/BooleanTypedValue.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/BooleanTypedValue.java index 6b407e8d0b..70093eeaf6 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/BooleanTypedValue.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/BooleanTypedValue.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,12 +35,7 @@ public class BooleanTypedValue extends TypedValue { public static BooleanTypedValue forValue(boolean b) { - if (b) { - return TRUE; - } - else { - return FALSE; - } + return (b ? TRUE : FALSE); } } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java index 68e47d8533..cce6b0c3cb 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,13 +42,15 @@ import org.springframework.util.MethodInvoker; public class ReflectionHelper { /** - * Compare argument arrays and return information about whether they match. A supplied type converter - * and conversionAllowed flag allow for matches to take into account that a type may be transformed - * into a different type by the converter. + * Compare argument arrays and return information about whether they match. A supplied + * type converter and conversionAllowed flag allow for matches to take into account + * that a type may be transformed into a different type by the converter. * @param expectedArgTypes the array of types the method/constructor is expecting - * @param suppliedArgTypes the array of types that are being supplied at the point of invocation + * @param suppliedArgTypes the array of types that are being supplied at the point of + * invocation * @param typeConverter a registered type converter - * @return a MatchInfo object indicating what kind of match it was or null if it was not a match + * @return a MatchInfo object indicating what kind of match it was or null if it was + * not a match */ static ArgumentsMatchInfo compareArguments( List expectedArgTypes, List suppliedArgTypes, TypeConverter typeConverter) { @@ -199,6 +201,7 @@ public class ReflectionHelper { } } } + // If already confirmed it cannot be a match, then return if (match == null) { return null; @@ -381,57 +384,74 @@ public class ReflectionHelper { // method the arguments should have been converted to the box form of the required type. Class componentType = requiredParameterTypes[parameterCount-1].getComponentType(); if (componentType.isPrimitive()) { - if (componentType==Integer.TYPE) { - int[] repackagedArguments = (int[]) Array.newInstance(componentType, arraySize); + if (componentType == Integer.TYPE) { + int[] repackagedArguments = (int[]) Array.newInstance(componentType, + arraySize); for (int i = 0; i < arraySize; i++) { - repackagedArguments[i] = ((Integer)args[parameterCount + i - 1]).intValue(); - } - newArgs[newArgs.length - 1] = repackagedArguments; - } else if(componentType==Float.TYPE) { - float[] repackagedArguments = (float[]) Array.newInstance(componentType, arraySize); - for (int i = 0; i < arraySize; i++) { - repackagedArguments[i] = ((Float)args[parameterCount + i - 1]).floatValue(); - } - newArgs[newArgs.length - 1] = repackagedArguments; - } else if(componentType==Double.TYPE) { - double[] repackagedArguments = (double[]) Array.newInstance(componentType, arraySize); - for (int i = 0; i < arraySize; i++) { - repackagedArguments[i] = ((Double)args[parameterCount + i - 1]).doubleValue(); - } - newArgs[newArgs.length - 1] = repackagedArguments; - } else if(componentType==Short.TYPE) { - short[] repackagedArguments = (short[]) Array.newInstance(componentType, arraySize); - for (int i = 0; i < arraySize; i++) { - repackagedArguments[i] = ((Short)args[parameterCount + i - 1]).shortValue(); - } - newArgs[newArgs.length - 1] = repackagedArguments; - } else if(componentType==Character.TYPE) { - char[] repackagedArguments = (char[]) Array.newInstance(componentType, arraySize); - for (int i = 0; i < arraySize; i++) { - repackagedArguments[i] = ((Character)args[parameterCount + i - 1]).charValue(); - } - newArgs[newArgs.length - 1] = repackagedArguments; - } else if(componentType==Byte.TYPE) { - byte[] repackagedArguments = (byte[]) Array.newInstance(componentType, arraySize); - for (int i = 0; i < arraySize; i++) { - repackagedArguments[i] = ((Byte)args[parameterCount + i - 1]).byteValue(); - } - newArgs[newArgs.length - 1] = repackagedArguments; - } else if(componentType==Boolean.TYPE) { - boolean[] repackagedArguments = (boolean[]) Array.newInstance(componentType, arraySize); - for (int i = 0; i < arraySize; i++) { - repackagedArguments[i] = ((Boolean)args[parameterCount + i - 1]).booleanValue(); - } - newArgs[newArgs.length - 1] = repackagedArguments; - } else if(componentType==Long.TYPE) { - long[] repackagedArguments = (long[]) Array.newInstance(componentType, arraySize); - for (int i = 0; i < arraySize; i++) { - repackagedArguments[i] = ((Long)args[parameterCount + i - 1]).longValue(); + repackagedArguments[i] = ((Integer) args[parameterCount + i - 1]).intValue(); } newArgs[newArgs.length - 1] = repackagedArguments; } - } else { - Object[] repackagedArguments = (Object[]) Array.newInstance(componentType, arraySize); + else if (componentType == Float.TYPE) { + float[] repackagedArguments = (float[]) Array.newInstance( + componentType, arraySize); + for (int i = 0; i < arraySize; i++) { + repackagedArguments[i] = ((Float) args[parameterCount + i - 1]).floatValue(); + } + newArgs[newArgs.length - 1] = repackagedArguments; + } + else if (componentType == Double.TYPE) { + double[] repackagedArguments = (double[]) Array.newInstance( + componentType, arraySize); + for (int i = 0; i < arraySize; i++) { + repackagedArguments[i] = ((Double) args[parameterCount + i - 1]).doubleValue(); + } + newArgs[newArgs.length - 1] = repackagedArguments; + } + else if (componentType == Short.TYPE) { + short[] repackagedArguments = (short[]) Array.newInstance( + componentType, arraySize); + for (int i = 0; i < arraySize; i++) { + repackagedArguments[i] = ((Short) args[parameterCount + i - 1]).shortValue(); + } + newArgs[newArgs.length - 1] = repackagedArguments; + } + else if (componentType == Character.TYPE) { + char[] repackagedArguments = (char[]) Array.newInstance( + componentType, arraySize); + for (int i = 0; i < arraySize; i++) { + repackagedArguments[i] = ((Character) args[parameterCount + i - 1]).charValue(); + } + newArgs[newArgs.length - 1] = repackagedArguments; + } + else if (componentType == Byte.TYPE) { + byte[] repackagedArguments = (byte[]) Array.newInstance( + componentType, arraySize); + for (int i = 0; i < arraySize; i++) { + repackagedArguments[i] = ((Byte) args[parameterCount + i - 1]).byteValue(); + } + newArgs[newArgs.length - 1] = repackagedArguments; + } + else if (componentType == Boolean.TYPE) { + boolean[] repackagedArguments = (boolean[]) Array.newInstance( + componentType, arraySize); + for (int i = 0; i < arraySize; i++) { + repackagedArguments[i] = ((Boolean) args[parameterCount + i - 1]).booleanValue(); + } + newArgs[newArgs.length - 1] = repackagedArguments; + } + else if (componentType == Long.TYPE) { + long[] repackagedArguments = (long[]) Array.newInstance( + componentType, arraySize); + for (int i = 0; i < arraySize; i++) { + repackagedArguments[i] = ((Long) args[parameterCount + i - 1]).longValue(); + } + newArgs[newArgs.length - 1] = repackagedArguments; + } + } + else { + Object[] repackagedArguments = (Object[]) Array.newInstance( + componentType, arraySize); // Copy all but the varargs arguments for (int i = 0; i < arraySize; i++) { repackagedArguments[i] = args[parameterCount + i - 1]; diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveConstructorExecutor.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveConstructorExecutor.java index e2601c893b..46202c5274 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveConstructorExecutor.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveConstructorExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,8 @@ import org.springframework.expression.TypedValue; import org.springframework.util.ReflectionUtils; /** - * A simple ConstructorExecutor implementation that runs a constructor using reflective invocation. + * A simple ConstructorExecutor implementation that runs a constructor using reflective + * invocation. * * @author Andy Clement * @author Juergen Hoeller diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveConstructorResolver.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveConstructorResolver.java index 36d48cbf87..3d26c61c49 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveConstructorResolver.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveConstructorResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,7 +32,8 @@ import org.springframework.expression.EvaluationException; import org.springframework.expression.TypeConverter; /** - * A constructor resolver that uses reflection to locate the constructor that should be invoked + * A constructor resolver that uses reflection to locate the constructor that should be + * invoked * * @author Andy Clement * @author Juergen Hoeller @@ -43,10 +44,12 @@ public class ReflectiveConstructorResolver implements ConstructorResolver { /** * Locate a constructor on the type. There are three kinds of match that might occur: *
          - *
        1. An exact match where the types of the arguments match the types of the constructor - *
        2. An in-exact match where the types we are looking for are subtypes of those defined on the constructor - *
        3. A match where we are able to convert the arguments into those expected by the constructor, according to the - * registered type converter. + *
        4. An exact match where the types of the arguments match the types of the + * constructor + *
        5. An in-exact match where the types we are looking for are subtypes of those + * defined on the constructor + *
        6. A match where we are able to convert the arguments into those expected by the + * constructor, according to the registered type converter. *
        */ @Override 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 fe2f5aa1d1..eb195f12e2 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 @@ -42,8 +42,8 @@ import org.springframework.expression.spel.SpelEvaluationException; import org.springframework.expression.spel.SpelMessage; /** - * Reflection-based {@link MethodResolver} used by default in {@link StandardEvaluationContext} - * unless explicit method resolvers have been specified. + * Reflection-based {@link MethodResolver} used by default in + * {@link StandardEvaluationContext} unless explicit method resolvers have been specified. * * @author Andy Clement * @author Juergen Hoeller @@ -53,8 +53,6 @@ import org.springframework.expression.spel.SpelMessage; */ 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, @@ -78,6 +76,7 @@ public class ReflectiveMethodResolver implements MethodResolver { this.useDistance = useDistance; } + /** * Locate a method on a type. There are three kinds of match that might occur: *
          diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java index ca38218661..ddf3ad8631 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java @@ -38,9 +38,9 @@ import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; /** - * Simple PropertyAccessor that uses reflection to access properties for reading and writing. - * A property can be accessed if it is accessible as a field on the object or through a - * getter (if being read) or a setter (if being written). + * Simple PropertyAccessor that uses reflection to access properties for reading and + * writing. A property can be accessed if it is accessible as a field on the object or + * through a getter (if being read) or a setter (if being written). * * @author Andy Clement * @author Juergen Hoeller diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardEvaluationContext.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardEvaluationContext.java index ce03b4927c..7f11c0d7b3 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardEvaluationContext.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardEvaluationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -242,8 +242,8 @@ public class StandardEvaluationContext implements EvaluationContext { */ public void registerMethodFilter(Class type, MethodFilter filter) throws IllegalStateException { ensureMethodResolversInitialized(); - if (reflectiveMethodResolver != null) { - reflectiveMethodResolver.registerMethodFilter(type, filter); + if (this.reflectiveMethodResolver != null) { + this.reflectiveMethodResolver.registerMethodFilter(type, filter); } else { throw new IllegalStateException("Method filter cannot be set as the reflective method resolver is not in use"); } @@ -272,7 +272,7 @@ public class StandardEvaluationContext implements EvaluationContext { private synchronized void initializeMethodResolvers() { if (this.methodResolvers == null) { List defaultResolvers = new ArrayList(); - defaultResolvers.add(reflectiveMethodResolver = new ReflectiveMethodResolver()); + defaultResolvers.add(this.reflectiveMethodResolver = new ReflectiveMethodResolver()); this.methodResolvers = defaultResolvers; } } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardTypeComparator.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardTypeComparator.java index 92aafb28bc..fad32af97d 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardTypeComparator.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardTypeComparator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,8 @@ import org.springframework.expression.spel.SpelEvaluationException; import org.springframework.expression.spel.SpelMessage; /** - * A simple basic TypeComparator implementation. It supports comparison of numbers and types implementing Comparable. + * A simple basic TypeComparator implementation. It supports comparison of numbers and + * types implementing Comparable. * * @author Andy Clement * @author Juergen Hoeller @@ -35,7 +36,8 @@ public class StandardTypeComparator implements TypeComparator { // If one is null, check if the other is if (left == null) { return right == null ? 0 : -1; - } else if (right == null) { + } + else if (right == null) { return 1; // left cannot be null } @@ -46,20 +48,24 @@ public class StandardTypeComparator implements TypeComparator { if (leftNumber instanceof Double || rightNumber instanceof Double) { double d1 = leftNumber.doubleValue(); double d2 = rightNumber.doubleValue(); - return Double.compare(d1,d2); - } else if (leftNumber instanceof Float || rightNumber instanceof Float) { + return Double.compare(d1, d2); + } + + if (leftNumber instanceof Float || rightNumber instanceof Float) { float f1 = leftNumber.floatValue(); float f2 = rightNumber.floatValue(); - return Float.compare(f1,f2); - } else if (leftNumber instanceof Long || rightNumber instanceof Long) { + return Float.compare(f1, f2); + } + + if (leftNumber instanceof Long || rightNumber instanceof Long) { Long l1 = leftNumber.longValue(); Long l2 = rightNumber.longValue(); return l1.compareTo(l2); - } else { - Integer i1 = leftNumber.intValue(); - Integer i2 = rightNumber.intValue(); - return i1.compareTo(i2); } + + Integer i1 = leftNumber.intValue(); + Integer i2 = rightNumber.intValue(); + return i1.compareTo(i2); } try { @@ -78,12 +84,15 @@ public class StandardTypeComparator implements TypeComparator { if (left == null || right == null) { return true; } + if (left instanceof Number && right instanceof Number) { return true; } + if (left instanceof Comparable) { return true; } + return false; } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java index 0500dde5d5..2104b058c3 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,8 +27,8 @@ import org.springframework.expression.spel.SpelMessage; import org.springframework.util.Assert; /** - * Default implementation of the {@link TypeConverter} interface, - * delegating to a core Spring {@link ConversionService}. + * Default implementation of the {@link TypeConverter} interface, delegating to a core + * Spring {@link ConversionService}. * * @author Juergen Hoeller * @author Andy Clement diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardTypeLocator.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardTypeLocator.java index 319985fe8a..8fea968184 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardTypeLocator.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardTypeLocator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,8 +27,9 @@ import org.springframework.expression.spel.SpelMessage; import org.springframework.util.ClassUtils; /** - * A default implementation of a TypeLocator that uses the context classloader (or any classloader set upon it). It - * supports 'well known' packages so if a type cannot be found it will try the registered imports to locate it. + * A default implementation of a TypeLocator that uses the context classloader (or any + * classloader set upon it). It supports 'well known' packages so if a type cannot be + * found it will try the registered imports to locate it. * * @author Andy Clement * @author Juergen Hoeller @@ -36,7 +37,7 @@ import org.springframework.util.ClassUtils; */ public class StandardTypeLocator implements TypeLocator { - private ClassLoader loader; + private final ClassLoader loader; private final List knownPackagePrefixes = new ArrayList(); diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/CachedMethodExecutorTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/CachedMethodExecutorTests.java new file mode 100644 index 0000000000..75e76a5903 --- /dev/null +++ b/spring-expression/src/test/java/org/springframework/expression/spel/CachedMethodExecutorTests.java @@ -0,0 +1,75 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.expression.spel; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.ast.MethodReference; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; + +import static org.junit.Assert.*; + +/** + * Test for caching in {@link MethodReference} (SPR-10657). + * + * @author Oliver Becker + */ +public class CachedMethodExecutorTests { + + private final ExpressionParser parser = new SpelExpressionParser(); + + private StandardEvaluationContext context; + + + @Before + public void setUp() throws Exception { + this.context = new StandardEvaluationContext(new RootObject()); + } + + + @Test + public void testCachedExecution() throws Exception { + Expression expression = this.parser.parseExpression("echo(#something)"); + + assertMethodExecution(expression, 42, "int: 42"); + assertMethodExecution(expression, 42, "int: 42"); + assertMethodExecution(expression, "Deep Thought", "String: Deep Thought"); + assertMethodExecution(expression, 42, "int: 42"); + } + + private void assertMethodExecution(Expression expression, Object var, String expected) { + this.context.setVariable("something", var); + assertEquals(expected, expression.getValue(this.context)); + } + + + public static class RootObject { + + public String echo(String value) { + return "String: " + value; + } + + public String echo(int value) { + return "int: " + value; + } + + } + +} diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java index 89092f58ea..78e0a92fdc 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java @@ -20,6 +20,7 @@ import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; +import java.sql.BatchUpdateException; import java.sql.CallableStatement; import java.sql.Connection; import java.sql.PreparedStatement; @@ -34,6 +35,7 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; + import javax.sql.DataSource; import org.springframework.dao.DataAccessException; @@ -49,6 +51,7 @@ import org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor; import org.springframework.jdbc.support.rowset.SqlRowSet; import org.springframework.util.Assert; import org.springframework.util.LinkedCaseInsensitiveMap; +import org.springframework.util.StringUtils; /** * This is the central class in the JDBC core package. @@ -544,21 +547,42 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { @Override public int[] batchUpdate(final String[] sql) throws DataAccessException { + Assert.notEmpty(sql, "SQL array must not be empty"); + if (logger.isDebugEnabled()) { logger.debug("Executing SQL batch update of " + sql.length + " statements"); } + class BatchUpdateStatementCallback implements StatementCallback, SqlProvider { + private String currSql; + @Override public int[] doInStatement(Statement stmt) throws SQLException, DataAccessException { + int[] rowsAffected = new int[sql.length]; + if (JdbcUtils.supportsBatchUpdates(stmt.getConnection())) { for (String sqlStmt : sql) { - this.currSql = sqlStmt; + this.currSql = appendSql(this.currSql, sqlStmt); stmt.addBatch(sqlStmt); } - rowsAffected = stmt.executeBatch(); + try { + rowsAffected = stmt.executeBatch(); + } + catch (BatchUpdateException ex) { + String batchExceptionSql = null; + for (int i = 0; i < ex.getUpdateCounts().length; i++) { + if (ex.getUpdateCounts()[i] == Statement.EXECUTE_FAILED) { + batchExceptionSql = appendSql(batchExceptionSql, sql[i]); + } + } + if (StringUtils.hasLength(batchExceptionSql)) { + this.currSql = batchExceptionSql; + } + throw ex; + } } else { for (int i = 0; i < sql.length; i++) { @@ -573,6 +597,11 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { } return rowsAffected; } + + private String appendSql(String sql, String statement) { + return (StringUtils.isEmpty(sql) ? statement : sql + "; " + statement); + } + @Override public String getSql() { return this.currSql; diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateTests.java index ffa2d74ddf..e8d9e19c51 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateTests.java @@ -16,6 +16,7 @@ package org.springframework.jdbc.core; +import java.sql.BatchUpdateException; import java.sql.CallableStatement; import java.sql.Connection; import java.sql.DatabaseMetaData; @@ -458,6 +459,24 @@ public class JdbcTemplateTests { verify(this.connection, atLeastOnce()).close(); } + @Test + public void testBatchUpdateWithBatchFailure() throws Exception { + final String[] sql = {"A", "B", "C", "D"}; + given(this.statement.executeBatch()).willThrow( + new BatchUpdateException(new int[] { 1, Statement.EXECUTE_FAILED, 1, + Statement.EXECUTE_FAILED })); + mockDatabaseMetaData(true); + given(this.connection.createStatement()).willReturn(this.statement); + + JdbcTemplate template = new JdbcTemplate(this.dataSource, false); + try { + template.batchUpdate(sql); + } + catch (UncategorizedSQLException ex) { + assertThat(ex.getSql(), equalTo("B; D")); + } + } + @Test public void testBatchUpdateWithNoBatchSupport() throws Exception { final String[] sql = {"UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = 1", diff --git a/spring-orm/src/main/java/org/springframework/orm/jdo/JdoTransactionManager.java b/spring-orm/src/main/java/org/springframework/orm/jdo/JdoTransactionManager.java index f2086038ca..296582f2eb 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jdo/JdoTransactionManager.java +++ b/spring-orm/src/main/java/org/springframework/orm/jdo/JdoTransactionManager.java @@ -154,7 +154,7 @@ public class JdoTransactionManager extends AbstractPlatformTransactionManager * The DataSource should match the one used by the JDO PersistenceManagerFactory: * for example, you could specify the same JNDI DataSource for both. *

          If the PersistenceManagerFactory uses a DataSource as connection factory, - * the DataSource will be autodetected: You can still explictly specify the + * the DataSource will be autodetected: You can still explicitly specify the * DataSource, but you don't need to in this case. *

          A transactional JDBC Connection for this DataSource will be provided to * application code accessing this DataSource directly via DataSourceUtils diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/JpaTransactionManager.java b/spring-orm/src/main/java/org/springframework/orm/jpa/JpaTransactionManager.java index 49d2cea7cc..1f424977a4 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/JpaTransactionManager.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/JpaTransactionManager.java @@ -216,7 +216,7 @@ public class JpaTransactionManager extends AbstractPlatformTransactionManager * The DataSource should match the one used by the JPA EntityManagerFactory: * for example, you could specify the same JNDI DataSource for both. *

          If the EntityManagerFactory uses a known DataSource as connection factory, - * the DataSource will be autodetected: You can still explictly specify the + * the DataSource will be autodetected: You can still explicitly specify the * DataSource, but you don't need to in this case. *

          A transactional JDBC Connection for this DataSource will be provided to * application code accessing this DataSource directly via DataSourceUtils @@ -258,7 +258,7 @@ public class JpaTransactionManager extends AbstractPlatformTransactionManager * Set the JPA dialect to use for this transaction manager. * Used for vendor-specific transaction management and JDBC connection exposure. *

          If the EntityManagerFactory uses a known JpaDialect, it will be autodetected: - * You can still explictly specify the DataSource, but you don't need to in this case. + * You can still explicitly specify the DataSource, but you don't need to in this case. *

          The dialect object can be used to retrieve the underlying JDBC connection * and thus allows for exposing JPA transactions as JDBC transactions. * @see EntityManagerFactoryInfo#getJpaDialect() diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilder.java b/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilder.java index 3a7d016980..142131fcf5 100644 --- a/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilder.java +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilder.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; + import javax.servlet.ServletContext; import javax.servlet.ServletRequest; import javax.servlet.http.Cookie; @@ -188,6 +189,21 @@ public class MockHttpServletRequestBuilder implements RequestBuilder, Mergeable return this; } + /** + * Set the 'Accept' header to the given media type(s). + * + * @param mediaTypes one or more media types + */ + public MockHttpServletRequestBuilder accept(String... mediaTypes) { + Assert.notEmpty(mediaTypes, "No 'Accept' media types"); + List result = new ArrayList(mediaTypes.length); + for (String mediaType : mediaTypes) { + result.add(MediaType.parseMediaType(mediaType)); + } + this.headers.set("Accept", MediaType.toString(result)); + return this; + } + /** * Set the request body. * diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/result/HeaderResultMatchers.java b/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/result/HeaderResultMatchers.java index 5e0f5b2a1d..0fef4e43fd 100644 --- a/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/result/HeaderResultMatchers.java +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/result/HeaderResultMatchers.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.springframework.test.web.servlet.result; import static org.springframework.test.util.AssertionErrors.assertEquals; +import static org.springframework.test.util.AssertionErrors.assertTrue; import static org.springframework.test.util.MatcherAssertionErrors.assertThat; import org.hamcrest.Matcher; @@ -28,6 +29,7 @@ import org.springframework.test.web.servlet.ResultMatcher; * class is usually accessed via {@link MockMvcResultMatchers#header()}. * * @author Rossen Stoyanchev + * @author Sam Brannen * @since 3.2 */ public class HeaderResultMatchers { @@ -41,36 +43,45 @@ public class HeaderResultMatchers { } /** - * Assert a response header with the given Hamcrest {@link Matcher}. + * Assert the primary value of the named response header with the given + * Hamcrest {@link Matcher}. */ public ResultMatcher string(final String name, final Matcher matcher) { return new ResultMatcher() { + @Override public void match(MvcResult result) { - assertThat("Response header", result.getResponse().getHeader(name), matcher); + assertThat("Response header " + name, result.getResponse().getHeader(name), matcher); } }; } /** - * Assert the primary value of a response header as a {@link String}. + * Assert the primary value of the named response header as a {@link String}. */ public ResultMatcher string(final String name, final String value) { return new ResultMatcher() { + @Override public void match(MvcResult result) { - assertEquals("Response header", value, result.getResponse().getHeader(name)); + assertEquals("Response header " + name, value, result.getResponse().getHeader(name)); } }; } /** - * Assert the primary value of a response header as a {@link Long}. + * Assert the primary value of the named response header as a {@code long}. + * + *

          The {@link ResultMatcher} returned by this method throws an {@link AssertionError} + * if the response does not contain the specified header, or if the supplied + * {@code value} does not match the primary value. */ public ResultMatcher longValue(final String name, final long value) { return new ResultMatcher() { + @Override public void match(MvcResult result) { + assertTrue("Response does not contain header " + name, result.getResponse().containsHeader(name)); assertEquals("Response header " + name, value, Long.parseLong(result.getResponse().getHeader(name))); } }; diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/HeaderAssertionTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/HeaderAssertionTests.java index d8bfe0c780..1a3f56771c 100644 --- a/spring-test-mvc/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/HeaderAssertionTests.java +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/HeaderAssertionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,72 +16,154 @@ package org.springframework.test.web.servlet.samples.standalone.resultmatchers; -import static org.hamcrest.Matchers.nullValue; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup; - -import java.util.Date; - import org.junit.Before; import org.junit.Test; import org.springframework.stereotype.Controller; import org.springframework.test.web.Person; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultMatcher; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.context.request.WebRequest; +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*; + /** * Examples of expectations on response header values. - * + * * @author Rossen Stoyanchev + * @author Sam Brannen */ public class HeaderAssertionTests { + private static final String EXPECTED_ASSERTION_ERROR_MSG = "Should have thrown an AssertionError"; + + private static final String IF_MODIFIED_SINCE = "If-Modified-Since"; + + private static final String LAST_MODIFIED = "Last-Modified"; + + private final long currentTime = System.currentTimeMillis(); + private MockMvc mockMvc; private PersonController personController; + @Before public void setup() { this.personController = new PersonController(); + this.personController.setStubTimestamp(currentTime); this.mockMvc = standaloneSetup(this.personController).build(); } @Test - public void testValue() throws Exception { - long currentTime = new Date().getTime(); - this.personController.setStubTimestamp(currentTime); - this.mockMvc.perform(get("/persons/1").header("If-Modified-Since", currentTime - (1000 * 60))) - .andExpect(header().string("Last-Modified", String.valueOf(currentTime))); + public void stringWithCorrectResponseHeaderValue() throws Exception { + this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, currentTime - (1000 * 60)))// + .andExpect(header().string(LAST_MODIFIED, String.valueOf(currentTime))); } @Test - public void testLongValue() throws Exception { - long currentTime = new Date().getTime(); - this.personController.setStubTimestamp(currentTime); - this.mockMvc.perform(get("/persons/1").header("If-Modified-Since", currentTime - (1000 * 60))) - .andExpect(header().longValue("Last-Modified", currentTime)); + public void stringWithMatcherAndCorrectResponseHeaderValue() throws Exception { + this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, currentTime - (1000 * 60)))// + .andExpect(header().string(LAST_MODIFIED, equalTo(String.valueOf(currentTime)))); } @Test - public void testMatcher() throws Exception { - long currentTime = new Date().getTime(); - this.personController.setStubTimestamp(currentTime); - this.mockMvc.perform(get("/persons/1").header("If-Modified-Since", currentTime)) - .andExpect(status().isNotModified()) - .andExpect(header().string("Last-Modified", nullValue())); + public void longValueWithCorrectResponseHeaderValue() throws Exception { + this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, currentTime - (1000 * 60)))// + .andExpect(header().longValue(LAST_MODIFIED, currentTime)); } + @Test + public void stringWithMissingResponseHeader() throws Exception { + this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, currentTime))// + .andExpect(status().isNotModified())// + .andExpect(header().string(LAST_MODIFIED, (String) null)); + } + + @Test + public void stringWithMatcherAndMissingResponseHeader() throws Exception { + this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, currentTime))// + .andExpect(status().isNotModified())// + .andExpect(header().string(LAST_MODIFIED, nullValue())); + } + + @Test + public void longValueWithMissingResponseHeader() throws Exception { + try { + this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, currentTime))// + .andExpect(status().isNotModified())// + .andExpect(header().longValue(LAST_MODIFIED, 99L)); + + fail(EXPECTED_ASSERTION_ERROR_MSG); + } + catch (AssertionError e) { + if (EXPECTED_ASSERTION_ERROR_MSG.equals(e.getMessage())) { + throw e; + } + assertEquals("Response does not contain header " + LAST_MODIFIED, e.getMessage()); + } + } + + @Test + public void stringWithIncorrectResponseHeaderValue() throws Exception { + long unexpected = currentTime + 1; + assertIncorrectResponseHeaderValue(header().string(LAST_MODIFIED, String.valueOf(unexpected)), unexpected); + } + + @Test + public void stringWithMatcherAndIncorrectResponseHeaderValue() throws Exception { + long unexpected = currentTime + 1; + assertIncorrectResponseHeaderValue(header().string(LAST_MODIFIED, equalTo(String.valueOf(unexpected))), + unexpected); + } + + @Test + public void longValueWithIncorrectResponseHeaderValue() throws Exception { + long unexpected = currentTime + 1; + assertIncorrectResponseHeaderValue(header().longValue(LAST_MODIFIED, unexpected), unexpected); + } + + private void assertIncorrectResponseHeaderValue(ResultMatcher resultMatcher, long unexpected) throws Exception { + try { + this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, currentTime - (1000 * 60)))// + .andExpect(resultMatcher); + + fail(EXPECTED_ASSERTION_ERROR_MSG); + } + catch (AssertionError e) { + if (EXPECTED_ASSERTION_ERROR_MSG.equals(e.getMessage())) { + throw e; + } + // [SPR-10659] Ensure that the header name is included in the message + // + // We don't use assertEquals() since we cannot control the formatting + // produced by JUnit or Hamcrest. + assertMessageContains(e, "Response header " + LAST_MODIFIED); + assertMessageContains(e, String.valueOf(unexpected)); + assertMessageContains(e, String.valueOf(currentTime)); + } + } + + private void assertMessageContains(AssertionError error, String expected) { + String message = error.getMessage(); + assertTrue("Failure message should contain: " + expected, message.contains(expected)); + } + + + // ------------------------------------------------------------------------- @Controller private static class PersonController { private long timestamp; + public void setStubTimestamp(long timestamp) { this.timestamp = timestamp; } diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletRequest.java b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletRequest.java index 533d52064a..8659f78806 100644 --- a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletRequest.java +++ b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletRequest.java @@ -36,6 +36,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; + import javax.servlet.AsyncContext; import javax.servlet.DispatcherType; import javax.servlet.RequestDispatcher; @@ -248,8 +249,8 @@ public class MockHttpServletRequest implements HttpServletRequest { */ public MockHttpServletRequest(ServletContext servletContext, String method, String requestURI) { this.servletContext = (servletContext != null ? servletContext : new MockServletContext()); - this.method = method; - this.requestURI = requestURI; + this.method = (method == null ? "" : method); + this.requestURI = (requestURI == null ? "" : requestURI); this.locales.add(Locale.ENGLISH); } @@ -858,7 +859,7 @@ public class MockHttpServletRequest implements HttpServletRequest { } public void setMethod(String method) { - this.method = method; + this.method = (method == null ? "" : method); } @Override @@ -936,7 +937,7 @@ public class MockHttpServletRequest implements HttpServletRequest { } public void setRequestURI(String requestURI) { - this.requestURI = requestURI; + this.requestURI = (requestURI == null ? "" : requestURI); } @Override @@ -946,8 +947,13 @@ public class MockHttpServletRequest implements HttpServletRequest { @Override public StringBuffer getRequestURL() { - StringBuffer url = new StringBuffer(this.scheme); - url.append("://").append(this.serverName).append(':').append(this.serverPort); + StringBuffer url = new StringBuffer(this.scheme).append("://").append(this.serverName); + + if (this.serverPort > 0 + && (("http".equalsIgnoreCase(scheme) && this.serverPort != 80) || ("https".equalsIgnoreCase(scheme) && this.serverPort != 443))) { + url.append(':').append(this.serverPort); + } + url.append(getRequestURI()); return url; } diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockHttpSession.java b/spring-test/src/main/java/org/springframework/mock/web/MockHttpSession.java index 5daf16d36e..513f64ad56 100644 --- a/spring-test/src/main/java/org/springframework/mock/web/MockHttpSession.java +++ b/spring-test/src/main/java/org/springframework/mock/web/MockHttpSession.java @@ -102,6 +102,7 @@ public class MockHttpSession implements HttpSession { @Override public long getCreationTime() { + assertIsValid(); return this.creationTime; } @@ -117,6 +118,7 @@ public class MockHttpSession implements HttpSession { @Override public long getLastAccessedTime() { + assertIsValid(); return this.lastAccessedTime; } @@ -142,6 +144,7 @@ public class MockHttpSession implements HttpSession { @Override public Object getAttribute(String name) { + assertIsValid(); Assert.notNull(name, "Attribute name must not be null"); return this.attributes.get(name); } @@ -153,16 +156,19 @@ public class MockHttpSession implements HttpSession { @Override public Enumeration getAttributeNames() { + assertIsValid(); return Collections.enumeration(new LinkedHashSet(this.attributes.keySet())); } @Override public String[] getValueNames() { + assertIsValid(); return this.attributes.keySet().toArray(new String[this.attributes.size()]); } @Override public void setAttribute(String name, Object value) { + assertIsValid(); Assert.notNull(name, "Attribute name must not be null"); if (value != null) { this.attributes.put(name, value); @@ -182,6 +188,7 @@ public class MockHttpSession implements HttpSession { @Override public void removeAttribute(String name) { + assertIsValid(); Assert.notNull(name, "Attribute name must not be null"); Object value = this.attributes.remove(name); if (value instanceof HttpSessionBindingListener) { @@ -216,11 +223,7 @@ public class MockHttpSession implements HttpSession { */ @Override public void invalidate() { - if (this.invalid) { - throw new IllegalStateException("The session has already been invalidated"); - } - - // else + assertIsValid(); this.invalid = true; clearAttributes(); } @@ -229,12 +232,25 @@ public class MockHttpSession implements HttpSession { return this.invalid; } + /** + * Convenience method for asserting that this session has not been + * {@linkplain #invalidate() invalidated}. + * + * @throws IllegalStateException if this session has been invalidated + */ + private void assertIsValid() { + if (isInvalid()) { + throw new IllegalStateException("The session has already been invalidated"); + } + } + public void setNew(boolean value) { this.isNew = value; } @Override public boolean isNew() { + assertIsValid(); return this.isNew; } diff --git a/spring-test/src/main/java/org/springframework/test/context/ActiveProfiles.java b/spring-test/src/main/java/org/springframework/test/context/ActiveProfiles.java index 39bfb5d3cd..0b76fa4231 100644 --- a/spring-test/src/main/java/org/springframework/test/context/ActiveProfiles.java +++ b/spring-test/src/main/java/org/springframework/test/context/ActiveProfiles.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,6 +34,7 @@ import java.lang.annotation.Target; * @see SmartContextLoader * @see MergedContextConfiguration * @see ContextConfiguration + * @see ActiveProfilesResolver * @see org.springframework.context.ApplicationContext * @see org.springframework.context.annotation.Profile */ @@ -47,8 +48,8 @@ public @interface ActiveProfiles { * Alias for {@link #profiles}. * *

          This attribute may not be used in conjunction - * with {@link #profiles}, but it may be used instead of - * {@link #profiles}. + * with {@link #profiles} or {@link #resolver}, but it may be used + * instead of them. */ String[] value() default {}; @@ -56,11 +57,24 @@ public @interface ActiveProfiles { * The bean definition profiles to activate. * *

          This attribute may not be used in conjunction - * with {@link #value}, but it may be used instead of - * {@link #value}. + * with {@link #value} or {@link #resolver}, but it may be used + * instead of them. */ String[] profiles() default {}; + /** + * The type of {@link ActiveProfilesResolver} to use for resolving the active + * bean definition profiles programmatically. + * + *

          This attribute may not be used in conjunction + * with {@link #profiles} or {@link #value}, but it may be used instead + * of them in order to resolve the active profiles programmatically. + * + * @since 4.0 + * @see ActiveProfilesResolver + */ + Class resolver() default ActiveProfilesResolver.class; + /** * Whether or not bean definition profiles from superclasses should be * inherited. diff --git a/spring-test/src/main/java/org/springframework/test/context/ActiveProfilesResolver.java b/spring-test/src/main/java/org/springframework/test/context/ActiveProfilesResolver.java new file mode 100644 index 0000000000..0985ebb02c --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/ActiveProfilesResolver.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context; + +/** + * Strategy interface for programmatically resolving which active bean + * definition profiles should be used when loading an + * {@link org.springframework.context.ApplicationContext ApplicationContext} + * for a test class. + * + *

          A custom {@code ActiveProfilesResolver} can be registered via the + * {@link ActiveProfiles#resolver resolver} attribute of {@code @ActiveProfiles}. + * + *

          Concrete implementations must provide a {@code public} no-args constructor. + * + * @author Sam Brannen + * @author Michail Nikolaev + * @since 4.0 + * @see ActiveProfiles + */ +public interface ActiveProfilesResolver { + + /** + * Resolve the bean definition profiles to use when loading an + * {@code ApplicationContext} for the given {@linkplain Class test class}. + * + * @param testClass the test class for which the profiles should be resolved; + * never {@code null} + * @return the list of bean definition profiles to use when loading the + * {@code ApplicationContext}; never {@code null} + * @see ActiveProfiles#resolver + * @see ActiveProfiles#inheritProfiles + */ + String[] resolve(Class testClass); + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java b/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java index a43897eb0b..92f5e2c764 100644 --- a/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java @@ -16,11 +16,6 @@ package org.springframework.test.context; -import static org.springframework.beans.BeanUtils.instantiateClass; -import static org.springframework.core.annotation.AnnotationUtils.findAnnotationDeclaringClass; -import static org.springframework.core.annotation.AnnotationUtils.findAnnotationDeclaringClassForTypes; -import static org.springframework.core.annotation.AnnotationUtils.isAnnotationDeclaredLocally; - import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.util.ArrayList; @@ -42,6 +37,9 @@ import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; +import static org.springframework.beans.BeanUtils.*; +import static org.springframework.core.annotation.AnnotationUtils.*; + /** * Utility methods for working with {@link ContextLoader ContextLoaders} and * {@link SmartContextLoader SmartContextLoaders} and resolving resource locations, @@ -49,12 +47,14 @@ import org.springframework.util.StringUtils; * initializers. * * @author Sam Brannen + * @author Michail Nikolaev * @since 3.1 * @see ContextLoader * @see SmartContextLoader * @see ContextConfiguration * @see ContextConfigurationAttributes * @see ActiveProfiles + * @see ActiveProfilesResolver * @see ApplicationContextInitializer * @see ContextHierarchy * @see MergedContextConfiguration @@ -477,24 +477,41 @@ abstract class ContextLoaderUtils { while (declaringClass != null) { ActiveProfiles annotation = declaringClass.getAnnotation(annotationType); - if (logger.isTraceEnabled()) { logger.trace(String.format("Retrieved @ActiveProfiles [%s] for declaring class [%s].", annotation, declaringClass.getName())); } + validateActiveProfilesConfiguration(declaringClass, annotation); String[] profiles = annotation.profiles(); String[] valueProfiles = annotation.value(); + Class resolverClass = annotation.resolver(); - if (!ObjectUtils.isEmpty(valueProfiles) && !ObjectUtils.isEmpty(profiles)) { - String msg = String.format("Test class [%s] has been configured with @ActiveProfiles' 'value' [%s] " - + "and 'profiles' [%s] attributes. Only one declaration of active bean " - + "definition profiles is permitted per @ActiveProfiles annotation.", declaringClass.getName(), - ObjectUtils.nullSafeToString(valueProfiles), ObjectUtils.nullSafeToString(profiles)); - logger.error(msg); - throw new IllegalStateException(msg); + boolean resolverDeclared = !ActiveProfilesResolver.class.equals(resolverClass); + boolean valueDeclared = !ObjectUtils.isEmpty(valueProfiles); + + if (resolverDeclared) { + ActiveProfilesResolver resolver = null; + try { + resolver = instantiateClass(resolverClass, ActiveProfilesResolver.class); + } + catch (Exception e) { + String msg = String.format("Could not instantiate ActiveProfilesResolver of " + + "type [%s] for test class [%s].", resolverClass.getName(), declaringClass.getName()); + logger.error(msg); + throw new IllegalStateException(msg, e); + } + + profiles = resolver.resolve(declaringClass); + if (profiles == null) { + String msg = String.format( + "ActiveProfilesResolver [%s] returned a null array of bean definition profiles.", + resolverClass.getName()); + logger.error(msg); + throw new IllegalStateException(msg); + } } - else if (!ObjectUtils.isEmpty(valueProfiles)) { + else if (valueDeclared) { profiles = valueProfiles; } @@ -511,6 +528,43 @@ abstract class ContextLoaderUtils { return StringUtils.toStringArray(activeProfiles); } + private static void validateActiveProfilesConfiguration(Class declaringClass, ActiveProfiles annotation) { + String[] valueProfiles = annotation.value(); + String[] profiles = annotation.profiles(); + Class resolverClass = annotation.resolver(); + boolean valueDeclared = !ObjectUtils.isEmpty(valueProfiles); + boolean profilesDeclared = !ObjectUtils.isEmpty(profiles); + boolean resolverDeclared = !ActiveProfilesResolver.class.equals(resolverClass); + + String msg = null; + + if (valueDeclared && profilesDeclared) { + msg = String.format("Test class [%s] has been configured with @ActiveProfiles' 'value' [%s] " + + "and 'profiles' [%s] attributes. Only one declaration of active bean " + + "definition profiles is permitted per @ActiveProfiles annotation.", declaringClass.getName(), + ObjectUtils.nullSafeToString(valueProfiles), ObjectUtils.nullSafeToString(profiles)); + } + else if (valueDeclared && resolverDeclared) { + msg = String.format("Test class [%s] has been configured with @ActiveProfiles' 'value' [%s] " + + "and 'resolver' [%s] attributes. Only one source of active bean " + + "definition profiles is permitted per @ActiveProfiles annotation, " + + "either declaritively or programmatically.", declaringClass.getName(), + ObjectUtils.nullSafeToString(valueProfiles), resolverClass.getName()); + } + else if (profilesDeclared && resolverDeclared) { + msg = String.format("Test class [%s] has been configured with @ActiveProfiles' 'profiles' [%s] " + + "and 'resolver' [%s] attributes. Only one source of active bean " + + "definition profiles is permitted per @ActiveProfiles annotation, " + + "either declaritively or programmatically.", declaringClass.getName(), + ObjectUtils.nullSafeToString(profiles), resolverClass.getName()); + } + + if (msg != null) { + logger.error(msg); + throw new IllegalStateException(msg); + } + } + /** * Build the {@link MergedContextConfiguration merged context configuration} for * the supplied {@link Class testClass} and {@code defaultContextLoaderClassName}, diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/AbstractTransactionalJUnit4SpringContextTests.java b/spring-test/src/main/java/org/springframework/test/context/junit4/AbstractTransactionalJUnit4SpringContextTests.java index 335497aa51..c1494050a2 100644 --- a/spring-test/src/main/java/org/springframework/test/context/junit4/AbstractTransactionalJUnit4SpringContextTests.java +++ b/spring-test/src/main/java/org/springframework/test/context/junit4/AbstractTransactionalJUnit4SpringContextTests.java @@ -86,8 +86,7 @@ public abstract class AbstractTransactionalJUnit4SpringContextTests extends Abst /** * Set the {@code DataSource}, typically provided via Dependency Injection. - *

          This method also instantiates the {@link #jdbcTemplate} instance - * variable. + *

          This method also instantiates the {@link #jdbcTemplate} instance variable. */ @Autowired public void setDataSource(DataSource dataSource) { @@ -103,49 +102,75 @@ public abstract class AbstractTransactionalJUnit4SpringContextTests extends Abst } /** - * Count the rows in the given table. + * Convenience method for counting the rows in the given table. * @param tableName table name to count rows in * @return the number of rows in the table + * @see JdbcTestUtils#countRowsInTable */ protected int countRowsInTable(String tableName) { return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName); } /** - * Count the rows in the given table, using the provided {@code WHERE} clause. + * Convenience method for counting the rows in the given table, using the + * provided {@code WHERE} clause. *

          See the Javadoc for {@link JdbcTestUtils#countRowsInTableWhere} for details. * @param tableName the name of the table to count rows in * @param whereClause the {@code WHERE} clause to append to the query * @return the number of rows in the table that match the provided * {@code WHERE} clause * @since 3.2 + * @see JdbcTestUtils#countRowsInTableWhere */ protected int countRowsInTableWhere(String tableName, String whereClause) { return JdbcTestUtils.countRowsInTableWhere(this.jdbcTemplate, tableName, whereClause); } /** - * Convenience method for deleting all rows from the specified tables. Use - * with caution outside of a transaction! + * Convenience method for deleting all rows from the specified tables. + *

          Use with caution outside of a transaction! * @param names the names of the tables from which to delete * @return the total number of rows deleted from all specified tables + * @see JdbcTestUtils#deleteFromTables */ protected int deleteFromTables(String... names) { return JdbcTestUtils.deleteFromTables(this.jdbcTemplate, names); } /** - * Convenience method for dropping all of the specified tables. Use - * with caution outside of a transaction! + * Convenience method for deleting all rows from the given table, using the + * provided {@code WHERE} clause. + *

          Use with caution outside of a transaction! + *

          See the Javadoc for {@link JdbcTestUtils#deleteFromTableWhere} for details. + * @param tableName the name of the table to delete rows from + * @param whereClause the {@code WHERE} clause to append to the query + * @param args arguments to bind to the query (leaving it to the {@code + * PreparedStatement} to guess the corresponding SQL type); may also contain + * {@link org.springframework.jdbc.core.SqlParameterValue SqlParameterValue} + * objects which indicate not only the argument value but also the SQL type + * and optionally the scale. + * @return the number of rows deleted from the table + * @since 4.0 + * @see JdbcTestUtils#deleteFromTableWhere + */ + protected int deleteFromTableWhere(String tableName, String whereClause, Object... args) { + return JdbcTestUtils.deleteFromTableWhere(jdbcTemplate, tableName, whereClause, args); + } + + /** + * Convenience method for dropping all of the specified tables. + *

          Use with caution outside of a transaction! * @param names the names of the tables to drop * @since 3.2 + * @see JdbcTestUtils#dropTables */ protected void dropTables(String... names) { JdbcTestUtils.dropTables(this.jdbcTemplate, names); } /** - * Execute the given SQL script. Use with caution outside of a transaction! + * Execute the given SQL script. + *

          Use with caution outside of a transaction! *

          The script will normally be loaded by classpath. There should be one * statement per line. Any semicolons will be removed. Do not use this * method to execute DDL if you expect rollback. @@ -154,11 +179,13 @@ public abstract class AbstractTransactionalJUnit4SpringContextTests extends Abst * exception in the event of an error * @throws DataAccessException if there is an error executing a statement * and continueOnError was {@code false} + * @see JdbcTestUtils#executeSqlScript(JdbcTemplate, EncodedResource, boolean) + * @see #setSqlScriptEncoding */ protected void executeSqlScript(String sqlResourcePath, boolean continueOnError) throws DataAccessException { Resource resource = this.applicationContext.getResource(sqlResourcePath); - JdbcTestUtils.executeSqlScript(this.jdbcTemplate, new EncodedResource(resource, - this.sqlScriptEncoding), continueOnError); + JdbcTestUtils.executeSqlScript(this.jdbcTemplate, new EncodedResource(resource, this.sqlScriptEncoding), + continueOnError); } } diff --git a/spring-test/src/main/java/org/springframework/test/context/testng/AbstractTransactionalTestNGSpringContextTests.java b/spring-test/src/main/java/org/springframework/test/context/testng/AbstractTransactionalTestNGSpringContextTests.java index c5d7a6770b..e71b82d529 100644 --- a/spring-test/src/main/java/org/springframework/test/context/testng/AbstractTransactionalTestNGSpringContextTests.java +++ b/spring-test/src/main/java/org/springframework/test/context/testng/AbstractTransactionalTestNGSpringContextTests.java @@ -77,8 +77,7 @@ public abstract class AbstractTransactionalTestNGSpringContextTests extends Abst /** * Set the {@code DataSource}, typically provided via Dependency Injection. - *

          This method also instantiates the {@link #jdbcTemplate} instance - * variable. + *

          This method also instantiates the {@link #jdbcTemplate} instance variable. */ @Autowired public void setDataSource(DataSource dataSource) { @@ -94,49 +93,75 @@ public abstract class AbstractTransactionalTestNGSpringContextTests extends Abst } /** - * Count the rows in the given table. + * Convenience method for counting the rows in the given table. * @param tableName table name to count rows in * @return the number of rows in the table + * @see JdbcTestUtils#countRowsInTable */ protected int countRowsInTable(String tableName) { return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName); } /** - * Count the rows in the given table, using the provided {@code WHERE} clause. + * Convenience method for counting the rows in the given table, using the + * provided {@code WHERE} clause. *

          See the Javadoc for {@link JdbcTestUtils#countRowsInTableWhere} for details. * @param tableName the name of the table to count rows in * @param whereClause the {@code WHERE} clause to append to the query * @return the number of rows in the table that match the provided * {@code WHERE} clause * @since 3.2 + * @see JdbcTestUtils#countRowsInTableWhere */ protected int countRowsInTableWhere(String tableName, String whereClause) { return JdbcTestUtils.countRowsInTableWhere(this.jdbcTemplate, tableName, whereClause); } /** - * Convenience method for deleting all rows from the specified tables. Use - * with caution outside of a transaction! + * Convenience method for deleting all rows from the specified tables. + *

          Use with caution outside of a transaction! * @param names the names of the tables from which to delete * @return the total number of rows deleted from all specified tables + * @see JdbcTestUtils#deleteFromTables */ protected int deleteFromTables(String... names) { return JdbcTestUtils.deleteFromTables(this.jdbcTemplate, names); } /** - * Convenience method for dropping all of the specified tables. Use - * with caution outside of a transaction! + * Convenience method for deleting all rows from the given table, using the + * provided {@code WHERE} clause. + *

          Use with caution outside of a transaction! + *

          See the Javadoc for {@link JdbcTestUtils#deleteFromTableWhere} for details. + * @param tableName the name of the table to delete rows from + * @param whereClause the {@code WHERE} clause to append to the query + * @param args arguments to bind to the query (leaving it to the {@code + * PreparedStatement} to guess the corresponding SQL type); may also contain + * {@link org.springframework.jdbc.core.SqlParameterValue SqlParameterValue} + * objects which indicate not only the argument value but also the SQL type + * and optionally the scale. + * @return the number of rows deleted from the table + * @since 4.0 + * @see JdbcTestUtils#deleteFromTableWhere + */ + protected int deleteFromTableWhere(String tableName, String whereClause, Object... args) { + return JdbcTestUtils.deleteFromTableWhere(jdbcTemplate, tableName, whereClause, args); + } + + /** + * Convenience method for dropping all of the specified tables. + *

          Use with caution outside of a transaction! * @param names the names of the tables to drop * @since 3.2 + * @see JdbcTestUtils#dropTables */ protected void dropTables(String... names) { JdbcTestUtils.dropTables(this.jdbcTemplate, names); } /** - * Execute the given SQL script. Use with caution outside of a transaction! + * Execute the given SQL script. + *

          Use with caution outside of a transaction! *

          The script will normally be loaded by classpath. There should be one * statement per line. Any semicolons will be removed. Do not use this * method to execute DDL if you expect rollback. @@ -145,11 +170,13 @@ public abstract class AbstractTransactionalTestNGSpringContextTests extends Abst * exception in the event of an error * @throws DataAccessException if there is an error executing a statement * and continueOnError was {@code false} + * @see JdbcTestUtils#executeSqlScript(JdbcTemplate, EncodedResource, boolean) + * @see #setSqlScriptEncoding */ protected void executeSqlScript(String sqlResourcePath, boolean continueOnError) throws DataAccessException { Resource resource = this.applicationContext.getResource(sqlResourcePath); - JdbcTestUtils.executeSqlScript(this.jdbcTemplate, new EncodedResource(resource, - this.sqlScriptEncoding), continueOnError); + JdbcTestUtils.executeSqlScript(this.jdbcTemplate, new EncodedResource(resource, this.sqlScriptEncoding), + continueOnError); } } diff --git a/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletRequestTests.java b/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletRequestTests.java index a237bb00d1..5eb2c42a62 100644 --- a/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletRequestTests.java +++ b/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletRequestTests.java @@ -193,6 +193,39 @@ public class MockHttpServletRequestTests { assertEqualEnumerations(Collections.enumeration(preferredLocales), request.getLocales()); } + @Test + public void getRequestURL() { + request.setServerPort(8080); + request.setRequestURI("/path"); + assertEquals("http://localhost:8080/path", request.getRequestURL().toString()); + + request.setScheme("https"); + request.setServerName("example.com"); + request.setServerPort(8443); + assertEquals("https://example.com:8443/path", request.getRequestURL().toString()); + } + + @Test + public void getRequestURLWithDefaults() { + StringBuffer requestURL = request.getRequestURL(); + assertEquals("http://localhost", requestURL.toString()); + } + + @Test + public void getRequestURLWithDefaultsAndHttps() { + request.setScheme("https"); + request.setServerPort(443); + StringBuffer requestURL = request.getRequestURL(); + assertEquals("https://localhost", requestURL.toString()); + } + + @Test + public void getRequestURLWithNegativePort() { + request.setServerPort(-99); + StringBuffer requestURL = request.getRequestURL(); + assertEquals("http://localhost", requestURL.toString()); + } + private void assertEqualEnumerations(Enumeration enum1, Enumeration enum2) { assertNotNull(enum1); assertNotNull(enum2); diff --git a/spring-test/src/test/java/org/springframework/mock/web/MockHttpSessionTests.java b/spring-test/src/test/java/org/springframework/mock/web/MockHttpSessionTests.java index 0379ea78d0..41b735c4bd 100644 --- a/spring-test/src/test/java/org/springframework/mock/web/MockHttpSessionTests.java +++ b/spring-test/src/test/java/org/springframework/mock/web/MockHttpSessionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,7 @@ import org.junit.Test; */ public class MockHttpSessionTests { - private MockHttpSession session = new MockHttpSession(); + private final MockHttpSession session = new MockHttpSession(); @Test @@ -44,4 +44,103 @@ public class MockHttpSessionTests { session.invalidate(); } + /** + * @since 4.0 + */ + @Test(expected = IllegalStateException.class) + public void getCreationTimeOnInvalidatedSession() { + session.invalidate(); + session.getCreationTime(); + } + + /** + * @since 4.0 + */ + @Test(expected = IllegalStateException.class) + public void getLastAccessedTimeOnInvalidatedSession() { + session.invalidate(); + session.getLastAccessedTime(); + } + + /** + * @since 4.0 + */ + @Test(expected = IllegalStateException.class) + public void getAttributeOnInvalidatedSession() { + session.invalidate(); + session.getAttribute("foo"); + } + + /** + * @since 4.0 + */ + @Test(expected = IllegalStateException.class) + public void getAttributeNamesOnInvalidatedSession() { + session.invalidate(); + session.getAttributeNames(); + } + + /** + * @since 4.0 + */ + @Test(expected = IllegalStateException.class) + public void getValueOnInvalidatedSession() { + session.invalidate(); + session.getValue("foo"); + } + + /** + * @since 4.0 + */ + @Test(expected = IllegalStateException.class) + public void getValueNamesOnInvalidatedSession() { + session.invalidate(); + session.getValueNames(); + } + + /** + * @since 4.0 + */ + @Test(expected = IllegalStateException.class) + public void setAttributeOnInvalidatedSession() { + session.invalidate(); + session.setAttribute("name", "value"); + } + + /** + * @since 4.0 + */ + @Test(expected = IllegalStateException.class) + public void putValueOnInvalidatedSession() { + session.invalidate(); + session.putValue("name", "value"); + } + + /** + * @since 4.0 + */ + @Test(expected = IllegalStateException.class) + public void removeAttributeOnInvalidatedSession() { + session.invalidate(); + session.removeAttribute("name"); + } + + /** + * @since 4.0 + */ + @Test(expected = IllegalStateException.class) + public void removeValueOnInvalidatedSession() { + session.invalidate(); + session.removeValue("name"); + } + + /** + * @since 4.0 + */ + @Test(expected = IllegalStateException.class) + public void isNewOnInvalidatedSession() { + session.invalidate(); + session.isNew(); + } + } diff --git a/spring-test/src/test/java/org/springframework/web/multipart/MockMultipartHttpServletRequestTests.java b/spring-test/src/test/java/org/springframework/mock/web/MockMultipartHttpServletRequestTests.java similarity index 89% rename from spring-test/src/test/java/org/springframework/web/multipart/MockMultipartHttpServletRequestTests.java rename to spring-test/src/test/java/org/springframework/mock/web/MockMultipartHttpServletRequestTests.java index 029f584c1f..51fc9e983c 100644 --- a/spring-test/src/test/java/org/springframework/web/multipart/MockMultipartHttpServletRequestTests.java +++ b/spring-test/src/test/java/org/springframework/mock/web/MockMultipartHttpServletRequestTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2006 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,12 +14,7 @@ * limitations under the License. */ -package org.springframework.web.multipart; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +package org.springframework.mock.web; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -31,10 +26,12 @@ import java.util.Map; import java.util.Set; import org.junit.Test; -import org.springframework.mock.web.MockMultipartFile; -import org.springframework.mock.web.MockMultipartHttpServletRequest; import org.springframework.util.FileCopyUtils; import org.springframework.util.ObjectUtils; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.multipart.MultipartHttpServletRequest; + +import static org.junit.Assert.*; /** * @author Juergen Hoeller diff --git a/spring-test/src/test/java/org/springframework/test/AbstractSpr3350SingleSpringContextTests.java b/spring-test/src/test/java/org/springframework/test/AbstractSpr3350SingleSpringContextTests.java deleted file mode 100644 index ba8c81c294..0000000000 --- a/spring-test/src/test/java/org/springframework/test/AbstractSpr3350SingleSpringContextTests.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2002-2013 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.test; - -import org.springframework.tests.sample.beans.Pet; -import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; -import org.springframework.context.ApplicationContext; - -/** - * Abstract JUnit 3.8 based unit test which verifies new functionality requested - * in SPR-3350. - * - * @author Sam Brannen - * @since 2.5 - */ -@SuppressWarnings("deprecation") -public abstract class AbstractSpr3350SingleSpringContextTests extends AbstractDependencyInjectionSpringContextTests { - - private Pet cat; - - - public AbstractSpr3350SingleSpringContextTests() { - super(); - } - - public AbstractSpr3350SingleSpringContextTests(String name) { - super(name); - } - - public final void setCat(final Pet cat) { - this.cat = cat; - } - - /** - * Forcing concrete subclasses to provide a config path appropriate to the - * configured - * {@link #createBeanDefinitionReader(org.springframework.context.support.GenericApplicationContext) - * BeanDefinitionReader}. - * - * @see org.springframework.test.AbstractSingleSpringContextTests#getConfigPath() - */ - @Override - protected abstract String getConfigPath(); - - /** - *

          - * Test which addresses the following issue raised in SPR-3350: - *

          - *

          - * {@link AbstractSingleSpringContextTests} always uses an - * {@link XmlBeanDefinitionReader} internally when creating the - * {@link ApplicationContext} inside - * {@link #createApplicationContext(String[])}. It would be nice to have the - * bean definition reader creation in a separate method so that subclasses - * can choose that individually without having to copy-n-paste code from - * createApplicationContext() to do the context creation and refresh. - * Consider JavaConfig where an Annotation based reader can be plugged in. - *

          - */ - public final void testApplicationContextNotAutoCreated() { - assertNotNull("The cat field should have been autowired.", this.cat); - assertEquals("Garfield", this.cat.getName()); - } -} diff --git a/spring-test/src/test/java/org/springframework/test/PropertiesBasedSpr3350SingleSpringContextTests-context.properties b/spring-test/src/test/java/org/springframework/test/PropertiesBasedSpr3350SingleSpringContextTests-context.properties deleted file mode 100644 index 7b96503a3f..0000000000 --- a/spring-test/src/test/java/org/springframework/test/PropertiesBasedSpr3350SingleSpringContextTests-context.properties +++ /dev/null @@ -1,2 +0,0 @@ -cat.(class)=org.springframework.tests.sample.beans.Pet -cat.$0=Garfield diff --git a/spring-test/src/test/java/org/springframework/test/PropertiesBasedSpr3350SingleSpringContextTests.java b/spring-test/src/test/java/org/springframework/test/PropertiesBasedSpr3350SingleSpringContextTests.java deleted file mode 100644 index 07b0cf4b5e..0000000000 --- a/spring-test/src/test/java/org/springframework/test/PropertiesBasedSpr3350SingleSpringContextTests.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2002-2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.test; - -import org.springframework.beans.factory.support.BeanDefinitionReader; -import org.springframework.beans.factory.support.PropertiesBeanDefinitionReader; -import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; -import org.springframework.context.support.GenericApplicationContext; - -/** - * Concrete implementation of {@link AbstractSpr3350SingleSpringContextTests} - * which configures a {@link PropertiesBeanDefinitionReader} instead of the - * default {@link XmlBeanDefinitionReader}. - * - * @author Sam Brannen - * @since 2.5 - */ -public class PropertiesBasedSpr3350SingleSpringContextTests extends AbstractSpr3350SingleSpringContextTests { - - public PropertiesBasedSpr3350SingleSpringContextTests() { - super(); - } - - public PropertiesBasedSpr3350SingleSpringContextTests(String name) { - super(name); - } - - /** - * Creates a new {@link PropertiesBeanDefinitionReader}. - * - * @see org.springframework.test.AbstractSingleSpringContextTests#createBeanDefinitionReader(org.springframework.context.support.GenericApplicationContext) - */ - @Override - protected final BeanDefinitionReader createBeanDefinitionReader(GenericApplicationContext context) { - return new PropertiesBeanDefinitionReader(context); - } - - /** - * Returns - * "PropertiesBasedSpr3350SingleSpringContextTests-context.properties". - */ - @Override - protected final String getConfigPath() { - return "PropertiesBasedSpr3350SingleSpringContextTests-context.properties"; - } -} diff --git a/spring-test/src/test/java/org/springframework/test/Spr3264DependencyInjectionSpringContextTests.java b/spring-test/src/test/java/org/springframework/test/Spr3264DependencyInjectionSpringContextTests.java deleted file mode 100644 index 08523c7949..0000000000 --- a/spring-test/src/test/java/org/springframework/test/Spr3264DependencyInjectionSpringContextTests.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2002-2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.test; - -/** - * JUnit 3.8 based unit test which verifies new functionality requested in SPR-3264. - * - * @author Sam Brannen - * @since 2.5 - * @see Spr3264SingleSpringContextTests - */ -@SuppressWarnings("deprecation") -public class Spr3264DependencyInjectionSpringContextTests extends AbstractDependencyInjectionSpringContextTests { - - public Spr3264DependencyInjectionSpringContextTests() { - super(); - } - - public Spr3264DependencyInjectionSpringContextTests(String name) { - super(name); - } - - /** - *

          - * Test which addresses the following issue raised in SPR-3264: - *

          - *

          - * AbstractDependencyInjectionSpringContextTests will try to apply - * auto-injection; this can be disabled but it has to be done manually - * inside the onSetUp... - *

          - */ - public void testInjectDependenciesThrowsIllegalStateException() { - - // Re-assert issues covered by Spr3264SingleSpringContextTests as a - // safety net. - assertNull("The ApplicationContext should NOT be automatically created if no 'locations' are defined.", - this.applicationContext); - assertEquals("Verifying the ApplicationContext load count.", 0, super.getLoadCount()); - - // Assert changes to AbstractDependencyInjectionSpringContextTests: - new AssertThrows(IllegalStateException.class) { - - @Override - public void test() throws Exception { - Spr3264DependencyInjectionSpringContextTests.super.injectDependencies(); - } - }.runTest(); - } - -} diff --git a/spring-test/src/test/java/org/springframework/test/Spr3264SingleSpringContextTests.java b/spring-test/src/test/java/org/springframework/test/Spr3264SingleSpringContextTests.java deleted file mode 100644 index 4b3223f4ba..0000000000 --- a/spring-test/src/test/java/org/springframework/test/Spr3264SingleSpringContextTests.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2002-2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.test; - -/** - * JUnit 3.8 based unit test which verifies new functionality requested in SPR-3264. - * - * @author Sam Brannen - * @since 2.5 - * @see Spr3264DependencyInjectionSpringContextTests - */ -@SuppressWarnings("deprecation") -public class Spr3264SingleSpringContextTests extends AbstractSingleSpringContextTests { - - public Spr3264SingleSpringContextTests() { - super(); - } - - public Spr3264SingleSpringContextTests(String name) { - super(name); - } - - /** - *

          - * Test which addresses the following issue raised in SPR-3264: - *

          - *

          - * AbstractSingleSpringContextTests always expects an application context to - * be created even if no files/locations are specified which can lead to NPE - * problems or force an appCtx to be instantiated even if not needed. - *

          - */ - public void testApplicationContextNotAutoCreated() { - assertNull("The ApplicationContext should NOT be automatically created if no 'locations' are defined.", - super.applicationContext); - assertEquals("Verifying the ApplicationContext load count.", 0, super.getLoadCount()); - } - -} diff --git a/spring-test/src/test/java/org/springframework/test/XmlBasedSpr3350SingleSpringContextTests-context.xml b/spring-test/src/test/java/org/springframework/test/XmlBasedSpr3350SingleSpringContextTests-context.xml deleted file mode 100644 index b1939de12f..0000000000 --- a/spring-test/src/test/java/org/springframework/test/XmlBasedSpr3350SingleSpringContextTests-context.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - diff --git a/spring-test/src/test/java/org/springframework/test/XmlBasedSpr3350SingleSpringContextTests.java b/spring-test/src/test/java/org/springframework/test/XmlBasedSpr3350SingleSpringContextTests.java deleted file mode 100644 index 192d005f4a..0000000000 --- a/spring-test/src/test/java/org/springframework/test/XmlBasedSpr3350SingleSpringContextTests.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2002-2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.test; - -import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; - -/** - * Concrete implementation of {@link AbstractSpr3350SingleSpringContextTests} - * which is based on the default {@link XmlBeanDefinitionReader}. - * - * @author Sam Brannen - * @since 2.5 - */ -public class XmlBasedSpr3350SingleSpringContextTests extends AbstractSpr3350SingleSpringContextTests { - - public XmlBasedSpr3350SingleSpringContextTests() { - super(); - } - - public XmlBasedSpr3350SingleSpringContextTests(String name) { - super(name); - } - - /** - * Returns "XmlBasedSpr3350SingleSpringContextTests-context.xml". - */ - @Override - protected final String getConfigPath() { - return "XmlBasedSpr3350SingleSpringContextTests-context.xml"; - } -} diff --git a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsTests$BareAnnotations-context.xml b/spring-test/src/test/java/org/springframework/test/context/AbstractContextLoaderUtilsTests$BareAnnotations-context.xml similarity index 100% rename from spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsTests$BareAnnotations-context.xml rename to spring-test/src/test/java/org/springframework/test/context/AbstractContextLoaderUtilsTests$BareAnnotations-context.xml diff --git a/spring-test/src/test/java/org/springframework/test/context/AbstractContextLoaderUtilsTests.java b/spring-test/src/test/java/org/springframework/test/context/AbstractContextLoaderUtilsTests.java new file mode 100644 index 0000000000..6942771cfe --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/AbstractContextLoaderUtilsTests.java @@ -0,0 +1,116 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context; + +import java.util.Collections; +import java.util.Set; + +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +import static org.junit.Assert.*; + +/** + * Abstract base class for tests involving {@link ContextLoaderUtils}. + * + * @author Sam Brannen + * @since 3.1 + */ +abstract class AbstractContextLoaderUtilsTests { + + static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; + static final String[] EMPTY_STRING_ARRAY = new String[0]; + static final Set>> EMPTY_INITIALIZER_CLASSES = // + Collections.>> emptySet(); + + + void assertMergedConfig(MergedContextConfiguration mergedConfig, Class expectedTestClass, + String[] expectedLocations, Class[] expectedClasses, + Class expectedContextLoaderClass) { + assertMergedConfig(mergedConfig, expectedTestClass, expectedLocations, expectedClasses, + EMPTY_INITIALIZER_CLASSES, expectedContextLoaderClass); + } + + void assertMergedConfig( + MergedContextConfiguration mergedConfig, + Class expectedTestClass, + String[] expectedLocations, + Class[] expectedClasses, + Set>> expectedInitializerClasses, + Class expectedContextLoaderClass) { + assertNotNull(mergedConfig); + assertEquals(expectedTestClass, mergedConfig.getTestClass()); + assertNotNull(mergedConfig.getLocations()); + assertArrayEquals(expectedLocations, mergedConfig.getLocations()); + assertNotNull(mergedConfig.getClasses()); + assertArrayEquals(expectedClasses, mergedConfig.getClasses()); + assertNotNull(mergedConfig.getActiveProfiles()); + assertEquals(expectedContextLoaderClass, mergedConfig.getContextLoader().getClass()); + assertNotNull(mergedConfig.getContextInitializerClasses()); + assertEquals(expectedInitializerClasses, mergedConfig.getContextInitializerClasses()); + } + + + static class Enigma { + } + + @ContextConfiguration + @ActiveProfiles + static class BareAnnotations { + } + + @Configuration + static class FooConfig { + } + + @Configuration + static class BarConfig { + } + + @ContextConfiguration(locations = "/foo.xml", inheritLocations = false) + @ActiveProfiles(profiles = "foo") + static class LocationsFoo { + } + + @ContextConfiguration(classes = FooConfig.class, inheritLocations = false) + @ActiveProfiles(profiles = "foo") + static class ClassesFoo { + } + + @ContextConfiguration(locations = "/bar.xml", inheritLocations = true, loader = AnnotationConfigContextLoader.class) + @ActiveProfiles("bar") + static class LocationsBar extends LocationsFoo { + } + + @ContextConfiguration(locations = "/bar.xml", inheritLocations = false, loader = AnnotationConfigContextLoader.class) + @ActiveProfiles("bar") + static class OverriddenLocationsBar extends LocationsFoo { + } + + @ContextConfiguration(classes = BarConfig.class, inheritLocations = true, loader = AnnotationConfigContextLoader.class) + @ActiveProfiles("bar") + static class ClassesBar extends ClassesFoo { + } + + @ContextConfiguration(classes = BarConfig.class, inheritLocations = false, loader = AnnotationConfigContextLoader.class) + @ActiveProfiles("bar") + static class OverriddenClassesBar extends ClassesFoo { + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/ContextCacheTests.java b/spring-test/src/test/java/org/springframework/test/context/ContextCacheTests.java index eb1c39a908..21f15f441f 100644 --- a/spring-test/src/test/java/org/springframework/test/context/ContextCacheTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/ContextCacheTests.java @@ -32,6 +32,7 @@ import static org.springframework.test.context.SpringRunnerContextCacheTests.*; * conjunction with cache keys used in {@link TestContext}. * * @author Sam Brannen + * @author Michail Nikolaev * @since 3.1 * @see SpringRunnerContextCacheTests */ @@ -84,6 +85,7 @@ public class ContextCacheTests { loadCtxAndAssertStats(FooBarProfilesTestCase.class, 1, 3, 1); loadCtxAndAssertStats(FooBarProfilesTestCase.class, 1, 4, 1); loadCtxAndAssertStats(BarFooProfilesTestCase.class, 1, 5, 1); + loadCtxAndAssertStats(FooBarActiveProfilesResolverTestCase.class, 1, 6, 1); } @Test @@ -287,6 +289,19 @@ public class ContextCacheTests { private static class BarFooProfilesTestCase { } + private static class FooBarActiveProfilesResolver implements ActiveProfilesResolver { + + @Override + public String[] resolve(Class testClass) { + return new String[] { "foo", "bar" }; + } + } + + @ActiveProfiles(resolver = FooBarActiveProfilesResolver.class) + @ContextConfiguration(classes = Config.class, loader = AnnotationConfigContextLoader.class) + private static class FooBarActiveProfilesResolverTestCase { + } + @ContextHierarchy({ @ContextConfiguration }) private static class ClassHierarchyContextHierarchyLevel1TestCase { diff --git a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsActiveProfilesTests.java b/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsActiveProfilesTests.java new file mode 100644 index 0000000000..90bfcf0076 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsActiveProfilesTests.java @@ -0,0 +1,288 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +import static org.junit.Assert.*; +import static org.springframework.test.context.ContextLoaderUtils.*; + +/** + * Unit tests for {@link ContextLoaderUtils} involving resolution of active bean + * definition profiles. + * + * @author Sam Brannen + * @author Michail Nikolaev + * @since 3.1 + */ +public class ContextLoaderUtilsActiveProfilesTests extends AbstractContextLoaderUtilsTests { + + @Test + public void resolveActiveProfilesWithoutAnnotation() { + String[] profiles = resolveActiveProfiles(Enigma.class); + assertArrayEquals(EMPTY_STRING_ARRAY, profiles); + } + + @Test + public void resolveActiveProfilesWithNoProfilesDeclared() { + String[] profiles = resolveActiveProfiles(BareAnnotations.class); + assertArrayEquals(EMPTY_STRING_ARRAY, profiles); + } + + @Test + public void resolveActiveProfilesWithEmptyProfiles() { + String[] profiles = resolveActiveProfiles(EmptyProfiles.class); + assertArrayEquals(EMPTY_STRING_ARRAY, profiles); + } + + @Test + public void resolveActiveProfilesWithDuplicatedProfiles() { + String[] profiles = resolveActiveProfiles(DuplicatedProfiles.class); + assertNotNull(profiles); + assertEquals(3, profiles.length); + + List list = Arrays.asList(profiles); + assertTrue(list.contains("foo")); + assertTrue(list.contains("bar")); + assertTrue(list.contains("baz")); + } + + @Test + public void resolveActiveProfilesWithLocalAnnotation() { + String[] profiles = resolveActiveProfiles(LocationsFoo.class); + assertNotNull(profiles); + assertArrayEquals(new String[] { "foo" }, profiles); + } + + @Test + public void resolveActiveProfilesWithInheritedAnnotationAndLocations() { + String[] profiles = resolveActiveProfiles(InheritedLocationsFoo.class); + assertNotNull(profiles); + assertArrayEquals(new String[] { "foo" }, profiles); + } + + @Test + public void resolveActiveProfilesWithInheritedAnnotationAndClasses() { + String[] profiles = resolveActiveProfiles(InheritedClassesFoo.class); + assertNotNull(profiles); + assertArrayEquals(new String[] { "foo" }, profiles); + } + + @Test + public void resolveActiveProfilesWithLocalAndInheritedAnnotations() { + String[] profiles = resolveActiveProfiles(LocationsBar.class); + assertNotNull(profiles); + assertEquals(2, profiles.length); + + List list = Arrays.asList(profiles); + assertTrue(list.contains("foo")); + assertTrue(list.contains("bar")); + } + + @Test + public void resolveActiveProfilesWithOverriddenAnnotation() { + String[] profiles = resolveActiveProfiles(Animals.class); + assertNotNull(profiles); + assertEquals(2, profiles.length); + + List list = Arrays.asList(profiles); + assertTrue(list.contains("dog")); + assertTrue(list.contains("cat")); + } + + /** + * @since 4.0 + */ + @Test + public void resolveActiveProfilesWithResolver() { + String[] profiles = resolveActiveProfiles(FooActiveProfilesResolverTest.class); + assertNotNull(profiles); + assertEquals(1, profiles.length); + assertArrayEquals(new String[] { "foo" }, profiles); + } + + /** + * @since 4.0 + */ + @Test + public void resolveActiveProfilesWithInheritedResolver() { + String[] profiles = resolveActiveProfiles(InheritedFooActiveProfilesResolverTest.class); + assertNotNull(profiles); + assertEquals(1, profiles.length); + assertArrayEquals(new String[] { "foo" }, profiles); + } + + /** + * @since 4.0 + */ + @Test + public void resolveActiveProfilesWithMergedInheritedResolver() { + String[] profiles = resolveActiveProfiles(MergedInheritedFooActiveProfilesResolverTest.class); + assertNotNull(profiles); + assertEquals(2, profiles.length); + List list = Arrays.asList(profiles); + assertTrue(list.contains("foo")); + assertTrue(list.contains("bar")); + } + + /** + * @since 4.0 + */ + @Test + public void resolveActiveProfilesWithOverridenInheritedResolver() { + String[] profiles = resolveActiveProfiles(OverridenInheritedFooActiveProfilesResolverTest.class); + assertNotNull(profiles); + assertEquals(1, profiles.length); + assertArrayEquals(new String[] { "bar" }, profiles); + } + + /** + * @since 4.0 + */ + @Test(expected = IllegalStateException.class) + public void resolveActiveProfilesWithConflictingResolverAndProfiles() { + resolveActiveProfiles(ConflictingResolverAndProfilesTest.class); + } + + /** + * @since 4.0 + */ + @Test(expected = IllegalStateException.class) + public void resolveActiveProfilesWithConflictingResolverAndValue() { + resolveActiveProfiles(ConflictingResolverAndValueTest.class); + } + + /** + * @since 4.0 + */ + @Test(expected = IllegalStateException.class) + public void resolveActiveProfilesWithConflictingProfilesAndValue() { + resolveActiveProfiles(ConflictingProfilesAndValueTest.class); + } + + /** + * @since 4.0 + */ + @Test(expected = IllegalStateException.class) + public void resolveActiveProfilesWithResolverWithoutDefaultConstructor() { + resolveActiveProfiles(NoDefaultConstructorActiveProfilesResolverTest.class); + } + + /** + * @since 4.0 + */ + @Test(expected = IllegalStateException.class) + public void resolveActiveProfilesWithResolverThatReturnsNull() { + resolveActiveProfiles(NullActiveProfilesResolverTest.class); + } + + + // ------------------------------------------------------------------------- + + @ActiveProfiles({ " ", "\t" }) + private static class EmptyProfiles { + } + + @ActiveProfiles({ "foo", "bar", " foo", "bar ", "baz" }) + private static class DuplicatedProfiles { + } + + @ActiveProfiles(profiles = { "dog", "cat" }, inheritProfiles = false) + private static class Animals extends LocationsBar { + } + + private static class InheritedLocationsFoo extends LocationsFoo { + } + + private static class InheritedClassesFoo extends ClassesFoo { + } + + @ActiveProfiles(resolver = NullActiveProfilesResolver.class) + private static class NullActiveProfilesResolverTest { + } + + @ActiveProfiles(resolver = NoDefaultConstructorActiveProfilesResolver.class) + private static class NoDefaultConstructorActiveProfilesResolverTest { + } + + @ActiveProfiles(resolver = FooActiveProfilesResolver.class) + private static class FooActiveProfilesResolverTest { + } + + private static class InheritedFooActiveProfilesResolverTest extends FooActiveProfilesResolverTest { + } + + @ActiveProfiles(resolver = BarActiveProfilesResolver.class) + private static class MergedInheritedFooActiveProfilesResolverTest extends InheritedFooActiveProfilesResolverTest { + } + + @ActiveProfiles(resolver = BarActiveProfilesResolver.class, inheritProfiles = false) + private static class OverridenInheritedFooActiveProfilesResolverTest extends InheritedFooActiveProfilesResolverTest { + } + + @ActiveProfiles(resolver = BarActiveProfilesResolver.class, profiles = "conflict") + private static class ConflictingResolverAndProfilesTest { + } + + @ActiveProfiles(resolver = BarActiveProfilesResolver.class, value = "conflict") + private static class ConflictingResolverAndValueTest { + } + + @ActiveProfiles(profiles = "conflict", value = "conflict") + private static class ConflictingProfilesAndValueTest { + } + + private static class FooActiveProfilesResolver implements ActiveProfilesResolver { + + @Override + public String[] resolve(Class testClass) { + return new String[] { "foo" }; + } + } + + private static class BarActiveProfilesResolver implements ActiveProfilesResolver { + + @Override + public String[] resolve(Class testClass) { + return new String[] { "bar" }; + } + } + + private static class NullActiveProfilesResolver implements ActiveProfilesResolver { + + @Override + public String[] resolve(Class testClass) { + return null; + } + } + + private static class NoDefaultConstructorActiveProfilesResolver implements ActiveProfilesResolver { + + @SuppressWarnings("unused") + NoDefaultConstructorActiveProfilesResolver(Object agument) { + } + + @Override + public String[] resolve(Class testClass) { + return null; + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsConfigurationAttributesTests.java b/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsConfigurationAttributesTests.java new file mode 100644 index 0000000000..3094581155 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsConfigurationAttributesTests.java @@ -0,0 +1,118 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context; + +import java.util.List; + +import org.junit.Test; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +import static org.junit.Assert.*; +import static org.springframework.test.context.ContextLoaderUtils.*; + +/** + * Unit tests for {@link ContextLoaderUtils} involving {@link ContextConfigurationAttributes}. + * + * @author Sam Brannen + * @since 3.1 + */ +public class ContextLoaderUtilsConfigurationAttributesTests extends AbstractContextLoaderUtilsTests { + + private void assertAttributes(ContextConfigurationAttributes attributes, Class expectedDeclaringClass, + String[] expectedLocations, Class[] expectedClasses, + Class expectedContextLoaderClass, boolean expectedInheritLocations) { + assertEquals(expectedDeclaringClass, attributes.getDeclaringClass()); + assertArrayEquals(expectedLocations, attributes.getLocations()); + assertArrayEquals(expectedClasses, attributes.getClasses()); + assertEquals(expectedInheritLocations, attributes.isInheritLocations()); + assertEquals(expectedContextLoaderClass, attributes.getContextLoaderClass()); + } + + private void assertLocationsFooAttributes(ContextConfigurationAttributes attributes) { + assertAttributes(attributes, LocationsFoo.class, new String[] { "/foo.xml" }, EMPTY_CLASS_ARRAY, + ContextLoader.class, false); + } + + private void assertClassesFooAttributes(ContextConfigurationAttributes attributes) { + assertAttributes(attributes, ClassesFoo.class, EMPTY_STRING_ARRAY, new Class[] { FooConfig.class }, + ContextLoader.class, false); + } + + private void assertLocationsBarAttributes(ContextConfigurationAttributes attributes) { + assertAttributes(attributes, LocationsBar.class, new String[] { "/bar.xml" }, EMPTY_CLASS_ARRAY, + AnnotationConfigContextLoader.class, true); + } + + private void assertClassesBarAttributes(ContextConfigurationAttributes attributes) { + assertAttributes(attributes, ClassesBar.class, EMPTY_STRING_ARRAY, new Class[] { BarConfig.class }, + AnnotationConfigContextLoader.class, true); + } + + @Test(expected = IllegalStateException.class) + public void resolveConfigAttributesWithConflictingLocations() { + resolveContextConfigurationAttributes(ConflictingLocations.class); + } + + @Test + public void resolveConfigAttributesWithBareAnnotations() { + List attributesList = resolveContextConfigurationAttributes(BareAnnotations.class); + assertNotNull(attributesList); + assertEquals(1, attributesList.size()); + assertAttributes(attributesList.get(0), BareAnnotations.class, EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, + ContextLoader.class, true); + } + + @Test + public void resolveConfigAttributesWithLocalAnnotationAndLocations() { + List attributesList = resolveContextConfigurationAttributes(LocationsFoo.class); + assertNotNull(attributesList); + assertEquals(1, attributesList.size()); + assertLocationsFooAttributes(attributesList.get(0)); + } + + @Test + public void resolveConfigAttributesWithLocalAnnotationAndClasses() { + List attributesList = resolveContextConfigurationAttributes(ClassesFoo.class); + assertNotNull(attributesList); + assertEquals(1, attributesList.size()); + assertClassesFooAttributes(attributesList.get(0)); + } + + @Test + public void resolveConfigAttributesWithLocalAndInheritedAnnotationsAndLocations() { + List attributesList = resolveContextConfigurationAttributes(LocationsBar.class); + assertNotNull(attributesList); + assertEquals(2, attributesList.size()); + assertLocationsBarAttributes(attributesList.get(0)); + assertLocationsFooAttributes(attributesList.get(1)); + } + + @Test + public void resolveConfigAttributesWithLocalAndInheritedAnnotationsAndClasses() { + List attributesList = resolveContextConfigurationAttributes(ClassesBar.class); + assertNotNull(attributesList); + assertEquals(2, attributesList.size()); + assertClassesBarAttributes(attributesList.get(0)); + assertClassesFooAttributes(attributesList.get(1)); + } + + + @ContextConfiguration(value = "x", locations = "y") + private static class ConflictingLocations { + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsContextHierarchyTests.java b/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsContextHierarchyTests.java new file mode 100644 index 0000000000..9f59087219 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsContextHierarchyTests.java @@ -0,0 +1,404 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.junit.Test; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.springframework.test.context.ContextLoaderUtils.*; + +/** + * Unit tests for {@link ContextLoaderUtils} involving context hierarchies. + * + * @author Sam Brannen + * @since 3.1 + */ +public class ContextLoaderUtilsContextHierarchyTests extends AbstractContextLoaderUtilsTests { + + private void debugConfigAttributes(List configAttributesList) { + // for (ContextConfigurationAttributes configAttributes : configAttributesList) { + // System.err.println(configAttributes); + // } + } + + @Test(expected = IllegalStateException.class) + public void resolveContextHierarchyAttributesForSingleTestClassWithContextConfigurationAndContextHierarchy() { + resolveContextHierarchyAttributes(SingleTestClassWithContextConfigurationAndContextHierarchy.class); + } + + @Test + public void resolveContextHierarchyAttributesForSingleTestClassWithImplicitSingleLevelContextHierarchy() { + List> hierarchyAttributes = resolveContextHierarchyAttributes(BareAnnotations.class); + assertEquals(1, hierarchyAttributes.size()); + List configAttributesList = hierarchyAttributes.get(0); + assertEquals(1, configAttributesList.size()); + debugConfigAttributes(configAttributesList); + } + + @Test + public void resolveContextHierarchyAttributesForSingleTestClassWithSingleLevelContextHierarchy() { + List> hierarchyAttributes = resolveContextHierarchyAttributes(SingleTestClassWithSingleLevelContextHierarchy.class); + assertEquals(1, hierarchyAttributes.size()); + List configAttributesList = hierarchyAttributes.get(0); + assertEquals(1, configAttributesList.size()); + debugConfigAttributes(configAttributesList); + } + + @Test + public void resolveContextHierarchyAttributesForSingleTestClassWithTripleLevelContextHierarchy() { + List> hierarchyAttributes = resolveContextHierarchyAttributes(SingleTestClassWithTripleLevelContextHierarchy.class); + assertEquals(1, hierarchyAttributes.size()); + List configAttributesList = hierarchyAttributes.get(0); + assertEquals(3, configAttributesList.size()); + debugConfigAttributes(configAttributesList); + } + + @Test + public void resolveContextHierarchyAttributesForTestClassHierarchyWithSingleLevelContextHierarchies() { + List> hierarchyAttributes = resolveContextHierarchyAttributes(TestClass3WithSingleLevelContextHierarchy.class); + assertEquals(3, hierarchyAttributes.size()); + + List configAttributesListClassLevel1 = hierarchyAttributes.get(0); + debugConfigAttributes(configAttributesListClassLevel1); + assertEquals(1, configAttributesListClassLevel1.size()); + assertThat(configAttributesListClassLevel1.get(0).getLocations()[0], equalTo("one.xml")); + + List configAttributesListClassLevel2 = hierarchyAttributes.get(1); + debugConfigAttributes(configAttributesListClassLevel2); + assertEquals(1, configAttributesListClassLevel2.size()); + assertArrayEquals(new String[] { "two-A.xml", "two-B.xml" }, + configAttributesListClassLevel2.get(0).getLocations()); + + List configAttributesListClassLevel3 = hierarchyAttributes.get(2); + debugConfigAttributes(configAttributesListClassLevel3); + assertEquals(1, configAttributesListClassLevel3.size()); + assertThat(configAttributesListClassLevel3.get(0).getLocations()[0], equalTo("three.xml")); + } + + @Test + public void resolveContextHierarchyAttributesForTestClassHierarchyWithBareContextConfigurationInSubclass() { + List> hierarchyAttributes = resolveContextHierarchyAttributes(TestClass2WithBareContextConfigurationInSubclass.class); + assertEquals(2, hierarchyAttributes.size()); + + List configAttributesListClassLevel1 = hierarchyAttributes.get(0); + debugConfigAttributes(configAttributesListClassLevel1); + assertEquals(1, configAttributesListClassLevel1.size()); + assertThat(configAttributesListClassLevel1.get(0).getLocations()[0], equalTo("one.xml")); + + List configAttributesListClassLevel2 = hierarchyAttributes.get(1); + debugConfigAttributes(configAttributesListClassLevel2); + assertEquals(1, configAttributesListClassLevel2.size()); + assertThat(configAttributesListClassLevel2.get(0).getLocations()[0], equalTo("two.xml")); + } + + @Test + public void resolveContextHierarchyAttributesForTestClassHierarchyWithBareContextConfigurationInSuperclass() { + List> hierarchyAttributes = resolveContextHierarchyAttributes(TestClass2WithBareContextConfigurationInSuperclass.class); + assertEquals(2, hierarchyAttributes.size()); + + List configAttributesListClassLevel1 = hierarchyAttributes.get(0); + debugConfigAttributes(configAttributesListClassLevel1); + assertEquals(1, configAttributesListClassLevel1.size()); + assertThat(configAttributesListClassLevel1.get(0).getLocations()[0], equalTo("one.xml")); + + List configAttributesListClassLevel2 = hierarchyAttributes.get(1); + debugConfigAttributes(configAttributesListClassLevel2); + assertEquals(1, configAttributesListClassLevel2.size()); + assertThat(configAttributesListClassLevel2.get(0).getLocations()[0], equalTo("two.xml")); + } + + @Test + public void resolveContextHierarchyAttributesForTestClassHierarchyWithMultiLevelContextHierarchies() { + List> hierarchyAttributes = resolveContextHierarchyAttributes(TestClass3WithMultiLevelContextHierarchy.class); + assertEquals(3, hierarchyAttributes.size()); + + List configAttributesListClassLevel1 = hierarchyAttributes.get(0); + debugConfigAttributes(configAttributesListClassLevel1); + assertEquals(2, configAttributesListClassLevel1.size()); + assertThat(configAttributesListClassLevel1.get(0).getLocations()[0], equalTo("1-A.xml")); + assertThat(configAttributesListClassLevel1.get(1).getLocations()[0], equalTo("1-B.xml")); + + List configAttributesListClassLevel2 = hierarchyAttributes.get(1); + debugConfigAttributes(configAttributesListClassLevel2); + assertEquals(2, configAttributesListClassLevel2.size()); + assertThat(configAttributesListClassLevel2.get(0).getLocations()[0], equalTo("2-A.xml")); + assertThat(configAttributesListClassLevel2.get(1).getLocations()[0], equalTo("2-B.xml")); + + List configAttributesListClassLevel3 = hierarchyAttributes.get(2); + debugConfigAttributes(configAttributesListClassLevel3); + assertEquals(3, configAttributesListClassLevel3.size()); + assertThat(configAttributesListClassLevel3.get(0).getLocations()[0], equalTo("3-A.xml")); + assertThat(configAttributesListClassLevel3.get(1).getLocations()[0], equalTo("3-B.xml")); + assertThat(configAttributesListClassLevel3.get(2).getLocations()[0], equalTo("3-C.xml")); + } + + private void assertContextConfigEntriesAreNotUnique(Class testClass) { + try { + resolveContextHierarchyAttributes(testClass); + fail("Should throw an IllegalStateException"); + } + catch (IllegalStateException e) { + String msg = String.format( + "The @ContextConfiguration elements configured via @ContextHierarchy in test class [%s] must define unique contexts to load.", + testClass.getName()); + assertEquals(msg, e.getMessage()); + } + } + + @Test + public void resolveContextHierarchyAttributesForSingleTestClassWithMultiLevelContextHierarchyWithEmptyContextConfig() { + assertContextConfigEntriesAreNotUnique(SingleTestClassWithMultiLevelContextHierarchyWithEmptyContextConfig.class); + } + + @Test + public void resolveContextHierarchyAttributesForSingleTestClassWithMultiLevelContextHierarchyWithDuplicatedContextConfig() { + assertContextConfigEntriesAreNotUnique(SingleTestClassWithMultiLevelContextHierarchyWithDuplicatedContextConfig.class); + } + + @Test + public void buildContextHierarchyMapForTestClassHierarchyWithMultiLevelContextHierarchies() { + Map> map = buildContextHierarchyMap(TestClass3WithMultiLevelContextHierarchy.class); + + assertThat(map.size(), is(3)); + assertThat(map.keySet(), hasItems("alpha", "beta", "gamma")); + + List alphaConfig = map.get("alpha"); + assertThat(alphaConfig.size(), is(3)); + assertThat(alphaConfig.get(0).getLocations()[0], is("1-A.xml")); + assertThat(alphaConfig.get(1).getLocations()[0], is("2-A.xml")); + assertThat(alphaConfig.get(2).getLocations()[0], is("3-A.xml")); + + List betaConfig = map.get("beta"); + assertThat(betaConfig.size(), is(3)); + assertThat(betaConfig.get(0).getLocations()[0], is("1-B.xml")); + assertThat(betaConfig.get(1).getLocations()[0], is("2-B.xml")); + assertThat(betaConfig.get(2).getLocations()[0], is("3-B.xml")); + + List gammaConfig = map.get("gamma"); + assertThat(gammaConfig.size(), is(1)); + assertThat(gammaConfig.get(0).getLocations()[0], is("3-C.xml")); + } + + @Test + public void buildContextHierarchyMapForTestClassHierarchyWithMultiLevelContextHierarchiesAndUnnamedConfig() { + Map> map = buildContextHierarchyMap(TestClass3WithMultiLevelContextHierarchyAndUnnamedConfig.class); + + String level1 = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + 1; + String level2 = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + 2; + String level3 = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + 3; + String level4 = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + 4; + String level5 = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + 5; + String level6 = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + 6; + String level7 = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + 7; + + assertThat(map.size(), is(7)); + assertThat(map.keySet(), hasItems(level1, level2, level3, level4, level5, level6, level7)); + + List level1Config = map.get(level1); + assertThat(level1Config.size(), is(1)); + assertThat(level1Config.get(0).getLocations()[0], is("1-A.xml")); + + List level2Config = map.get(level2); + assertThat(level2Config.size(), is(1)); + assertThat(level2Config.get(0).getLocations()[0], is("1-B.xml")); + + List level3Config = map.get(level3); + assertThat(level3Config.size(), is(1)); + assertThat(level3Config.get(0).getLocations()[0], is("2-A.xml")); + + // ... + + List level7Config = map.get(level7); + assertThat(level7Config.size(), is(1)); + assertThat(level7Config.get(0).getLocations()[0], is("3-C.xml")); + } + + @Test + public void buildContextHierarchyMapForTestClassHierarchyWithMultiLevelContextHierarchiesAndPartiallyNamedConfig() { + Map> map = buildContextHierarchyMap(TestClass2WithMultiLevelContextHierarchyAndPartiallyNamedConfig.class); + + String level1 = "parent"; + String level2 = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + 2; + String level3 = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + 3; + + assertThat(map.size(), is(3)); + assertThat(map.keySet(), hasItems(level1, level2, level3)); + Iterator levels = map.keySet().iterator(); + assertThat(levels.next(), is(level1)); + assertThat(levels.next(), is(level2)); + assertThat(levels.next(), is(level3)); + + List level1Config = map.get(level1); + assertThat(level1Config.size(), is(2)); + assertThat(level1Config.get(0).getLocations()[0], is("1-A.xml")); + assertThat(level1Config.get(1).getLocations()[0], is("2-A.xml")); + + List level2Config = map.get(level2); + assertThat(level2Config.size(), is(1)); + assertThat(level2Config.get(0).getLocations()[0], is("1-B.xml")); + + List level3Config = map.get(level3); + assertThat(level3Config.size(), is(1)); + assertThat(level3Config.get(0).getLocations()[0], is("2-C.xml")); + } + + + // ------------------------------------------------------------------------- + + @ContextConfiguration("foo.xml") + @ContextHierarchy(@ContextConfiguration("bar.xml")) + private static class SingleTestClassWithContextConfigurationAndContextHierarchy { + } + + @ContextHierarchy(@ContextConfiguration("A.xml")) + private static class SingleTestClassWithSingleLevelContextHierarchy { + } + + @ContextHierarchy({// + // + @ContextConfiguration("A.xml"),// + @ContextConfiguration("B.xml"),// + @ContextConfiguration("C.xml") // + }) + private static class SingleTestClassWithTripleLevelContextHierarchy { + } + + @ContextHierarchy(@ContextConfiguration("one.xml")) + private static class TestClass1WithSingleLevelContextHierarchy { + } + + @ContextHierarchy(@ContextConfiguration({ "two-A.xml", "two-B.xml" })) + private static class TestClass2WithSingleLevelContextHierarchy extends TestClass1WithSingleLevelContextHierarchy { + } + + @ContextHierarchy(@ContextConfiguration("three.xml")) + private static class TestClass3WithSingleLevelContextHierarchy extends TestClass2WithSingleLevelContextHierarchy { + } + + @ContextConfiguration("one.xml") + private static class TestClass1WithBareContextConfigurationInSuperclass { + } + + @ContextHierarchy(@ContextConfiguration("two.xml")) + private static class TestClass2WithBareContextConfigurationInSuperclass extends + TestClass1WithBareContextConfigurationInSuperclass { + } + + @ContextHierarchy(@ContextConfiguration("one.xml")) + private static class TestClass1WithBareContextConfigurationInSubclass { + } + + @ContextConfiguration("two.xml") + private static class TestClass2WithBareContextConfigurationInSubclass extends + TestClass1WithBareContextConfigurationInSuperclass { + } + + @ContextHierarchy({// + // + @ContextConfiguration(locations = "1-A.xml", name = "alpha"),// + @ContextConfiguration(locations = "1-B.xml", name = "beta") // + }) + private static class TestClass1WithMultiLevelContextHierarchy { + } + + @ContextHierarchy({// + // + @ContextConfiguration(locations = "2-A.xml", name = "alpha"),// + @ContextConfiguration(locations = "2-B.xml", name = "beta") // + }) + private static class TestClass2WithMultiLevelContextHierarchy extends TestClass1WithMultiLevelContextHierarchy { + } + + @ContextHierarchy({// + // + @ContextConfiguration(locations = "3-A.xml", name = "alpha"),// + @ContextConfiguration(locations = "3-B.xml", name = "beta"),// + @ContextConfiguration(locations = "3-C.xml", name = "gamma") // + }) + private static class TestClass3WithMultiLevelContextHierarchy extends TestClass2WithMultiLevelContextHierarchy { + } + + @ContextHierarchy({// + // + @ContextConfiguration(locations = "1-A.xml"),// + @ContextConfiguration(locations = "1-B.xml") // + }) + private static class TestClass1WithMultiLevelContextHierarchyAndUnnamedConfig { + } + + @ContextHierarchy({// + // + @ContextConfiguration(locations = "2-A.xml"),// + @ContextConfiguration(locations = "2-B.xml") // + }) + private static class TestClass2WithMultiLevelContextHierarchyAndUnnamedConfig extends + TestClass1WithMultiLevelContextHierarchyAndUnnamedConfig { + } + + @ContextHierarchy({// + // + @ContextConfiguration(locations = "3-A.xml"),// + @ContextConfiguration(locations = "3-B.xml"),// + @ContextConfiguration(locations = "3-C.xml") // + }) + private static class TestClass3WithMultiLevelContextHierarchyAndUnnamedConfig extends + TestClass2WithMultiLevelContextHierarchyAndUnnamedConfig { + } + + @ContextHierarchy({// + // + @ContextConfiguration(locations = "1-A.xml", name = "parent"),// + @ContextConfiguration(locations = "1-B.xml") // + }) + private static class TestClass1WithMultiLevelContextHierarchyAndPartiallyNamedConfig { + } + + @ContextHierarchy({// + // + @ContextConfiguration(locations = "2-A.xml", name = "parent"),// + @ContextConfiguration(locations = "2-C.xml") // + }) + private static class TestClass2WithMultiLevelContextHierarchyAndPartiallyNamedConfig extends + TestClass1WithMultiLevelContextHierarchyAndPartiallyNamedConfig { + } + + @ContextHierarchy({ + // + @ContextConfiguration,// + @ContextConfiguration // + }) + private static class SingleTestClassWithMultiLevelContextHierarchyWithEmptyContextConfig { + } + + @ContextHierarchy({ + // + @ContextConfiguration("foo.xml"),// + @ContextConfiguration(classes = BarConfig.class),// duplicate! + @ContextConfiguration("baz.xml"),// + @ContextConfiguration(classes = BarConfig.class),// duplicate! + @ContextConfiguration(loader = AnnotationConfigContextLoader.class) // + }) + private static class SingleTestClassWithMultiLevelContextHierarchyWithDuplicatedContextConfig { + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsContextInitializerTests.java b/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsContextInitializerTests.java new file mode 100644 index 0000000000..654efb2bd6 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsContextInitializerTests.java @@ -0,0 +1,123 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context; + +import java.util.HashSet; +import java.util.Set; + +import org.junit.Test; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.test.context.support.DelegatingSmartContextLoader; +import org.springframework.web.context.support.GenericWebApplicationContext; + +import static org.springframework.test.context.ContextLoaderUtils.*; + +/** + * Unit tests for {@link ContextLoaderUtils} involving {@link ApplicationContextInitializer}s. + * + * @author Sam Brannen + * @since 3.1 + */ +public class ContextLoaderUtilsContextInitializerTests extends AbstractContextLoaderUtilsTests { + + @Test + public void buildMergedConfigWithLocalInitializer() { + Class testClass = InitializersFoo.class; + Class[] expectedClasses = new Class[] { FooConfig.class }; + Set>> expectedInitializerClasses// + = new HashSet>>(); + expectedInitializerClasses.add(FooInitializer.class); + + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null); + assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, expectedInitializerClasses, + DelegatingSmartContextLoader.class); + } + + @Test + public void buildMergedConfigWithLocalAndInheritedInitializer() { + Class testClass = InitializersBar.class; + Class[] expectedClasses = new Class[] { FooConfig.class, BarConfig.class }; + Set>> expectedInitializerClasses// + = new HashSet>>(); + expectedInitializerClasses.add(FooInitializer.class); + expectedInitializerClasses.add(BarInitializer.class); + + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null); + assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, expectedInitializerClasses, + DelegatingSmartContextLoader.class); + } + + @Test + public void buildMergedConfigWithOverriddenInitializers() { + Class testClass = OverriddenInitializersBar.class; + Class[] expectedClasses = new Class[] { FooConfig.class, BarConfig.class }; + Set>> expectedInitializerClasses// + = new HashSet>>(); + expectedInitializerClasses.add(BarInitializer.class); + + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null); + assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, expectedInitializerClasses, + DelegatingSmartContextLoader.class); + } + + @Test + public void buildMergedConfigWithOverriddenInitializersAndClasses() { + Class testClass = OverriddenInitializersAndClassesBar.class; + Class[] expectedClasses = new Class[] { BarConfig.class }; + Set>> expectedInitializerClasses// + = new HashSet>>(); + expectedInitializerClasses.add(BarInitializer.class); + + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null); + assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, expectedInitializerClasses, + DelegatingSmartContextLoader.class); + } + + + private static class FooInitializer implements ApplicationContextInitializer { + + @Override + public void initialize(GenericApplicationContext applicationContext) { + } + } + + private static class BarInitializer implements ApplicationContextInitializer { + + @Override + public void initialize(GenericWebApplicationContext applicationContext) { + } + } + + @ContextConfiguration(classes = FooConfig.class, initializers = FooInitializer.class) + private static class InitializersFoo { + } + + @ContextConfiguration(classes = BarConfig.class, initializers = BarInitializer.class) + private static class InitializersBar extends InitializersFoo { + } + + @ContextConfiguration(classes = BarConfig.class, initializers = BarInitializer.class, inheritInitializers = false) + private static class OverriddenInitializersBar extends InitializersFoo { + } + + @ContextConfiguration(classes = BarConfig.class, inheritLocations = false, initializers = BarInitializer.class, inheritInitializers = false) + private static class OverriddenInitializersAndClassesBar extends InitializersFoo { + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsMergedConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsMergedConfigTests.java new file mode 100644 index 0000000000..c2db87bdf0 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsMergedConfigTests.java @@ -0,0 +1,131 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context; + +import org.junit.Test; +import org.springframework.test.context.support.AnnotationConfigContextLoader; +import org.springframework.test.context.support.DelegatingSmartContextLoader; +import org.springframework.test.context.support.GenericPropertiesContextLoader; + +import static org.springframework.test.context.ContextLoaderUtils.*; + +/** + * Unit tests for {@link ContextLoaderUtils} involving {@link MergedContextConfiguration}. + * + * @author Sam Brannen + * @since 3.1 + */ +public class ContextLoaderUtilsMergedConfigTests extends AbstractContextLoaderUtilsTests { + + @Test(expected = IllegalArgumentException.class) + public void buildMergedConfigWithoutAnnotation() { + buildMergedContextConfiguration(Enigma.class, null, null); + } + + @Test + public void buildMergedConfigWithBareAnnotations() { + Class testClass = BareAnnotations.class; + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null); + + assertMergedConfig( + mergedConfig, + testClass, + new String[] { "classpath:/org/springframework/test/context/AbstractContextLoaderUtilsTests$BareAnnotations-context.xml" }, + EMPTY_CLASS_ARRAY, DelegatingSmartContextLoader.class); + } + + @Test + public void buildMergedConfigWithLocalAnnotationAndLocations() { + Class testClass = LocationsFoo.class; + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null); + + assertMergedConfig(mergedConfig, testClass, new String[] { "classpath:/foo.xml" }, EMPTY_CLASS_ARRAY, + DelegatingSmartContextLoader.class); + } + + @Test + public void buildMergedConfigWithLocalAnnotationAndClasses() { + Class testClass = ClassesFoo.class; + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null); + + assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, new Class[] { FooConfig.class }, + DelegatingSmartContextLoader.class); + } + + @Test + public void buildMergedConfigWithLocalAnnotationAndOverriddenContextLoaderAndLocations() { + Class testClass = LocationsFoo.class; + Class expectedContextLoaderClass = GenericPropertiesContextLoader.class; + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, + expectedContextLoaderClass.getName(), null); + + assertMergedConfig(mergedConfig, testClass, new String[] { "classpath:/foo.xml" }, EMPTY_CLASS_ARRAY, + expectedContextLoaderClass); + } + + @Test + public void buildMergedConfigWithLocalAnnotationAndOverriddenContextLoaderAndClasses() { + Class testClass = ClassesFoo.class; + Class expectedContextLoaderClass = GenericPropertiesContextLoader.class; + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, + expectedContextLoaderClass.getName(), null); + + assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, new Class[] { FooConfig.class }, + expectedContextLoaderClass); + } + + @Test + public void buildMergedConfigWithLocalAndInheritedAnnotationsAndLocations() { + Class testClass = LocationsBar.class; + String[] expectedLocations = new String[] { "/foo.xml", "/bar.xml" }; + + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null); + assertMergedConfig(mergedConfig, testClass, expectedLocations, EMPTY_CLASS_ARRAY, + AnnotationConfigContextLoader.class); + } + + @Test + public void buildMergedConfigWithLocalAndInheritedAnnotationsAndClasses() { + Class testClass = ClassesBar.class; + Class[] expectedClasses = new Class[] { FooConfig.class, BarConfig.class }; + + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null); + assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, + AnnotationConfigContextLoader.class); + } + + @Test + public void buildMergedConfigWithAnnotationsAndOverriddenLocations() { + Class testClass = OverriddenLocationsBar.class; + String[] expectedLocations = new String[] { "/bar.xml" }; + + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null); + assertMergedConfig(mergedConfig, testClass, expectedLocations, EMPTY_CLASS_ARRAY, + AnnotationConfigContextLoader.class); + } + + @Test + public void buildMergedConfigWithAnnotationsAndOverriddenClasses() { + Class testClass = OverriddenClassesBar.class; + Class[] expectedClasses = new Class[] { BarConfig.class }; + + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null); + assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, + AnnotationConfigContextLoader.class); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsTests.java b/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsTests.java deleted file mode 100644 index ecedda4ddf..0000000000 --- a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsTests.java +++ /dev/null @@ -1,846 +0,0 @@ -/* - * Copyright 2002-2013 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.test.context; - -import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.*; -import static org.springframework.test.context.ContextLoaderUtils.*; - -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.junit.Test; -import org.springframework.context.ApplicationContextInitializer; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.support.GenericApplicationContext; -import org.springframework.test.context.support.AnnotationConfigContextLoader; -import org.springframework.test.context.support.DelegatingSmartContextLoader; -import org.springframework.test.context.support.GenericPropertiesContextLoader; -import org.springframework.web.context.support.GenericWebApplicationContext; - -/** - * Unit tests for {@link ContextLoaderUtils}. - * - * @author Sam Brannen - * @since 3.1 - */ -public class ContextLoaderUtilsTests { - - private static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; - private static final String[] EMPTY_STRING_ARRAY = new String[0]; - private static final Set>> EMPTY_INITIALIZER_CLASSES = // - Collections.>> emptySet(); - - - private void assertAttributes(ContextConfigurationAttributes attributes, Class expectedDeclaringClass, - String[] expectedLocations, Class[] expectedClasses, - Class expectedContextLoaderClass, boolean expectedInheritLocations) { - assertEquals(expectedDeclaringClass, attributes.getDeclaringClass()); - assertArrayEquals(expectedLocations, attributes.getLocations()); - assertArrayEquals(expectedClasses, attributes.getClasses()); - assertEquals(expectedInheritLocations, attributes.isInheritLocations()); - assertEquals(expectedContextLoaderClass, attributes.getContextLoaderClass()); - } - - private void assertLocationsFooAttributes(ContextConfigurationAttributes attributes) { - assertAttributes(attributes, LocationsFoo.class, new String[] { "/foo.xml" }, EMPTY_CLASS_ARRAY, - ContextLoader.class, false); - } - - private void assertClassesFooAttributes(ContextConfigurationAttributes attributes) { - assertAttributes(attributes, ClassesFoo.class, EMPTY_STRING_ARRAY, new Class[] { FooConfig.class }, - ContextLoader.class, false); - } - - private void assertLocationsBarAttributes(ContextConfigurationAttributes attributes) { - assertAttributes(attributes, LocationsBar.class, new String[] { "/bar.xml" }, EMPTY_CLASS_ARRAY, - AnnotationConfigContextLoader.class, true); - } - - private void assertClassesBarAttributes(ContextConfigurationAttributes attributes) { - assertAttributes(attributes, ClassesBar.class, EMPTY_STRING_ARRAY, new Class[] { BarConfig.class }, - AnnotationConfigContextLoader.class, true); - } - - private void assertMergedConfig(MergedContextConfiguration mergedConfig, Class expectedTestClass, - String[] expectedLocations, Class[] expectedClasses, - Class expectedContextLoaderClass) { - assertMergedConfig(mergedConfig, expectedTestClass, expectedLocations, expectedClasses, - EMPTY_INITIALIZER_CLASSES, expectedContextLoaderClass); - } - - private void assertMergedConfig( - MergedContextConfiguration mergedConfig, - Class expectedTestClass, - String[] expectedLocations, - Class[] expectedClasses, - Set>> expectedInitializerClasses, - Class expectedContextLoaderClass) { - assertNotNull(mergedConfig); - assertEquals(expectedTestClass, mergedConfig.getTestClass()); - assertNotNull(mergedConfig.getLocations()); - assertArrayEquals(expectedLocations, mergedConfig.getLocations()); - assertNotNull(mergedConfig.getClasses()); - assertArrayEquals(expectedClasses, mergedConfig.getClasses()); - assertNotNull(mergedConfig.getActiveProfiles()); - assertEquals(expectedContextLoaderClass, mergedConfig.getContextLoader().getClass()); - assertNotNull(mergedConfig.getContextInitializerClasses()); - assertEquals(expectedInitializerClasses, mergedConfig.getContextInitializerClasses()); - } - - private void debugConfigAttributes(List configAttributesList) { - // for (ContextConfigurationAttributes configAttributes : configAttributesList) { - // System.err.println(configAttributes); - // } - } - - @Test(expected = IllegalStateException.class) - public void resolveContextHierarchyAttributesForSingleTestClassWithContextConfigurationAndContextHierarchy() { - resolveContextHierarchyAttributes(SingleTestClassWithContextConfigurationAndContextHierarchy.class); - } - - @Test - public void resolveContextHierarchyAttributesForSingleTestClassWithImplicitSingleLevelContextHierarchy() { - List> hierarchyAttributes = resolveContextHierarchyAttributes(BareAnnotations.class); - assertEquals(1, hierarchyAttributes.size()); - List configAttributesList = hierarchyAttributes.get(0); - assertEquals(1, configAttributesList.size()); - debugConfigAttributes(configAttributesList); - } - - @Test - public void resolveContextHierarchyAttributesForSingleTestClassWithSingleLevelContextHierarchy() { - List> hierarchyAttributes = resolveContextHierarchyAttributes(SingleTestClassWithSingleLevelContextHierarchy.class); - assertEquals(1, hierarchyAttributes.size()); - List configAttributesList = hierarchyAttributes.get(0); - assertEquals(1, configAttributesList.size()); - debugConfigAttributes(configAttributesList); - } - - @Test - public void resolveContextHierarchyAttributesForSingleTestClassWithTripleLevelContextHierarchy() { - List> hierarchyAttributes = resolveContextHierarchyAttributes(SingleTestClassWithTripleLevelContextHierarchy.class); - assertEquals(1, hierarchyAttributes.size()); - List configAttributesList = hierarchyAttributes.get(0); - assertEquals(3, configAttributesList.size()); - debugConfigAttributes(configAttributesList); - } - - @Test - public void resolveContextHierarchyAttributesForTestClassHierarchyWithSingleLevelContextHierarchies() { - List> hierarchyAttributes = resolveContextHierarchyAttributes(TestClass3WithSingleLevelContextHierarchy.class); - assertEquals(3, hierarchyAttributes.size()); - - List configAttributesListClassLevel1 = hierarchyAttributes.get(0); - debugConfigAttributes(configAttributesListClassLevel1); - assertEquals(1, configAttributesListClassLevel1.size()); - assertThat(configAttributesListClassLevel1.get(0).getLocations()[0], equalTo("one.xml")); - - List configAttributesListClassLevel2 = hierarchyAttributes.get(1); - debugConfigAttributes(configAttributesListClassLevel2); - assertEquals(1, configAttributesListClassLevel2.size()); - assertArrayEquals(new String[] { "two-A.xml", "two-B.xml" }, - configAttributesListClassLevel2.get(0).getLocations()); - - List configAttributesListClassLevel3 = hierarchyAttributes.get(2); - debugConfigAttributes(configAttributesListClassLevel3); - assertEquals(1, configAttributesListClassLevel3.size()); - assertThat(configAttributesListClassLevel3.get(0).getLocations()[0], equalTo("three.xml")); - } - - @Test - public void resolveContextHierarchyAttributesForTestClassHierarchyWithBareContextConfigurationInSubclass() { - List> hierarchyAttributes = resolveContextHierarchyAttributes(TestClass2WithBareContextConfigurationInSubclass.class); - assertEquals(2, hierarchyAttributes.size()); - - List configAttributesListClassLevel1 = hierarchyAttributes.get(0); - debugConfigAttributes(configAttributesListClassLevel1); - assertEquals(1, configAttributesListClassLevel1.size()); - assertThat(configAttributesListClassLevel1.get(0).getLocations()[0], equalTo("one.xml")); - - List configAttributesListClassLevel2 = hierarchyAttributes.get(1); - debugConfigAttributes(configAttributesListClassLevel2); - assertEquals(1, configAttributesListClassLevel2.size()); - assertThat(configAttributesListClassLevel2.get(0).getLocations()[0], equalTo("two.xml")); - } - - @Test - public void resolveContextHierarchyAttributesForTestClassHierarchyWithBareContextConfigurationInSuperclass() { - List> hierarchyAttributes = resolveContextHierarchyAttributes(TestClass2WithBareContextConfigurationInSuperclass.class); - assertEquals(2, hierarchyAttributes.size()); - - List configAttributesListClassLevel1 = hierarchyAttributes.get(0); - debugConfigAttributes(configAttributesListClassLevel1); - assertEquals(1, configAttributesListClassLevel1.size()); - assertThat(configAttributesListClassLevel1.get(0).getLocations()[0], equalTo("one.xml")); - - List configAttributesListClassLevel2 = hierarchyAttributes.get(1); - debugConfigAttributes(configAttributesListClassLevel2); - assertEquals(1, configAttributesListClassLevel2.size()); - assertThat(configAttributesListClassLevel2.get(0).getLocations()[0], equalTo("two.xml")); - } - - @Test - public void resolveContextHierarchyAttributesForTestClassHierarchyWithMultiLevelContextHierarchies() { - List> hierarchyAttributes = resolveContextHierarchyAttributes(TestClass3WithMultiLevelContextHierarchy.class); - assertEquals(3, hierarchyAttributes.size()); - - List configAttributesListClassLevel1 = hierarchyAttributes.get(0); - debugConfigAttributes(configAttributesListClassLevel1); - assertEquals(2, configAttributesListClassLevel1.size()); - assertThat(configAttributesListClassLevel1.get(0).getLocations()[0], equalTo("1-A.xml")); - assertThat(configAttributesListClassLevel1.get(1).getLocations()[0], equalTo("1-B.xml")); - - List configAttributesListClassLevel2 = hierarchyAttributes.get(1); - debugConfigAttributes(configAttributesListClassLevel2); - assertEquals(2, configAttributesListClassLevel2.size()); - assertThat(configAttributesListClassLevel2.get(0).getLocations()[0], equalTo("2-A.xml")); - assertThat(configAttributesListClassLevel2.get(1).getLocations()[0], equalTo("2-B.xml")); - - List configAttributesListClassLevel3 = hierarchyAttributes.get(2); - debugConfigAttributes(configAttributesListClassLevel3); - assertEquals(3, configAttributesListClassLevel3.size()); - assertThat(configAttributesListClassLevel3.get(0).getLocations()[0], equalTo("3-A.xml")); - assertThat(configAttributesListClassLevel3.get(1).getLocations()[0], equalTo("3-B.xml")); - assertThat(configAttributesListClassLevel3.get(2).getLocations()[0], equalTo("3-C.xml")); - } - - private void assertContextConfigEntriesAreNotUnique(Class testClass) { - try { - resolveContextHierarchyAttributes(testClass); - fail("Should throw an IllegalStateException"); - } - catch (IllegalStateException e) { - String msg = String.format( - "The @ContextConfiguration elements configured via @ContextHierarchy in test class [%s] must define unique contexts to load.", - testClass.getName()); - assertEquals(msg, e.getMessage()); - } - } - - @Test - public void resolveContextHierarchyAttributesForSingleTestClassWithMultiLevelContextHierarchyWithEmptyContextConfig() { - assertContextConfigEntriesAreNotUnique(SingleTestClassWithMultiLevelContextHierarchyWithEmptyContextConfig.class); - } - - @Test - public void resolveContextHierarchyAttributesForSingleTestClassWithMultiLevelContextHierarchyWithDuplicatedContextConfig() { - assertContextConfigEntriesAreNotUnique(SingleTestClassWithMultiLevelContextHierarchyWithDuplicatedContextConfig.class); - } - - @Test - public void buildContextHierarchyMapForTestClassHierarchyWithMultiLevelContextHierarchies() { - Map> map = buildContextHierarchyMap(TestClass3WithMultiLevelContextHierarchy.class); - - assertThat(map.size(), is(3)); - assertThat(map.keySet(), hasItems("alpha", "beta", "gamma")); - - List alphaConfig = map.get("alpha"); - assertThat(alphaConfig.size(), is(3)); - assertThat(alphaConfig.get(0).getLocations()[0], is("1-A.xml")); - assertThat(alphaConfig.get(1).getLocations()[0], is("2-A.xml")); - assertThat(alphaConfig.get(2).getLocations()[0], is("3-A.xml")); - - List betaConfig = map.get("beta"); - assertThat(betaConfig.size(), is(3)); - assertThat(betaConfig.get(0).getLocations()[0], is("1-B.xml")); - assertThat(betaConfig.get(1).getLocations()[0], is("2-B.xml")); - assertThat(betaConfig.get(2).getLocations()[0], is("3-B.xml")); - - List gammaConfig = map.get("gamma"); - assertThat(gammaConfig.size(), is(1)); - assertThat(gammaConfig.get(0).getLocations()[0], is("3-C.xml")); - } - - @Test - public void buildContextHierarchyMapForTestClassHierarchyWithMultiLevelContextHierarchiesAndUnnamedConfig() { - Map> map = buildContextHierarchyMap(TestClass3WithMultiLevelContextHierarchyAndUnnamedConfig.class); - - String level1 = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + 1; - String level2 = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + 2; - String level3 = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + 3; - String level4 = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + 4; - String level5 = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + 5; - String level6 = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + 6; - String level7 = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + 7; - - assertThat(map.size(), is(7)); - assertThat(map.keySet(), hasItems(level1, level2, level3, level4, level5, level6, level7)); - - List level1Config = map.get(level1); - assertThat(level1Config.size(), is(1)); - assertThat(level1Config.get(0).getLocations()[0], is("1-A.xml")); - - List level2Config = map.get(level2); - assertThat(level2Config.size(), is(1)); - assertThat(level2Config.get(0).getLocations()[0], is("1-B.xml")); - - List level3Config = map.get(level3); - assertThat(level3Config.size(), is(1)); - assertThat(level3Config.get(0).getLocations()[0], is("2-A.xml")); - - // ... - - List level7Config = map.get(level7); - assertThat(level7Config.size(), is(1)); - assertThat(level7Config.get(0).getLocations()[0], is("3-C.xml")); - } - - @Test - public void buildContextHierarchyMapForTestClassHierarchyWithMultiLevelContextHierarchiesAndPartiallyNamedConfig() { - Map> map = buildContextHierarchyMap(TestClass2WithMultiLevelContextHierarchyAndPartiallyNamedConfig.class); - - String level1 = "parent"; - String level2 = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + 2; - String level3 = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + 3; - - assertThat(map.size(), is(3)); - assertThat(map.keySet(), hasItems(level1, level2, level3)); - Iterator levels = map.keySet().iterator(); - assertThat(levels.next(), is(level1)); - assertThat(levels.next(), is(level2)); - assertThat(levels.next(), is(level3)); - - List level1Config = map.get(level1); - assertThat(level1Config.size(), is(2)); - assertThat(level1Config.get(0).getLocations()[0], is("1-A.xml")); - assertThat(level1Config.get(1).getLocations()[0], is("2-A.xml")); - - List level2Config = map.get(level2); - assertThat(level2Config.size(), is(1)); - assertThat(level2Config.get(0).getLocations()[0], is("1-B.xml")); - - List level3Config = map.get(level3); - assertThat(level3Config.size(), is(1)); - assertThat(level3Config.get(0).getLocations()[0], is("2-C.xml")); - } - - @Test(expected = IllegalStateException.class) - public void resolveConfigAttributesWithConflictingLocations() { - resolveContextConfigurationAttributes(ConflictingLocations.class); - } - - @Test - public void resolveConfigAttributesWithBareAnnotations() { - List attributesList = resolveContextConfigurationAttributes(BareAnnotations.class); - assertNotNull(attributesList); - assertEquals(1, attributesList.size()); - assertAttributes(attributesList.get(0), BareAnnotations.class, EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, - ContextLoader.class, true); - } - - @Test - public void resolveConfigAttributesWithLocalAnnotationAndLocations() { - List attributesList = resolveContextConfigurationAttributes(LocationsFoo.class); - assertNotNull(attributesList); - assertEquals(1, attributesList.size()); - assertLocationsFooAttributes(attributesList.get(0)); - } - - @Test - public void resolveConfigAttributesWithLocalAnnotationAndClasses() { - List attributesList = resolveContextConfigurationAttributes(ClassesFoo.class); - assertNotNull(attributesList); - assertEquals(1, attributesList.size()); - assertClassesFooAttributes(attributesList.get(0)); - } - - @Test - public void resolveConfigAttributesWithLocalAndInheritedAnnotationsAndLocations() { - List attributesList = resolveContextConfigurationAttributes(LocationsBar.class); - assertNotNull(attributesList); - assertEquals(2, attributesList.size()); - assertLocationsBarAttributes(attributesList.get(0)); - assertLocationsFooAttributes(attributesList.get(1)); - } - - @Test - public void resolveConfigAttributesWithLocalAndInheritedAnnotationsAndClasses() { - List attributesList = resolveContextConfigurationAttributes(ClassesBar.class); - assertNotNull(attributesList); - assertEquals(2, attributesList.size()); - assertClassesBarAttributes(attributesList.get(0)); - assertClassesFooAttributes(attributesList.get(1)); - } - - @Test(expected = IllegalArgumentException.class) - public void buildMergedConfigWithoutAnnotation() { - buildMergedContextConfiguration(Enigma.class, null, null); - } - - @Test - public void buildMergedConfigWithBareAnnotations() { - Class testClass = BareAnnotations.class; - MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null); - - assertMergedConfig( - mergedConfig, - testClass, - new String[] { "classpath:/org/springframework/test/context/ContextLoaderUtilsTests$BareAnnotations-context.xml" }, - EMPTY_CLASS_ARRAY, DelegatingSmartContextLoader.class); - } - - @Test - public void buildMergedConfigWithLocalAnnotationAndLocations() { - Class testClass = LocationsFoo.class; - MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null); - - assertMergedConfig(mergedConfig, testClass, new String[] { "classpath:/foo.xml" }, EMPTY_CLASS_ARRAY, - DelegatingSmartContextLoader.class); - } - - @Test - public void buildMergedConfigWithLocalAnnotationAndClasses() { - Class testClass = ClassesFoo.class; - MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null); - - assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, new Class[] { FooConfig.class }, - DelegatingSmartContextLoader.class); - } - - @Test - public void buildMergedConfigWithLocalAnnotationAndOverriddenContextLoaderAndLocations() { - Class testClass = LocationsFoo.class; - Class expectedContextLoaderClass = GenericPropertiesContextLoader.class; - MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, - expectedContextLoaderClass.getName(), null); - - assertMergedConfig(mergedConfig, testClass, new String[] { "classpath:/foo.xml" }, EMPTY_CLASS_ARRAY, - expectedContextLoaderClass); - } - - @Test - public void buildMergedConfigWithLocalAnnotationAndOverriddenContextLoaderAndClasses() { - Class testClass = ClassesFoo.class; - Class expectedContextLoaderClass = GenericPropertiesContextLoader.class; - MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, - expectedContextLoaderClass.getName(), null); - - assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, new Class[] { FooConfig.class }, - expectedContextLoaderClass); - } - - @Test - public void buildMergedConfigWithLocalAndInheritedAnnotationsAndLocations() { - Class testClass = LocationsBar.class; - String[] expectedLocations = new String[] { "/foo.xml", "/bar.xml" }; - - MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null); - assertMergedConfig(mergedConfig, testClass, expectedLocations, EMPTY_CLASS_ARRAY, - AnnotationConfigContextLoader.class); - } - - @Test - public void buildMergedConfigWithLocalAndInheritedAnnotationsAndClasses() { - Class testClass = ClassesBar.class; - Class[] expectedClasses = new Class[] { FooConfig.class, BarConfig.class }; - - MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null); - assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, - AnnotationConfigContextLoader.class); - } - - @Test - public void buildMergedConfigWithAnnotationsAndOverriddenLocations() { - Class testClass = OverriddenLocationsBar.class; - String[] expectedLocations = new String[] { "/bar.xml" }; - - MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null); - assertMergedConfig(mergedConfig, testClass, expectedLocations, EMPTY_CLASS_ARRAY, - AnnotationConfigContextLoader.class); - } - - @Test - public void buildMergedConfigWithAnnotationsAndOverriddenClasses() { - Class testClass = OverriddenClassesBar.class; - Class[] expectedClasses = new Class[] { BarConfig.class }; - - MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null); - assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, - AnnotationConfigContextLoader.class); - } - - @Test - public void buildMergedConfigWithLocalInitializer() { - Class testClass = InitializersFoo.class; - Class[] expectedClasses = new Class[] { FooConfig.class }; - Set>> expectedInitializerClasses// - = new HashSet>>(); - expectedInitializerClasses.add(FooInitializer.class); - - MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null); - assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, expectedInitializerClasses, - DelegatingSmartContextLoader.class); - } - - @Test - public void buildMergedConfigWithLocalAndInheritedInitializer() { - Class testClass = InitializersBar.class; - Class[] expectedClasses = new Class[] { FooConfig.class, BarConfig.class }; - Set>> expectedInitializerClasses// - = new HashSet>>(); - expectedInitializerClasses.add(FooInitializer.class); - expectedInitializerClasses.add(BarInitializer.class); - - MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null); - assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, expectedInitializerClasses, - DelegatingSmartContextLoader.class); - } - - @Test - public void buildMergedConfigWithOverriddenInitializers() { - Class testClass = OverriddenInitializersBar.class; - Class[] expectedClasses = new Class[] { FooConfig.class, BarConfig.class }; - Set>> expectedInitializerClasses// - = new HashSet>>(); - expectedInitializerClasses.add(BarInitializer.class); - - MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null); - assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, expectedInitializerClasses, - DelegatingSmartContextLoader.class); - } - - @Test - public void buildMergedConfigWithOverriddenInitializersAndClasses() { - Class testClass = OverriddenInitializersAndClassesBar.class; - Class[] expectedClasses = new Class[] { BarConfig.class }; - Set>> expectedInitializerClasses// - = new HashSet>>(); - expectedInitializerClasses.add(BarInitializer.class); - - MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null); - assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, expectedInitializerClasses, - DelegatingSmartContextLoader.class); - } - - @Test - public void resolveActiveProfilesWithoutAnnotation() { - String[] profiles = resolveActiveProfiles(Enigma.class); - assertArrayEquals(EMPTY_STRING_ARRAY, profiles); - } - - @Test - public void resolveActiveProfilesWithNoProfilesDeclared() { - String[] profiles = resolveActiveProfiles(BareAnnotations.class); - assertArrayEquals(EMPTY_STRING_ARRAY, profiles); - } - - @Test - public void resolveActiveProfilesWithEmptyProfiles() { - String[] profiles = resolveActiveProfiles(EmptyProfiles.class); - assertArrayEquals(EMPTY_STRING_ARRAY, profiles); - } - - @Test - public void resolveActiveProfilesWithDuplicatedProfiles() { - String[] profiles = resolveActiveProfiles(DuplicatedProfiles.class); - assertNotNull(profiles); - assertEquals(3, profiles.length); - - List list = Arrays.asList(profiles); - assertTrue(list.contains("foo")); - assertTrue(list.contains("bar")); - assertTrue(list.contains("baz")); - } - - @Test - public void resolveActiveProfilesWithLocalAnnotation() { - String[] profiles = resolveActiveProfiles(LocationsFoo.class); - assertNotNull(profiles); - assertArrayEquals(new String[] { "foo" }, profiles); - } - - @Test - public void resolveActiveProfilesWithInheritedAnnotationAndLocations() { - String[] profiles = resolveActiveProfiles(InheritedLocationsFoo.class); - assertNotNull(profiles); - assertArrayEquals(new String[] { "foo" }, profiles); - } - - @Test - public void resolveActiveProfilesWithInheritedAnnotationAndClasses() { - String[] profiles = resolveActiveProfiles(InheritedClassesFoo.class); - assertNotNull(profiles); - assertArrayEquals(new String[] { "foo" }, profiles); - } - - @Test - public void resolveActiveProfilesWithLocalAndInheritedAnnotations() { - String[] profiles = resolveActiveProfiles(LocationsBar.class); - assertNotNull(profiles); - assertEquals(2, profiles.length); - - List list = Arrays.asList(profiles); - assertTrue(list.contains("foo")); - assertTrue(list.contains("bar")); - } - - @Test - public void resolveActiveProfilesWithOverriddenAnnotation() { - String[] profiles = resolveActiveProfiles(Animals.class); - assertNotNull(profiles); - assertEquals(2, profiles.length); - - List list = Arrays.asList(profiles); - assertTrue(list.contains("dog")); - assertTrue(list.contains("cat")); - } - - - // ------------------------------------------------------------------------- - - private static class Enigma { - } - - @ContextConfiguration(value = "x", locations = "y") - private static class ConflictingLocations { - } - - @ContextConfiguration - @ActiveProfiles - private static class BareAnnotations { - } - - @ActiveProfiles({ " ", "\t" }) - private static class EmptyProfiles { - } - - @ActiveProfiles({ "foo", "bar", " foo", "bar ", "baz" }) - private static class DuplicatedProfiles { - } - - @Configuration - private static class FooConfig { - } - - @ContextConfiguration(locations = "/foo.xml", inheritLocations = false) - @ActiveProfiles(profiles = "foo") - private static class LocationsFoo { - } - - @ContextConfiguration(classes = FooConfig.class, inheritLocations = false) - @ActiveProfiles(profiles = "foo") - private static class ClassesFoo { - } - - private static class InheritedLocationsFoo extends LocationsFoo { - } - - private static class InheritedClassesFoo extends ClassesFoo { - } - - @Configuration - private static class BarConfig { - } - - @ContextConfiguration(locations = "/bar.xml", inheritLocations = true, loader = AnnotationConfigContextLoader.class) - @ActiveProfiles("bar") - private static class LocationsBar extends LocationsFoo { - } - - @ContextConfiguration(locations = "/bar.xml", inheritLocations = false, loader = AnnotationConfigContextLoader.class) - @ActiveProfiles("bar") - private static class OverriddenLocationsBar extends LocationsFoo { - } - - @ContextConfiguration(classes = BarConfig.class, inheritLocations = true, loader = AnnotationConfigContextLoader.class) - @ActiveProfiles("bar") - private static class ClassesBar extends ClassesFoo { - } - - @ContextConfiguration(classes = BarConfig.class, inheritLocations = false, loader = AnnotationConfigContextLoader.class) - @ActiveProfiles("bar") - private static class OverriddenClassesBar extends ClassesFoo { - } - - @ActiveProfiles(profiles = { "dog", "cat" }, inheritProfiles = false) - private static class Animals extends LocationsBar { - } - - private static class FooInitializer implements ApplicationContextInitializer { - - @Override - public void initialize(GenericApplicationContext applicationContext) { - } - } - - private static class BarInitializer implements ApplicationContextInitializer { - - @Override - public void initialize(GenericWebApplicationContext applicationContext) { - } - } - - @ContextConfiguration(classes = FooConfig.class, initializers = FooInitializer.class) - private static class InitializersFoo { - } - - @ContextConfiguration(classes = BarConfig.class, initializers = BarInitializer.class) - private static class InitializersBar extends InitializersFoo { - } - - @ContextConfiguration(classes = BarConfig.class, initializers = BarInitializer.class, inheritInitializers = false) - private static class OverriddenInitializersBar extends InitializersFoo { - } - - @ContextConfiguration(classes = BarConfig.class, inheritLocations = false, initializers = BarInitializer.class, inheritInitializers = false) - private static class OverriddenInitializersAndClassesBar extends InitializersFoo { - } - - @ContextConfiguration("foo.xml") - @ContextHierarchy(@ContextConfiguration("bar.xml")) - private static class SingleTestClassWithContextConfigurationAndContextHierarchy { - } - - @ContextHierarchy(@ContextConfiguration("A.xml")) - private static class SingleTestClassWithSingleLevelContextHierarchy { - } - - @ContextHierarchy({// - // - @ContextConfiguration("A.xml"),// - @ContextConfiguration("B.xml"),// - @ContextConfiguration("C.xml") // - }) - private static class SingleTestClassWithTripleLevelContextHierarchy { - } - - @ContextHierarchy(@ContextConfiguration("one.xml")) - private static class TestClass1WithSingleLevelContextHierarchy { - } - - @ContextHierarchy(@ContextConfiguration({ "two-A.xml", "two-B.xml" })) - private static class TestClass2WithSingleLevelContextHierarchy extends TestClass1WithSingleLevelContextHierarchy { - } - - @ContextHierarchy(@ContextConfiguration("three.xml")) - private static class TestClass3WithSingleLevelContextHierarchy extends TestClass2WithSingleLevelContextHierarchy { - } - - @ContextConfiguration("one.xml") - private static class TestClass1WithBareContextConfigurationInSuperclass { - } - - @ContextHierarchy(@ContextConfiguration("two.xml")) - private static class TestClass2WithBareContextConfigurationInSuperclass extends - TestClass1WithBareContextConfigurationInSuperclass { - } - - @ContextHierarchy(@ContextConfiguration("one.xml")) - private static class TestClass1WithBareContextConfigurationInSubclass { - } - - @ContextConfiguration("two.xml") - private static class TestClass2WithBareContextConfigurationInSubclass extends - TestClass1WithBareContextConfigurationInSuperclass { - } - - @ContextHierarchy({// - // - @ContextConfiguration(locations = "1-A.xml", name = "alpha"),// - @ContextConfiguration(locations = "1-B.xml", name = "beta") // - }) - private static class TestClass1WithMultiLevelContextHierarchy { - } - - @ContextHierarchy({// - // - @ContextConfiguration(locations = "2-A.xml", name = "alpha"),// - @ContextConfiguration(locations = "2-B.xml", name = "beta") // - }) - private static class TestClass2WithMultiLevelContextHierarchy extends TestClass1WithMultiLevelContextHierarchy { - } - - @ContextHierarchy({// - // - @ContextConfiguration(locations = "3-A.xml", name = "alpha"),// - @ContextConfiguration(locations = "3-B.xml", name = "beta"),// - @ContextConfiguration(locations = "3-C.xml", name = "gamma") // - }) - private static class TestClass3WithMultiLevelContextHierarchy extends TestClass2WithMultiLevelContextHierarchy { - } - - @ContextHierarchy({// - // - @ContextConfiguration(locations = "1-A.xml"),// - @ContextConfiguration(locations = "1-B.xml") // - }) - private static class TestClass1WithMultiLevelContextHierarchyAndUnnamedConfig { - } - - @ContextHierarchy({// - // - @ContextConfiguration(locations = "2-A.xml"),// - @ContextConfiguration(locations = "2-B.xml") // - }) - private static class TestClass2WithMultiLevelContextHierarchyAndUnnamedConfig extends - TestClass1WithMultiLevelContextHierarchyAndUnnamedConfig { - } - - @ContextHierarchy({// - // - @ContextConfiguration(locations = "3-A.xml"),// - @ContextConfiguration(locations = "3-B.xml"),// - @ContextConfiguration(locations = "3-C.xml") // - }) - private static class TestClass3WithMultiLevelContextHierarchyAndUnnamedConfig extends - TestClass2WithMultiLevelContextHierarchyAndUnnamedConfig { - } - - @ContextHierarchy({// - // - @ContextConfiguration(locations = "1-A.xml", name = "parent"),// - @ContextConfiguration(locations = "1-B.xml") // - }) - private static class TestClass1WithMultiLevelContextHierarchyAndPartiallyNamedConfig { - } - - @ContextHierarchy({// - // - @ContextConfiguration(locations = "2-A.xml", name = "parent"),// - @ContextConfiguration(locations = "2-C.xml") // - }) - private static class TestClass2WithMultiLevelContextHierarchyAndPartiallyNamedConfig extends - TestClass1WithMultiLevelContextHierarchyAndPartiallyNamedConfig { - } - - @ContextHierarchy({ - // - @ContextConfiguration,// - @ContextConfiguration // - }) - private static class SingleTestClassWithMultiLevelContextHierarchyWithEmptyContextConfig { - } - - @ContextHierarchy({ - // - @ContextConfiguration("foo.xml"),// - @ContextConfiguration(classes = BarConfig.class),// duplicate! - @ContextConfiguration("baz.xml"),// - @ContextConfiguration(classes = BarConfig.class),// duplicate! - @ContextConfiguration(loader = AnnotationConfigContextLoader.class) // - }) - private static class SingleTestClassWithMultiLevelContextHierarchyWithDuplicatedContextConfig { - } - -} diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/DispatcherWacRootWacEarTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/DispatcherWacRootWacEarTests.java index 622547c48b..33ca51cfae 100644 --- a/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/DispatcherWacRootWacEarTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/DispatcherWacRootWacEarTests.java @@ -18,6 +18,7 @@ package org.springframework.test.context.hierarchies.web; import javax.servlet.ServletContext; +import org.junit.Ignore; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; @@ -47,12 +48,14 @@ public class DispatcherWacRootWacEarTests extends RootWacEarTests { private String dispatcher; + @Ignore("Superseded by verifyDispatcherWacConfig()") @Test @Override public void verifyEarConfig() { /* no-op */ } + @Ignore("Superseded by verifyDispatcherWacConfig()") @Test @Override public void verifyRootWacConfig() { diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/RootWacEarTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/RootWacEarTests.java index dbd3e93aa8..ca5439415f 100644 --- a/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/RootWacEarTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/RootWacEarTests.java @@ -16,6 +16,7 @@ package org.springframework.test.context.hierarchies.web; +import org.junit.Ignore; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; @@ -58,6 +59,7 @@ public class RootWacEarTests extends EarTests { private String root; + @Ignore("Superseded by verifyRootWacConfig()") @Test @Override public void verifyEarConfig() { diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4TestSuite.java b/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4TestSuite.java index 72ddd1b8c4..181bc8f7cb 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4TestSuite.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4TestSuite.java @@ -37,7 +37,9 @@ import org.springframework.test.context.junit4.annotation.ExplicitConfigClassesI import org.springframework.test.context.junit4.orm.HibernateSessionFlushingTests; import org.springframework.test.context.junit4.profile.annotation.DefaultProfileAnnotationConfigTests; import org.springframework.test.context.junit4.profile.annotation.DevProfileAnnotationConfigTests; +import org.springframework.test.context.junit4.profile.annotation.DevProfileResolverAnnotationConfigTests; import org.springframework.test.context.junit4.profile.xml.DefaultProfileXmlConfigTests; +import org.springframework.test.context.junit4.profile.xml.DevProfileResolverXmlConfigTests; import org.springframework.test.context.junit4.profile.xml.DevProfileXmlConfigTests; /** @@ -77,8 +79,10 @@ StandardJUnit4FeaturesTests.class,// DefaultLoaderBeanOverridingExplicitConfigClassesInheritedTests.class,// DefaultProfileAnnotationConfigTests.class,// DevProfileAnnotationConfigTests.class,// + DevProfileResolverAnnotationConfigTests.class,// DefaultProfileXmlConfigTests.class,// DevProfileXmlConfigTests.class,// + DevProfileResolverXmlConfigTests.class,// ExpectedExceptionSpringRunnerTests.class,// TimedSpringRunnerTests.class,// RepeatedSpringRunnerTests.class,// diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DevProfileResolverAnnotationConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DevProfileResolverAnnotationConfigTests.java new file mode 100644 index 0000000000..09a4bca53a --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DevProfileResolverAnnotationConfigTests.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context.junit4.profile.annotation; + +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ActiveProfilesResolver; + +/** + * @author Michail Nikolaev + * @since 4.0 + */ +@ActiveProfiles(resolver = DevProfileResolverAnnotationConfigTests.class, inheritProfiles = false) +public class DevProfileResolverAnnotationConfigTests extends DevProfileAnnotationConfigTests implements + ActiveProfilesResolver { + + @Override + public String[] resolve(Class testClass) { + return new String[] { "dev" }; + } +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/ProfileAnnotationConfigTestSuite.java b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/ProfileAnnotationConfigTestSuite.java index b3b7068c39..d0286917d8 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/ProfileAnnotationConfigTestSuite.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/ProfileAnnotationConfigTestSuite.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,8 @@ import org.junit.runners.Suite.SuiteClasses; // Note: the following 'multi-line' layout is for enhanced code readability. @SuiteClasses({// DefaultProfileAnnotationConfigTests.class,// - DevProfileAnnotationConfigTests.class // + DevProfileAnnotationConfigTests.class,// + DevProfileResolverAnnotationConfigTests.class // }) public class ProfileAnnotationConfigTestSuite { } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DevProfileResolverAnnotationConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DevProfileResolverAnnotationConfigTests.java new file mode 100644 index 0000000000..2cfafabe37 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DevProfileResolverAnnotationConfigTests.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context.junit4.profile.importresource; + +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ActiveProfilesResolver; + +/** + * @author Michail Nikolaev + * @since 4.0 + */ +@ActiveProfiles(resolver = DevProfileResolverAnnotationConfigTests.class, inheritProfiles = false) +public class DevProfileResolverAnnotationConfigTests extends DevProfileAnnotationConfigTests implements + ActiveProfilesResolver { + + @Override + public String[] resolve(Class testClass) { + return new String[] { "dev" }; + } +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/resolver/ClassNameActiveProfilesResolver.java b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/resolver/ClassNameActiveProfilesResolver.java new file mode 100644 index 0000000000..51d5fc4c5d --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/resolver/ClassNameActiveProfilesResolver.java @@ -0,0 +1,31 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context.junit4.profile.resolver; + +import org.springframework.test.context.ActiveProfilesResolver; + +/** + * @author Michail Nikolaev + * @since 4.0 + */ +public class ClassNameActiveProfilesResolver implements ActiveProfilesResolver { + + @Override + public String[] resolve(Class testClass) { + return new String[] { testClass.getSimpleName().toLowerCase() }; + } +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/resolver/ClassNameActiveProfilesResolverTest.java b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/resolver/ClassNameActiveProfilesResolverTest.java new file mode 100644 index 0000000000..52e705250d --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/resolver/ClassNameActiveProfilesResolverTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context.junit4.profile.resolver; + +import java.util.Arrays; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.junit.Assert.*; + +/** + * @author Michail Nikolaev + * @since 4.0 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +@ActiveProfiles(resolver = ClassNameActiveProfilesResolver.class) +public class ClassNameActiveProfilesResolverTest { + + @Configuration + static class Config { + + } + + + @Autowired + private ApplicationContext applicationContext; + + + @Test + public void test() { + assertTrue(Arrays.asList(applicationContext.getEnvironment().getActiveProfiles()).contains( + getClass().getSimpleName().toLowerCase())); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/DevProfileResolverXmlConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/DevProfileResolverXmlConfigTests.java new file mode 100644 index 0000000000..36c3fdcc07 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/DevProfileResolverXmlConfigTests.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context.junit4.profile.xml; + +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ActiveProfilesResolver; + +/** + * @author Michail Nikolaev + * @since 4.0 + */ +@ActiveProfiles(resolver = DevProfileResolverXmlConfigTests.class, inheritProfiles = false) +public class DevProfileResolverXmlConfigTests extends DevProfileXmlConfigTests implements ActiveProfilesResolver { + + @Override + public String[] resolve(Class testClass) { + return new String[] { "dev" }; + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/ProfileXmlConfigTestSuite.java b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/ProfileXmlConfigTestSuite.java index 38a5939c61..16230c0a55 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/ProfileXmlConfigTestSuite.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/ProfileXmlConfigTestSuite.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,8 @@ import org.junit.runners.Suite.SuiteClasses; // Note: the following 'multi-line' layout is for enhanced code readability. @SuiteClasses({// DefaultProfileXmlConfigTests.class,// - DevProfileXmlConfigTests.class // + DevProfileXmlConfigTests.class,// + DevProfileResolverXmlConfigTests.class // }) public class ProfileXmlConfigTestSuite { } diff --git a/spring-test/src/test/java/org/springframework/test/jdbc/JdbcTestUtilsTests.java b/spring-test/src/test/java/org/springframework/test/jdbc/JdbcTestUtilsTests.java index 6ebdf91819..04aad514f3 100644 --- a/spring-test/src/test/java/org/springframework/test/jdbc/JdbcTestUtilsTests.java +++ b/spring-test/src/test/java/org/springframework/test/jdbc/JdbcTestUtilsTests.java @@ -48,6 +48,7 @@ public class JdbcTestUtilsTests { @Mock private JdbcTemplate jdbcTemplate; + @Test public void containsDelimiters() { assertTrue("test with ';' is wrong", !JdbcTestUtils.containsSqlScriptDelimiters("select 1\n select ';'", ';')); @@ -117,26 +118,52 @@ public class JdbcTestUtilsTests { assertEquals("statement 4 not split correctly", statement4, statements.get(3)); } + /** + * See SPR-10330 + * @since 4.0 + */ @Test - public void testDeleteNoWhere() throws Exception { + public void readAndSplitScriptContainingCommentsWithLeadingTabs() throws Exception { + + EncodedResource resource = new EncodedResource(new ClassPathResource( + "test-data-with-comments-and-leading-tabs.sql", getClass())); + LineNumberReader lineNumberReader = new LineNumberReader(resource.getReader()); + + String script = JdbcTestUtils.readScript(lineNumberReader); + + char delim = ';'; + List statements = new ArrayList(); + JdbcTestUtils.splitSqlScript(script, delim, statements); + + String statement1 = "insert into customer (id, name) values (1, 'Sam Brannen')"; + String statement2 = "insert into orders(id, order_date, customer_id) values (1, '2013-06-08', 1)"; + String statement3 = "insert into orders(id, order_date, customer_id) values (2, '2013-06-08', 1)"; + + assertEquals("wrong number of statements", 3, statements.size()); + assertEquals("statement 1 not split correctly", statement1, statements.get(0)); + assertEquals("statement 2 not split correctly", statement2, statements.get(1)); + assertEquals("statement 3 not split correctly", statement3, statements.get(2)); + } + + @Test + public void deleteWithoutWhereClause() throws Exception { given(jdbcTemplate.update("DELETE FROM person")).willReturn(10); int deleted = JdbcTestUtils.deleteFromTableWhere(jdbcTemplate, "person", null); assertThat(deleted, equalTo(10)); } @Test - public void testDeleteWhere() throws Exception { + public void deleteWithWhereClause() throws Exception { given(jdbcTemplate.update("DELETE FROM person WHERE name = 'Bob' and age > 25")).willReturn(10); int deleted = JdbcTestUtils.deleteFromTableWhere(jdbcTemplate, "person", "name = 'Bob' and age > 25"); assertThat(deleted, equalTo(10)); } @Test - public void deleteWhereAndArguments() throws Exception { + public void deleteWithWhereClauseAndArguments() throws Exception { given(jdbcTemplate.update("DELETE FROM person WHERE name = ? and age > ?", "Bob", 25)).willReturn(10); int deleted = JdbcTestUtils.deleteFromTableWhere(jdbcTemplate, "person", "name = ? and age > ?", "Bob", 25); assertThat(deleted, equalTo(10)); } - } diff --git a/spring-test/src/test/resources/org/springframework/test/jdbc/test-data-with-comments-and-leading-tabs.sql b/spring-test/src/test/resources/org/springframework/test/jdbc/test-data-with-comments-and-leading-tabs.sql new file mode 100644 index 0000000000..8f54e9eb7d --- /dev/null +++ b/spring-test/src/test/resources/org/springframework/test/jdbc/test-data-with-comments-and-leading-tabs.sql @@ -0,0 +1,9 @@ +-- The next comment line starts with a tab. + -- x, y, z... + +insert into customer (id, name) +values (1, 'Sam Brannen'); + -- This is also a comment with a leading tab. +insert into orders(id, order_date, customer_id) values (1, '2013-06-08', 1); + -- This is also a comment with a leading tab, a space, and a tab. +insert into orders(id, order_date, customer_id) values (2, '2013-06-08', 1); diff --git a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java index c41d04d69e..7e538ffe92 100644 --- a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java +++ b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -412,8 +412,19 @@ public class HttpHeaders implements MultiValueMap, Serializable * Returns the value of the {@code IfModifiedSince} header. *

          The date is returned as the number of milliseconds since January 1, 1970 GMT. Returns -1 when the date is unknown. * @return the header value + * @deprecated use {@link #getIfModifiedSince()} */ + @Deprecated public long getIfNotModifiedSince() { + return getIfModifiedSince(); + } + + /** + * Returns the value of the {@code If-Modified-Since} header. + *

          The date is returned as the number of milliseconds since January 1, 1970 GMT. Returns -1 when the date is unknown. + * @return the header value + */ + public long getIfModifiedSince() { return getFirstDate(IF_MODIFIED_SINCE); } 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 c15dc17edf..9dbff1f5a0 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 @@ -144,15 +144,15 @@ import java.util.concurrent.Callable; * Such command objects along with their validation results will be exposed * as model attributes, by default using the non-qualified command class name * in property notation (e.g. "orderAddress" for type "mypackage.OrderAddress"). - * Specify a parameter-level {@link ModelAttribute} annotation for declaring - * a specific model attribute name. + * Specify a parameter-level {@link ModelAttribute @ModelAttribute} annotation for + * declaring a specific model attribute name. *

        1. {@link org.springframework.validation.Errors} / * {@link org.springframework.validation.BindingResult} validation results * for a preceding command/form object (the immediate preceding argument). *
        2. {@link org.springframework.web.bind.support.SessionStatus} status handle * 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). + * attributes that have been indicated by the {@link SessionAttributes @SessionAttributes} + * annotation at the handler type level). *
        3. {@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, @@ -161,26 +161,26 @@ import java.util.concurrent.Callable; * *

          The following return types are supported for handler methods: *

            - *
          • A {@code ModelAndView} object (Servlet MVC or Portlet MVC), + *
          • A {@link ModelAndView} object (Servlet MVC or Portlet MVC), * with the model implicitly enriched with command objects and the results - * of {@link ModelAttribute} annotated reference data accessor methods. + * of {@link ModelAttribute @ModelAttribute} annotated reference data accessor methods. *
          • A {@link org.springframework.ui.Model Model} object, with the view name * implicitly determined through a {@link org.springframework.web.servlet.RequestToViewNameTranslator} * and the model implicitly enriched with command objects and the results - * of {@link ModelAttribute} annotated reference data accessor methods. + * of {@link ModelAttribute @ModelAttribute} annotated reference data accessor methods. *
          • A {@link java.util.Map} object for exposing a model, * with the view name implicitly determined through a * {@link org.springframework.web.servlet.RequestToViewNameTranslator} * and the model implicitly enriched with command objects and the results - * of {@link ModelAttribute} annotated reference data accessor methods. + * of {@link ModelAttribute @ModelAttribute} annotated reference data accessor methods. *
          • A {@link org.springframework.web.servlet.View} object, with the * model implicitly determined through command objects and - * {@link ModelAttribute} annotated reference data accessor methods. + * {@link ModelAttribute @ModelAttribute} annotated reference data accessor methods. * The handler method may also programmatically enrich the model by * declaring a {@link org.springframework.ui.Model} argument (see above). *
          • A {@link String} value which is interpreted as view name, * with the model implicitly determined through command objects and - * {@link ModelAttribute} annotated reference data accessor methods. + * {@link ModelAttribute @ModelAttribute} annotated reference data accessor methods. * The handler method may also programmatically enrich the model by * declaring a {@link org.springframework.ui.ModelMap} argument * (see above). @@ -198,7 +198,7 @@ import java.util.concurrent.Callable; *
          • A {@link Callable} which is used by Spring MVC to obtain the return * value asynchronously in a separate thread transparently managed by Spring MVC * on behalf of the application. - *
          • A {@code org.springframework.web.context.request.async.DeferredResult} + *
          • A {@link org.springframework.web.context.request.async.DeferredResult} * which the application uses to produce a return value in a separate * thread of its own choosing, as an alternative to returning a Callable. *
          • {@code void} if the method handles the response itself (by @@ -211,10 +211,10 @@ import java.util.concurrent.Callable; * only applicable in a Servlet environment). *
          • Any other return type will be considered as single model attribute * to be exposed to the view, using the attribute name specified through - * {@link ModelAttribute} at the method level (or the default attribute name - * based on the return type's class name otherwise). The model will be + * {@link ModelAttribute @ModelAttribute} at the method level (or the default attribute + * name based on the return type's class name otherwise). The model will be * implicitly enriched with command objects and the results of - * {@link ModelAttribute} annotated reference data accessor methods. + * {@link ModelAttribute @ModelAttribute} annotated reference data accessor methods. *
          * *

          NOTE: {@code @RequestMapping} will only be processed if an diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResult.java b/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResult.java index 70e89d1bce..34a4ad927f 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResult.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResult.java @@ -107,6 +107,22 @@ public class DeferredResult { return ((this.result != RESULT_NONE) || this.expired); } + /** + * @return {@code true} if the DeferredResult has been set. + */ + public boolean hasResult() { + return this.result != RESULT_NONE; + } + + /** + * @return the result or {@code null} if the result wasn't set; since the result can + * also be {@code null}, it is recommended to use {@link #hasResult()} first + * to check if there is a result prior to calling this method. + */ + public Object getResult() { + return hasResult() ? this.result : null; + } + /** * Return the configured timeout value in milliseconds. */ diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java b/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java index 64cc3681e6..e8d6fba1d6 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -97,17 +97,11 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle arg = resolveDefaultValue(namedValueInfo.defaultValue); } - boolean emptyArgValue = "".equals(arg); - if (binderFactory != null) { WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name); arg = binder.convertIfNecessary(arg, paramType, parameter); } - if (emptyArgValue && (arg == null)) { - handleMissingValue(namedValueInfo.name, parameter); - } - handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest); return arg; diff --git a/spring-web/src/test/java/org/springframework/http/client/AbstractHttpRequestFactoryTestCase.java b/spring-web/src/test/java/org/springframework/http/client/AbstractHttpRequestFactoryTestCase.java index 3a3d4a6885..0456dbe6e9 100644 --- a/spring-web/src/test/java/org/springframework/http/client/AbstractHttpRequestFactoryTestCase.java +++ b/spring-web/src/test/java/org/springframework/http/client/AbstractHttpRequestFactoryTestCase.java @@ -16,9 +16,6 @@ package org.springframework.http.client; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - import java.io.IOException; import java.io.InputStream; import java.net.URI; @@ -43,8 +40,10 @@ import org.junit.BeforeClass; import org.junit.Test; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; -import org.springframework.tests.web.FreePortScanner; import org.springframework.util.FileCopyUtils; +import org.springframework.util.SocketUtils; + +import static org.junit.Assert.*; public abstract class AbstractHttpRequestFactoryTestCase { @@ -56,7 +55,7 @@ public abstract class AbstractHttpRequestFactoryTestCase { @BeforeClass public static void startJettyServer() throws Exception { - int port = FreePortScanner.getFreePort(); + int port = SocketUtils.findAvailableTcpPort(); jettyServer = new Server(port); baseUrl = "http://localhost:" + port; diff --git a/spring-web/src/test/java/org/springframework/tests/web/FreePortScanner.java b/spring-web/src/test/java/org/springframework/tests/web/FreePortScanner.java deleted file mode 100644 index 9f5a3d9c33..0000000000 --- a/spring-web/src/test/java/org/springframework/tests/web/FreePortScanner.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2002-2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.tests.web; - -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.ServerSocket; -import java.util.Random; - -import org.springframework.util.Assert; - -/** - * Utility class that finds free BSD ports for use in testing scenario's. - * - * @author Ben Hale - * @author Arjen Poutsma - */ -public abstract class FreePortScanner { - - private static final int MIN_SAFE_PORT = 1024; - - private static final int MAX_PORT = 65535; - - private static final Random random = new Random(); - - /** - * Returns the number of a free port in the default range. - */ - public static int getFreePort() { - return getFreePort(MIN_SAFE_PORT, MAX_PORT); - } - - /** - * Returns the number of a free port in the given range. - */ - public static int getFreePort(int minPort, int maxPort) { - Assert.isTrue(minPort > 0, "'minPort' must be larger than 0"); - Assert.isTrue(maxPort > minPort, "'maxPort' must be larger than minPort"); - int portRange = maxPort - minPort; - int candidatePort; - int searchCounter = 0; - do { - if (++searchCounter > portRange) { - throw new IllegalStateException( - String.format("There were no ports available in the range %d to %d", minPort, maxPort)); - } - candidatePort = getRandomPort(minPort, portRange); - } - while (!isPortAvailable(candidatePort)); - - return candidatePort; - } - - private static int getRandomPort(int minPort, int portRange) { - return minPort + random.nextInt(portRange); - } - - private static boolean isPortAvailable(int port) { - ServerSocket serverSocket; - try { - serverSocket = new ServerSocket(); - } - catch (IOException ex) { - throw new IllegalStateException("Unable to create ServerSocket.", ex); - } - - try { - InetSocketAddress sa = new InetSocketAddress(port); - serverSocket.bind(sa); - return true; - } - catch (IOException ex) { - return false; - } - finally { - try { - serverSocket.close(); - } - catch (IOException ex) { - // ignore - } - } - } - -} diff --git a/spring-web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java b/spring-web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java index ea09d9a3dd..ed7438eb49 100644 --- a/spring-web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java @@ -16,14 +16,6 @@ package org.springframework.web.client; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URI; @@ -63,10 +55,12 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; -import org.springframework.tests.web.FreePortScanner; import org.springframework.util.FileCopyUtils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import org.springframework.util.SocketUtils; + +import static org.junit.Assert.*; /** @author Arjen Poutsma */ public class RestTemplateIntegrationTests { @@ -83,7 +77,7 @@ public class RestTemplateIntegrationTests { @BeforeClass public static void startJettyServer() throws Exception { - int port = FreePortScanner.getFreePort(); + int port = SocketUtils.findAvailableTcpPort(); jettyServer = new Server(port); baseUrl = "http://localhost:" + port; ServletContextHandler handler = new ServletContextHandler(); @@ -243,6 +237,7 @@ public class RestTemplateIntegrationTests { } @Test + @SuppressWarnings("unchecked") public void exchangeGet() throws Exception { HttpHeaders requestHeaders = new HttpHeaders(); requestHeaders.set("MyHeader", "MyValue"); diff --git a/spring-web/src/test/java/org/springframework/web/context/request/async/DeferredResultTests.java b/spring-web/src/test/java/org/springframework/web/context/request/async/DeferredResultTests.java index dbdf0a5a2b..6c0854b77f 100644 --- a/spring-web/src/test/java/org/springframework/web/context/request/async/DeferredResultTests.java +++ b/spring-web/src/test/java/org/springframework/web/context/request/async/DeferredResultTests.java @@ -20,7 +20,7 @@ import org.junit.Test; import org.springframework.web.context.request.async.DeferredResult.DeferredResultHandler; import static org.junit.Assert.*; -import static org.mockito.BDDMockito.*; +import static org.mockito.Mockito.*; /** * DeferredResult tests. @@ -69,6 +69,21 @@ public class DeferredResultTests { verify(handler).handleResult("hello"); } + @Test + public void hasResult() { + DeferredResultHandler handler = mock(DeferredResultHandler.class); + + DeferredResult result = new DeferredResult(); + result.setResultHandler(handler); + + assertFalse(result.hasResult()); + assertNull(result.getResult()); + + result.setResult("hello"); + + assertEquals("hello", result.getResult()); + } + @Test public void onCompletion() throws Exception { final StringBuilder sb = new StringBuilder(); 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 d678d5b84c..357146ec0d 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-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,6 +49,7 @@ import static org.junit.Assert.*; import static org.mockito.BDDMockito.*; import static org.mockito.Mockito.*; + /** * Test fixture with {@link org.springframework.web.method.annotation.RequestParamMethodArgumentResolver}. * @@ -70,6 +71,7 @@ public class RequestParamMethodArgumentResolverTests { private MethodParameter paramServlet30Part; private MethodParameter paramRequestPartAnnot; private MethodParameter paramRequired; + private MethodParameter paramNotRequired; private NativeWebRequest webRequest; @@ -81,8 +83,10 @@ public class RequestParamMethodArgumentResolverTests { ParameterNameDiscoverer paramNameDiscoverer = new LocalVariableTableParameterNameDiscoverer(); - Method method = getClass().getMethod("params", String.class, String[].class, Map.class, MultipartFile.class, - Map.class, String.class, MultipartFile.class, List.class, Part.class, MultipartFile.class, String.class); + Method method = getClass().getMethod("params", String.class, String[].class, + Map.class, MultipartFile.class, Map.class, String.class, + MultipartFile.class, List.class, Part.class, MultipartFile.class, + String.class, String.class); paramNamedDefaultValueString = new MethodParameter(method, 0); paramNamedStringArray = new MethodParameter(method, 1); @@ -99,6 +103,7 @@ public class RequestParamMethodArgumentResolverTests { paramServlet30Part.initParameterNameDiscovery(paramNameDiscoverer); paramRequestPartAnnot = new MethodParameter(method, 9); paramRequired = new MethodParameter(method, 10); + paramNotRequired = new MethodParameter(method, 11); request = new MockHttpServletRequest(); webRequest = new ServletWebRequest(request, new MockHttpServletResponse()); @@ -243,9 +248,9 @@ public class RequestParamMethodArgumentResolverTests { fail("Expected exception"); } - // SPR-10402 + // SPR-10578 - @Test(expected = MissingServletRequestParameterException.class) + @Test public void missingRequestParamEmptyValueConvertedToNull() throws Exception { WebDataBinder binder = new WebRequestDataBinder(null); @@ -256,8 +261,25 @@ public class RequestParamMethodArgumentResolverTests { this.request.addParameter("stringNotAnnot", ""); - resolver.resolveArgument(paramStringNotAnnot, null, webRequest, binderFactory); - fail("Expected exception"); + Object arg = resolver.resolveArgument(paramStringNotAnnot, null, webRequest, binderFactory); + + assertNull(arg); + } + + @Test + public void missingRequestParamEmptyValueNotRequired() throws Exception { + + WebDataBinder binder = new WebRequestDataBinder(null); + binder.registerCustomEditor(String.class, new StringTrimmerEditor(true)); + + WebDataBinderFactory binderFactory = mock(WebDataBinderFactory.class); + given(binderFactory.createBinder(webRequest, null, "name")).willReturn(binder); + + this.request.addParameter("name", ""); + + Object arg = resolver.resolveArgument(paramNotRequired, null, webRequest, binderFactory); + + assertNull(arg); } @Test @@ -311,7 +333,8 @@ public class RequestParamMethodArgumentResolverTests { List multipartFileList, Part servlet30Part, @RequestPart MultipartFile requestPartAnnot, - @RequestParam(value = "name") String paramRequired) { + @RequestParam(value = "name") String paramRequired, + @RequestParam(value = "name", required=false) String paramNotRequired) { } } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java index 6cd0fcb1bf..96d905b9af 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java @@ -63,11 +63,11 @@ import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.context.request.WebRequest; -import org.springframework.web.context.request.async.WebAsyncTask; import org.springframework.web.context.request.async.AsyncWebRequest; import org.springframework.web.context.request.async.CallableProcessingInterceptor; import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor; import org.springframework.web.context.request.async.WebAsyncManager; +import org.springframework.web.context.request.async.WebAsyncTask; import org.springframework.web.context.request.async.WebAsyncUtils; import org.springframework.web.method.ControllerAdviceBean; import org.springframework.web.method.HandlerMethod; @@ -218,8 +218,8 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i * Return the configured argument resolvers, or possibly {@code null} if * not initialized yet via {@link #afterPropertiesSet()}. */ - public HandlerMethodArgumentResolverComposite getArgumentResolvers() { - return this.argumentResolvers; + public List getArgumentResolvers() { + return this.argumentResolvers.getResolvers(); } /** @@ -239,8 +239,8 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i * Return the argument resolvers for {@code @InitBinder} methods, or possibly * {@code null} if not initialized yet via {@link #afterPropertiesSet()}. */ - public HandlerMethodArgumentResolverComposite getInitBinderArgumentResolvers() { - return this.initBinderArgumentResolvers; + public List getInitBinderArgumentResolvers() { + return this.initBinderArgumentResolvers.getResolvers(); } /** @@ -277,8 +277,8 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i * Return the configured handlers, or possibly {@code null} if not * initialized yet via {@link #afterPropertiesSet()}. */ - public HandlerMethodReturnValueHandlerComposite getReturnValueHandlers() { - return this.returnValueHandlers; + public List getReturnValueHandlers() { + return this.returnValueHandlers.getHandlers(); } /** diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestDataValueProcessor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestDataValueProcessor.java index 6dd24b7826..2ce8a20739 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestDataValueProcessor.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestDataValueProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,9 +42,10 @@ public interface RequestDataValueProcessor { * Invoked when a new form action is rendered. * @param request the current request * @param action the form action + * @param httpMethod the form HTTP method * @return the action to use, possibly modified */ - String processAction(HttpServletRequest request, String action); + String processAction(HttpServletRequest request, String action, String httpMethod); /** * Invoked when a form field value is rendered. diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/FormTag.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/FormTag.java index 7548ded940..a58b4c7acb 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/FormTag.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/FormTag.java @@ -346,8 +346,7 @@ public class FormTag extends AbstractHtmlElementTag { tagWriter.startTag(FORM_TAG); writeDefaultAttributes(tagWriter); tagWriter.writeAttribute(ACTION_ATTRIBUTE, resolveAction()); - writeOptionalAttribute(tagWriter, METHOD_ATTRIBUTE, - isMethodBrowserSupported(getMethod()) ? getMethod() : DEFAULT_METHOD); + writeOptionalAttribute(tagWriter, METHOD_ATTRIBUTE, getHttpMethod()); writeOptionalAttribute(tagWriter, TARGET_ATTRIBUTE, getTarget()); writeOptionalAttribute(tagWriter, ENCTYPE_ATTRIBUTE, getEnctype()); writeOptionalAttribute(tagWriter, ACCEPT_CHARSET_ATTRIBUTE, getAcceptCharset()); @@ -382,6 +381,10 @@ public class FormTag extends AbstractHtmlElementTag { return EVAL_BODY_INCLUDE; } + private String getHttpMethod() { + return isMethodBrowserSupported(getMethod()) ? getMethod() : DEFAULT_METHOD; + } + private void assertHttpMethod(String method) { for (HttpMethod httpMethod : HttpMethod.values()) { if (httpMethod.name().equalsIgnoreCase(method)) { @@ -465,7 +468,7 @@ public class FormTag extends AbstractHtmlElementTag { RequestDataValueProcessor processor = getRequestContext().getRequestDataValueProcessor(); ServletRequest request = this.pageContext.getRequest(); if ((processor != null) && (request instanceof HttpServletRequest)) { - action = processor.processAction((HttpServletRequest) request, action); + action = processor.processAction((HttpServletRequest) request, action, getHttpMethod()); } return action; } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterTests.java index 629b7ea631..d08c795602 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterTests.java @@ -16,9 +16,6 @@ package org.springframework.web.servlet.mvc.method.annotation; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - import java.lang.reflect.Method; import java.util.Arrays; @@ -41,6 +38,8 @@ import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.FlashMap; import org.springframework.web.servlet.ModelAndView; +import static org.junit.Assert.*; + /** * Unit tests for {@link RequestMappingHandlerAdapter}. * @@ -72,9 +71,9 @@ public class RequestMappingHandlerAdapterTests { adapter.setApplicationContext(new StaticWebApplicationContext()); adapter.afterPropertiesSet(); - RESOLVER_COUNT = adapter.getArgumentResolvers().getResolvers().size(); - INIT_BINDER_RESOLVER_COUNT = adapter.getInitBinderArgumentResolvers().getResolvers().size(); - HANDLER_COUNT = adapter.getReturnValueHandlers().getHandlers().size(); + RESOLVER_COUNT = adapter.getArgumentResolvers().size(); + INIT_BINDER_RESOLVER_COUNT = adapter.getInitBinderArgumentResolvers().size(); + HANDLER_COUNT = adapter.getReturnValueHandlers().size(); } @Before @@ -131,7 +130,7 @@ public class RequestMappingHandlerAdapterTests { this.handlerAdapter.setCustomArgumentResolvers(Arrays.asList(resolver)); this.handlerAdapter.afterPropertiesSet(); - assertTrue(this.handlerAdapter.getArgumentResolvers().getResolvers().contains(resolver)); + assertTrue(this.handlerAdapter.getArgumentResolvers().contains(resolver)); assertMethodProcessorCount(RESOLVER_COUNT + 1, INIT_BINDER_RESOLVER_COUNT + 1, HANDLER_COUNT); } @@ -159,7 +158,7 @@ public class RequestMappingHandlerAdapterTests { this.handlerAdapter.setCustomReturnValueHandlers(Arrays.asList(handler)); this.handlerAdapter.afterPropertiesSet(); - assertTrue(this.handlerAdapter.getReturnValueHandlers().getHandlers().contains(handler)); + assertTrue(this.handlerAdapter.getReturnValueHandlers().contains(handler)); assertMethodProcessorCount(RESOLVER_COUNT, INIT_BINDER_RESOLVER_COUNT, HANDLER_COUNT + 1); } @@ -192,9 +191,9 @@ public class RequestMappingHandlerAdapterTests { } private void assertMethodProcessorCount(int resolverCount, int initBinderResolverCount, int handlerCount) { - assertEquals(resolverCount, this.handlerAdapter.getArgumentResolvers().getResolvers().size()); - assertEquals(initBinderResolverCount, this.handlerAdapter.getInitBinderArgumentResolvers().getResolvers().size()); - assertEquals(handlerCount, this.handlerAdapter.getReturnValueHandlers().getHandlers().size()); + assertEquals(resolverCount, this.handlerAdapter.getArgumentResolvers().size()); + assertEquals(initBinderResolverCount, this.handlerAdapter.getInitBinderArgumentResolvers().size()); + assertEquals(handlerCount, this.handlerAdapter.getReturnValueHandlers().size()); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartIntegrationTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartIntegrationTests.java index a15466aabc..7ff9859aeb 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartIntegrationTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartIntegrationTests.java @@ -16,8 +16,6 @@ package org.springframework.web.servlet.mvc.method.annotation; -import static org.junit.Assert.assertEquals; - import java.net.URI; import java.util.Arrays; @@ -42,9 +40,9 @@ import org.springframework.http.converter.ResourceHttpMessageConverter; import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter; import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter; import org.springframework.stereotype.Controller; -import org.springframework.tests.web.FreePortScanner; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import org.springframework.util.SocketUtils; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestPart; @@ -58,6 +56,8 @@ import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; +import static org.junit.Assert.*; + /** * Test access to parts of a multipart request with {@link RequestPart}. * @@ -74,8 +74,7 @@ public class RequestPartIntegrationTests { @BeforeClass public static void startServer() throws Exception { - - int port = FreePortScanner.getFreePort(); + int port = SocketUtils.findAvailableTcpPort(); baseUrl = "http://localhost:" + port; server = new Server(port); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/support/RequestDataValueProcessorWrapper.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/RequestDataValueProcessorWrapper.java index 4d5b846832..f760ed262a 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/support/RequestDataValueProcessorWrapper.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/RequestDataValueProcessorWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,8 +20,6 @@ import java.util.Map; import javax.servlet.http.HttpServletRequest; -import org.springframework.web.servlet.support.RequestDataValueProcessor; - public class RequestDataValueProcessorWrapper implements RequestDataValueProcessor { private RequestDataValueProcessor processor; @@ -41,8 +39,8 @@ public class RequestDataValueProcessorWrapper implements RequestDataValueProcess } @Override - public String processAction(HttpServletRequest request, String action) { - return (this.processor != null) ? this.processor.processAction(request, action) : action; + public String processAction(HttpServletRequest request, String action, String httpMethod) { + return (this.processor != null) ? this.processor.processAction(request, action, httpMethod) : action; } @Override diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/FormTagTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/FormTagTests.java index a0ff3611d9..43f86fac47 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/FormTagTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/FormTagTests.java @@ -326,7 +326,7 @@ public class FormTagTests extends AbstractHtmlElementTagTests { public void testRequestDataValueProcessorHooks() throws Exception { String action = "/my/form?foo=bar"; RequestDataValueProcessor processor = getMockRequestDataValueProcessor(); - given(processor.processAction(this.request, action)).willReturn(action); + given(processor.processAction(this.request, action, "post")).willReturn(action); given(processor.getExtraHiddenFields(this.request)).willReturn(Collections.singletonMap("key", "value")); this.tag.doStartTag(); diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/BinaryMessage.java b/spring-websocket/src/main/java/org/springframework/web/socket/BinaryMessage.java index b9eba5a1cb..8bcb3a2a3d 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/BinaryMessage.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/BinaryMessage.java @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.web.socket; import java.nio.ByteBuffer; - /** * A {@link WebSocketMessage} that contains a binary {@link ByteBuffer} payload. * @@ -79,6 +79,7 @@ public final class BinaryMessage extends WebSocketMessage { } } + /** * Returns access to the message payload as a byte array. NOTE: the returned array * should be considered read-only and should not be modified. diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/CloseStatus.java b/spring-websocket/src/main/java/org/springframework/web/socket/CloseStatus.java index 9e63bdf8e6..4f54b8dc43 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/CloseStatus.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/CloseStatus.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.web.socket; import org.eclipse.jetty.websocket.api.StatusCode; @@ -148,8 +149,8 @@ public final class CloseStatus { /** * Create a new {@link CloseStatus} instance. - * @param code - * @param reason + * @param code the status code + * @param reason the reason */ public CloseStatus(int code, String reason) { Assert.isTrue((code >= 1000 && code < 5000), "Invalid code"); @@ -157,6 +158,7 @@ public final class CloseStatus { this.reason = reason; } + /** * Returns the status code. */ diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/TextMessage.java b/spring-websocket/src/main/java/org/springframework/web/socket/TextMessage.java index 88d8d43e42..2ef928d1cb 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/TextMessage.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/TextMessage.java @@ -16,7 +16,6 @@ package org.springframework.web.socket; - /** * A {@link WebSocketMessage} that contains a textual {@link String} payload. * @@ -25,11 +24,9 @@ package org.springframework.web.socket; */ public final class TextMessage extends WebSocketMessage { - /** * Create a new {@link TextMessage} instance. * @param payload the payload - * @param isLast whether this the last part of a message received or transmitted in parts */ public TextMessage(CharSequence payload) { super(payload.toString(), true); @@ -44,6 +41,7 @@ public final class TextMessage extends WebSocketMessage { super(payload.toString(), isLast); } + @Override protected int getPayloadSize() { return getPayload().length(); diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketHandler.java index 600785bf6b..309bb41abc 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketHandler.java @@ -19,18 +19,15 @@ package org.springframework.web.socket; /** * A handler for WebSocket messages and lifecycle events. * - *

          - * Implementations of this interface are encouraged to handle exceptions locally where it - * makes sense or alternatively let the exception bubble up in which case the exception is - * logged and the session closed with {@link CloseStatus#SERVER_ERROR SERVER_ERROR(1011)} - * by default. The exception handling strategy is provided by + *

          Implementations of this interface are encouraged to handle exceptions locally where + * it makes sense or alternatively let the exception bubble up in which case the exception + * is logged and the session closed with + * {@link CloseStatus#SERVER_ERROR SERVER_ERROR(1011)} by default. The exception handling + * strategy is provided by * {@link org.springframework.web.socket.support.ExceptionWebSocketHandlerDecorator * ExceptionWebSocketHandlerDecorator}, which can be customized or replaced by decorating * the {@link WebSocketHandler} with a different decorator. * - * @param The type of message being handled {@link TextMessage}, {@link BinaryMessage} - * (or {@link WebSocketMessage} for both). - * * @author Rossen Stoyanchev * @author Phillip Webb * @since 4.0 @@ -40,7 +37,6 @@ public interface WebSocketHandler { /** * Invoked after WebSocket negotiation has succeeded and the WebSocket connection is * opened and ready for use. - * * @throws Exception this method can handle or propagate exceptions; see class-level * Javadoc for details. */ @@ -48,7 +44,6 @@ public interface WebSocketHandler { /** * Invoked when a new WebSocket message arrives. - * * @throws Exception this method can handle or propagate exceptions; see class-level * Javadoc for details. */ @@ -56,7 +51,6 @@ public interface WebSocketHandler { /** * Handle an error from the underlying WebSocket message transport. - * * @throws Exception this method can handle or propagate exceptions; see class-level * Javadoc for details. */ @@ -67,7 +61,6 @@ public interface WebSocketHandler { * transport error has occurred. Although the session may technically still be open, * depending on the underlying implementation, sending messages at this point is * discouraged and most likely will not succeed. - * * @throws Exception this method can handle or propagate exceptions; see class-level * Javadoc for details. */ diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketMessage.java b/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketMessage.java index bc7f00a46b..0b934ad7cb 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketMessage.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketMessage.java @@ -44,6 +44,7 @@ public abstract class WebSocketMessage { this.last = isLast; } + /** * Returns the message payload. This will never be {@code null}. */ diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/adapter/AbstractWebSocketSesssionAdapter.java b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/AbstractWebSocketSesssionAdapter.java index fb261af2c3..7aff7a2347 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/adapter/AbstractWebSocketSesssionAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/AbstractWebSocketSesssionAdapter.java @@ -26,7 +26,6 @@ import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketMessage; import org.springframework.web.socket.WebSocketSession; - /** * An abstract base class for implementations of {@link WebSocketSession}. * diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/adapter/BinaryWebSocketHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/BinaryWebSocketHandlerAdapter.java index 003740430f..efec63ce1d 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/adapter/BinaryWebSocketHandlerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/BinaryWebSocketHandlerAdapter.java @@ -23,7 +23,6 @@ import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketSession; - /** * A convenient base class for {@link WebSocketHandler} implementation that process binary * messages only. Text messages are rejected with {@link CloseStatus#NOT_ACCEPTABLE}. All @@ -35,7 +34,6 @@ import org.springframework.web.socket.WebSocketSession; */ public class BinaryWebSocketHandlerAdapter extends WebSocketHandlerAdapter { - @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) { try { diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/adapter/ConfigurableWebSocketSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/ConfigurableWebSocketSession.java index 2a3b305edd..17e8a53da9 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/adapter/ConfigurableWebSocketSession.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/ConfigurableWebSocketSession.java @@ -21,7 +21,6 @@ import java.security.Principal; import org.springframework.web.socket.WebSocketSession; - /** * A WebSocketSession with configurable properties. * diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/adapter/JettyWebSocketListenerAdapter.java b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/JettyWebSocketListenerAdapter.java index 6022f778b4..df971ac443 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/adapter/JettyWebSocketListenerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/JettyWebSocketListenerAdapter.java @@ -39,12 +39,12 @@ public class JettyWebSocketListenerAdapter implements WebSocketListener { private final WebSocketHandler webSocketHandler; - private JettyWebSocketSessionAdapter wsSession; + private final JettyWebSocketSessionAdapter wsSession; public JettyWebSocketListenerAdapter(WebSocketHandler webSocketHandler, JettyWebSocketSessionAdapter wsSession) { - Assert.notNull(webSocketHandler, "webSocketHandler is required"); - Assert.notNull(wsSession, "wsSession is required"); + Assert.notNull(webSocketHandler, "webSocketHandler must not be null"); + Assert.notNull(wsSession, "wsSession must not be null"); this.webSocketHandler = webSocketHandler; this.wsSession = wsSession; } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/adapter/JettyWebSocketSessionAdapter.java b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/JettyWebSocketSessionAdapter.java index c2a6d1213d..c34ff2ba1a 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/adapter/JettyWebSocketSessionAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/JettyWebSocketSessionAdapter.java @@ -29,7 +29,6 @@ import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; - /** * Adapts a Jetty {@link org.eclipse.jetty.websocket.api.Session} to * {@link WebSocketSession}. @@ -48,7 +47,7 @@ public class JettyWebSocketSessionAdapter @Override public void initSession(Session session) { - Assert.notNull(session, "session is required"); + Assert.notNull(session, "session must not be null"); this.session = session; } @@ -122,4 +121,4 @@ public class JettyWebSocketSessionAdapter this.session.close(status.getCode(), status.getReason()); } -} \ No newline at end of file +} diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/adapter/StandardEndpointAdapter.java b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/StandardEndpointAdapter.java index 7bf3c3247b..4bfd55d265 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/adapter/StandardEndpointAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/StandardEndpointAdapter.java @@ -32,7 +32,6 @@ import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.support.ExceptionWebSocketHandlerDecorator; - /** * Adapts a {@link WebSocketHandler} to a standard {@link Endpoint}. * @@ -49,8 +48,8 @@ public class StandardEndpointAdapter extends Endpoint { public StandardEndpointAdapter(WebSocketHandler handler, StandardWebSocketSessionAdapter wsSession) { - Assert.notNull(handler, "handler is required"); - Assert.notNull(wsSession, "wsSession is required"); + Assert.notNull(handler, "handler must not be null"); + Assert.notNull(wsSession, "wsSession must not be null"); this.handler = handler; this.wsSession = wsSession; } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/adapter/StandardWebSocketSessionAdapter.java b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/StandardWebSocketSessionAdapter.java index cff1931a52..48c4d3c8f1 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/adapter/StandardWebSocketSessionAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/StandardWebSocketSessionAdapter.java @@ -48,7 +48,7 @@ public class StandardWebSocketSessionAdapter extends AbstractWebSocketSesssionAd @Override public void initSession(javax.websocket.Session session) { - Assert.notNull(session, "session is required"); + Assert.notNull(session, "session must not be null"); this.session = session; } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/adapter/TextWebSocketHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/TextWebSocketHandlerAdapter.java index 76f26182c3..d2e05109bc 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/adapter/TextWebSocketHandlerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/TextWebSocketHandlerAdapter.java @@ -23,7 +23,6 @@ import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketSession; - /** * A convenient base class for {@link WebSocketHandler} implementation that process text * messages only. Binary messages are rejected with {@link CloseStatus#NOT_ACCEPTABLE}. All @@ -35,7 +34,6 @@ import org.springframework.web.socket.WebSocketSession; */ public class TextWebSocketHandlerAdapter extends WebSocketHandlerAdapter { - @Override protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) { try { diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/adapter/WebSocketHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/WebSocketHandlerAdapter.java index fefd330cf7..dfbf32c7fc 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/adapter/WebSocketHandlerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/WebSocketHandlerAdapter.java @@ -23,7 +23,6 @@ import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketMessage; import org.springframework.web.socket.WebSocketSession; - /** * A convenient base class for {@link WebSocketHandler} implementation with empty methods. * diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/client/ConnectionManagerSupport.java b/spring-websocket/src/main/java/org/springframework/web/socket/client/ConnectionManagerSupport.java index cabbaff559..7137c46409 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/client/ConnectionManagerSupport.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/client/ConnectionManagerSupport.java @@ -48,18 +48,21 @@ public abstract class ConnectionManagerSupport implements SmartLifecycle { private int phase = Integer.MAX_VALUE; - private TaskExecutor taskExecutor = new SimpleAsyncTaskExecutor("EndpointConnectionManager-"); + private final TaskExecutor taskExecutor = new SimpleAsyncTaskExecutor("EndpointConnectionManager-"); private final Object lifecycleMonitor = new Object(); public ConnectionManagerSupport(String uriTemplate, Object... uriVariables) { - this.uri = UriComponentsBuilder.fromUriString(uriTemplate).buildAndExpand(uriVariables).encode().toUri(); + this.uri = UriComponentsBuilder.fromUriString(uriTemplate).buildAndExpand( + uriVariables).encode().toUri(); } + /** * Set whether to auto-connect to the remote endpoint after this connection manager * has been initialized and the Spring context has been refreshed. + * *

          Default is "false". */ public void setAutoStartup(boolean autoStartup) { @@ -111,8 +114,7 @@ public abstract class ConnectionManagerSupport implements SmartLifecycle { } /** - * Connect to the configured {@link #setDefaultUri(URI) default URI}. If already - * connected, the method has no impact. + * Start the websocket connection. If already connected, the method has no impact. */ @Override public final void start() { diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/client/WebSocketClient.java b/spring-websocket/src/main/java/org/springframework/web/socket/client/WebSocketClient.java index 52fc158dd2..e2eb714782 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/client/WebSocketClient.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/client/WebSocketClient.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.web.socket.client; import java.net.URI; @@ -25,15 +26,14 @@ import org.springframework.web.socket.WebSocketSession; * Contract for initiating a WebSocket request. As an alternative considering using the * declarative style {@link WebSocketConnectionManager} that starts a WebSocket connection * to a pre-configured URI when the application starts. - * + * * @author Rossen Stoyanchev * @since 4.0 - * + * * @see WebSocketConnectionManager */ public interface WebSocketClient { - WebSocketSession doHandshake(WebSocketHandler webSocketHandler, String uriTemplate, Object... uriVariables) throws WebSocketConnectFailureException; diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/client/WebSocketConnectFailureException.java b/spring-websocket/src/main/java/org/springframework/web/socket/client/WebSocketConnectFailureException.java index 2172685cf0..87a4cc8c65 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/client/WebSocketConnectFailureException.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/client/WebSocketConnectFailureException.java @@ -27,7 +27,6 @@ import org.springframework.core.NestedRuntimeException; @SuppressWarnings("serial") public class WebSocketConnectFailureException extends NestedRuntimeException { - public WebSocketConnectFailureException(String msg, Throwable cause) { super(msg, cause); } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/client/WebSocketConnectionManager.java b/spring-websocket/src/main/java/org/springframework/web/socket/client/WebSocketConnectionManager.java index 599e94f91c..ec2e7e7e8e 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/client/WebSocketConnectionManager.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/client/WebSocketConnectionManager.java @@ -57,10 +57,11 @@ public class WebSocketConnectionManager extends ConnectionManagerSupport { this.syncClientLifecycle = ((client instanceof SmartLifecycle) && !((SmartLifecycle) client).isRunning()); } + /** * Decorate the WebSocketHandler provided to the class constructor. - *

          - * By default {@link LoggingWebSocketHandlerDecorator} is added. + * + *

          By default {@link LoggingWebSocketHandlerDecorator} is added. */ protected WebSocketHandler decorateWebSocketHandler(WebSocketHandler handler) { return new LoggingWebSocketHandlerDecorator(handler); @@ -88,7 +89,7 @@ public class WebSocketConnectionManager extends ConnectionManagerSupport { @Override public void stopInternal() throws Exception { if (this.syncClientLifecycle) { - ((SmartLifecycle) client).stop(); + ((SmartLifecycle) this.client).stop(); } super.stopInternal(); } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/client/endpoint/EndpointConnectionManager.java b/spring-websocket/src/main/java/org/springframework/web/socket/client/endpoint/EndpointConnectionManager.java index fa049bb8c7..25084c8c54 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/client/endpoint/EndpointConnectionManager.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/client/endpoint/EndpointConnectionManager.java @@ -61,14 +61,14 @@ public class EndpointConnectionManager extends ConnectionManagerSupport implemen public EndpointConnectionManager(Endpoint endpoint, String uriTemplate, Object... uriVariables) { super(uriTemplate, uriVariables); - Assert.notNull(endpoint, "endpoint is required"); + Assert.notNull(endpoint, "endpoint must not be null"); this.endpointProvider = null; this.endpoint = endpoint; } public EndpointConnectionManager(Class endpointClass, String uriTemplate, Object... uriVars) { super(uriTemplate, uriVars); - Assert.notNull(endpointClass, "endpointClass is required"); + Assert.notNull(endpointClass, "endpointClass must not be null"); this.endpointProvider = new BeanCreatingHandlerProvider(endpointClass); this.endpoint = null; } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/client/endpoint/StandardWebSocketClient.java b/spring-websocket/src/main/java/org/springframework/web/socket/client/endpoint/StandardWebSocketClient.java index 8dabcface6..c6b57fc295 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/client/endpoint/StandardWebSocketClient.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/client/endpoint/StandardWebSocketClient.java @@ -45,7 +45,7 @@ import org.springframework.web.util.UriComponentsBuilder; /** * Initiates WebSocket requests to a WebSocket server programatically through the standard - * Java WebSocket API . + * Java WebSocket API. * * @author Rossen Stoyanchev * @since 4.0 @@ -62,15 +62,16 @@ public class StandardWebSocketClient implements WebSocketClient { } public StandardWebSocketClient(WebSocketContainer webSocketContainer) { - Assert.notNull(webSocketContainer, "webSocketContainer is required"); + Assert.notNull(webSocketContainer, "webSocketContainer must not be null"); this.webSocketContainer = webSocketContainer; } + @Override public WebSocketSession doHandshake(WebSocketHandler webSocketHandler, String uriTemplate, Object... uriVariables) throws WebSocketConnectFailureException { - Assert.notNull(uriTemplate, "uriTemplate is required"); + Assert.notNull(uriTemplate, "uriTemplate must not be null"); UriComponents uriComponents = UriComponentsBuilder.fromUriString(uriTemplate).buildAndExpand(uriVariables).encode(); return doHandshake(webSocketHandler, null, uriComponents.toUri()); } @@ -79,8 +80,8 @@ public class StandardWebSocketClient implements WebSocketClient { public WebSocketSession doHandshake(WebSocketHandler webSocketHandler, HttpHeaders httpHeaders, URI uri) throws WebSocketConnectFailureException { - Assert.notNull(webSocketHandler, "webSocketHandler is required"); - Assert.notNull(uri, "uri is required"); + Assert.notNull(webSocketHandler, "webSocketHandler must not be null"); + Assert.notNull(uri, "uri must not be null"); httpHeaders = (httpHeaders != null) ? httpHeaders : new HttpHeaders(); diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/client/jetty/JettyWebSocketClient.java b/spring-websocket/src/main/java/org/springframework/web/socket/client/jetty/JettyWebSocketClient.java index 0203203e8b..223ebabc92 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/client/jetty/JettyWebSocketClient.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/client/jetty/JettyWebSocketClient.java @@ -32,8 +32,10 @@ import org.springframework.web.socket.client.WebSocketConnectFailureException; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; - /** + * Initiates WebSocket requests to a WebSocket server programatically through the Jetty + * WebSocket API. + * * @author Rossen Stoyanchev * @since 4.0 */ @@ -134,8 +136,8 @@ public class JettyWebSocketClient implements WebSocketClient, SmartLifecycle { public WebSocketSession doHandshake(WebSocketHandler webSocketHandler, HttpHeaders headers, URI uri) throws WebSocketConnectFailureException { - Assert.notNull(webSocketHandler, "webSocketHandler is required"); - Assert.notNull(uri, "uri is required"); + Assert.notNull(webSocketHandler, "webSocketHandler must not be null"); + Assert.notNull(uri, "uri must not be null"); if (logger.isDebugEnabled()) { logger.debug("Connecting to " + uri); diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/DefaultHandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/DefaultHandshakeHandler.java index 60c8758c13..65e4e07f31 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/DefaultHandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/DefaultHandshakeHandler.java @@ -41,8 +41,8 @@ import org.springframework.web.socket.WebSocketHandler; /** * A default implemnetation of {@link HandshakeHandler}. - *

          - * A container-specific {@link RequestUpgradeStrategy} is required since standard Java + * + *

          A container-specific {@link RequestUpgradeStrategy} is required since standard Java * WebSocket currently does not provide a way to initiate a WebSocket handshake. * Currently available are implementations for Tomcat and GlassFish. * @@ -57,7 +57,7 @@ public class DefaultHandshakeHandler implements HandshakeHandler { private List supportedProtocols = new ArrayList(); - private RequestUpgradeStrategy requestUpgradeStrategy; + private final RequestUpgradeStrategy requestUpgradeStrategy; /** diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/HandshakeFailureException.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/HandshakeFailureException.java index aae60c7d86..121e3b454f 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/HandshakeFailureException.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/HandshakeFailureException.java @@ -18,14 +18,13 @@ package org.springframework.web.socket.server; import org.springframework.core.NestedRuntimeException; - /** * Thrown when handshake processing failed to complete due to an internal, unrecoverable * error. This implies a server error (HTTP status code 500) as opposed to a failure in * the handshake negotiation. - *

          - * By contrast, when handshake negotiation fails, the response status code will be 200 and - * the response headers and body will have been updated to reflect the cause for the + * + *

          By contrast, when handshake negotiation fails, the response status code will be 200 + * and the response headers and body will have been updated to reflect the cause for the * failure. A {@link HandshakeHandler} implementation will have protected methods to * customize updates to the response in those cases. * @@ -35,7 +34,6 @@ import org.springframework.core.NestedRuntimeException; @SuppressWarnings("serial") public class HandshakeFailureException extends NestedRuntimeException { - public HandshakeFailureException(String msg, Throwable cause) { super(msg, cause); } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/HandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/HandshakeHandler.java index 4286a6f77e..75654847ce 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/HandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/HandshakeHandler.java @@ -33,7 +33,6 @@ import org.springframework.web.socket.support.PerConnectionWebSocketHandler; */ public interface HandshakeHandler { - /** * Initiate the handshake. * diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/ServerEndpointExporter.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/ServerEndpointExporter.java index f9df84aa99..1b03a81f61 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/ServerEndpointExporter.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/ServerEndpointExporter.java @@ -43,10 +43,10 @@ import org.springframework.util.ReflectionUtils; * {@link ServerEndpoint} and registers them as well. Although not required, it is likely * annotated endpoints should have their {@code configurator} property set to * {@link SpringConfigurator}. - *

          - * When this class is used, by declaring it in Spring configuration, it should be possible - * to turn off a Servlet container's scan for WebSocket endpoints. This can be done with - * the help of the {@code } element in web.xml. + * + *

          When this class is used, by declaring it in Spring configuration, it should be + * possible to turn off a Servlet container's scan for WebSocket endpoints. This can be + * done with the help of the {@code } element in web.xml. * * @author Rossen Stoyanchev * @since 4.0 @@ -123,7 +123,7 @@ public class ServerEndpointExporter implements InitializingBean, BeanPostProcess @Override public void afterPropertiesSet() throws Exception { - Assert.notNull(serverContainer, "javax.websocket.server.ServerContainer not available"); + Assert.state(this.serverContainer != null, "javax.websocket.server.ServerContainer not available"); List> allClasses = new ArrayList>(this.annotatedEndpointClasses); allClasses.addAll(this.annotatedEndpointBeanTypes); diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/ServerEndpointRegistration.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/ServerEndpointRegistration.java index cbf1fd9bea..1f4075ffb2 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/ServerEndpointRegistration.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/ServerEndpointRegistration.java @@ -35,14 +35,13 @@ import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.util.Assert; import org.springframework.web.socket.support.BeanCreatingHandlerProvider; - /** * An implementation of {@link javax.websocket.server.ServerEndpointConfig} that also * contains the target {@link javax.websocket.Endpoint}, provided either as a reference or * as a bean name. - *

          - * {@link ServerEndpointRegistration} beans are detected by {@link ServerEndpointExporter} - * and registered with a Java WebSocket runtime at startup. + * + *

          {@link ServerEndpointRegistration} beans are detected by + * {@link ServerEndpointExporter} and registered with a Java WebSocket runtime at startup. * * @author Rossen Stoyanchev * @since 4.0 @@ -67,26 +66,32 @@ public class ServerEndpointRegistration implements ServerEndpointConfig, BeanFac private final Map userProperties = new HashMap(); - private Configurator configurator = new EndpointRegistrationConfigurator(); + private final Configurator configurator = new EndpointRegistrationConfigurator(); /** - * Class constructor with the {@code javax.webscoket.Endpoint} class. - * - * @param path - * @param endpointClass + * Create a new {@link ServerEndpointRegistration} instance from an + * {@code javax.webscoket.Endpoint} class. + * @param path the endpoint path + * @param endpointClass the endpoint class */ public ServerEndpointRegistration(String path, Class endpointClass) { Assert.hasText(path, "path must not be empty"); - Assert.notNull(endpointClass, "endpointClass is required"); + Assert.notNull(endpointClass, "endpointClass must not be null"); this.path = path; this.endpointProvider = new BeanCreatingHandlerProvider(endpointClass); this.endpoint = null; } + /** + * Create a new {@link ServerEndpointRegistration} instance from an + * {@code javax.webscoket.Endpoint} instance. + * @param path the endpoint path + * @param endpoint the endpoint instance + */ public ServerEndpointRegistration(String path, Endpoint endpoint) { Assert.hasText(path, "path must not be empty"); - Assert.notNull(endpoint, "endpoint is required"); + Assert.notNull(endpoint, "endpoint must not be null"); this.path = path; this.endpointProvider = null; this.endpoint = endpoint; @@ -188,7 +193,7 @@ public class ServerEndpointRegistration implements ServerEndpointConfig, BeanFac @SuppressWarnings("unchecked") @Override public T getEndpointInstance(Class clazz) throws InstantiationException { - return (T) ServerEndpointRegistration.this.getEndpoint(); + return (T) getEndpoint(); } @Override diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/ServletServerContainerFactoryBean.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/ServletServerContainerFactoryBean.java index dc86275e2e..5267594cfb 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/ServletServerContainerFactoryBean.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/ServletServerContainerFactoryBean.java @@ -33,9 +33,9 @@ import org.springframework.web.socket.sockjs.SockJsService; * {@code javax.servlet.ServletContext} attribute, simply declaring this FactoryBean and * using its setters allows configuring the {@code ServerContainer} through Spring * configuration. - *

          - * This is useful even if the {@code ServerContainer} is not injected into any other bean. - * For example, an application can configure a {@link DefaultHandshakeHandler}, a + * + *

          This is useful even if the {@code ServerContainer} is not injected into any other + * bean. For example, an application can configure a {@link DefaultHandshakeHandler}, a * {@link SockJsService}, or {@link ServerEndpointExporter}, and separately declare this * FactoryBean in order to customize the properties of the (one and only) * {@code ServerContainer} instance. @@ -112,8 +112,8 @@ public class ServletServerContainerFactoryBean @Override public void afterPropertiesSet() throws Exception { - Assert.notNull(this.serverContainer, - "A ServletContext is required to access the javax.websocket.server.ServerContainer instance"); + Assert.state(this.serverContainer != null, "A ServletContext is required to " + + "access the javax.websocket.server.ServerContainer instance"); if (this.asyncSendTimeout != null) { this.serverContainer.setAsyncSendTimeout(this.asyncSendTimeout); diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/SpringConfigurator.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/SpringConfigurator.java index 281ec5723d..e916c9ce50 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/SpringConfigurator.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/SpringConfigurator.java @@ -29,6 +29,7 @@ import org.springframework.web.context.WebApplicationContext; /** * A {@link javax.websocket.server.ServerEndpointConfig.Configurator} for initializing * {@link ServerEndpoint}-annotated classes through Spring. + * *

          *

            * @ServerEndpoint(value = "/echo", configurator = SpringConfigurator.class)
          diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/support/ServerWebSocketSessionInitializer.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/support/ServerWebSocketSessionInitializer.java
          index f2e9bd3c17..7be6c1309a 100644
          --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/support/ServerWebSocketSessionInitializer.java
          +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/support/ServerWebSocketSessionInitializer.java
          @@ -21,7 +21,6 @@ import org.springframework.http.server.ServerHttpResponse;
           import org.springframework.web.socket.WebSocketSession;
           import org.springframework.web.socket.adapter.ConfigurableWebSocketSession;
           
          -
           /**
            * Copies information from the handshake HTTP request and response to a given
            * {@link WebSocketSession}.
          @@ -31,7 +30,6 @@ import org.springframework.web.socket.adapter.ConfigurableWebSocketSession;
            */
           public class ServerWebSocketSessionInitializer {
           
          -
           	public void initialize(ServerHttpRequest request, ServerHttpResponse response, ConfigurableWebSocketSession session) {
           		session.setUri(request.getURI());
           		session.setRemoteHostName(request.getRemoteHostName());
          diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/support/WebSocketHttpRequestHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/support/WebSocketHttpRequestHandler.java
          index 8d1086f82d..83e3c97d22 100644
          --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/support/WebSocketHttpRequestHandler.java
          +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/support/WebSocketHttpRequestHandler.java
          @@ -35,14 +35,15 @@ import org.springframework.web.socket.support.ExceptionWebSocketHandlerDecorator
           import org.springframework.web.socket.support.LoggingWebSocketHandlerDecorator;
           
           /**
          - * An {@link HttpRequestHandler} for processing WebSocket handshake requests.
          - * 

          - * This is the main class to use when configuring a server WebSocket at a specific URL. It - * is a very thin wrapper around a {@link HandshakeHandler} and a {@link WebSocketHandler} - * instance also adapting the {@link HttpServletRequest} and {@link HttpServletResponse} - * to {@link ServerHttpRequest} and {@link ServerHttpResponse} respectively. - *

          - * The {@link #decorateWebSocketHandler(WebSocketHandler)} method decorates the given + * A {@link HttpRequestHandler} for processing WebSocket handshake requests. + * + *

          This is the main class to use when configuring a server WebSocket at a specific URL. + * It is a very thin wrapper around a {@link HandshakeHandler} and a + * {@link WebSocketHandler} instance also adapting the {@link HttpServletRequest} and + * {@link HttpServletResponse} to {@link ServerHttpRequest} and {@link ServerHttpResponse} + * respectively. + * + *

          The {@link #decorateWebSocketHandler(WebSocketHandler)} method decorates the given * WebSocketHandler with a logging and exception handling decorators. This method can * be overridden to change that. * @@ -61,16 +62,17 @@ public class WebSocketHttpRequestHandler implements HttpRequestHandler { } public WebSocketHttpRequestHandler( WebSocketHandler webSocketHandler, HandshakeHandler handshakeHandler) { - Assert.notNull(webSocketHandler, "webSocketHandler is required"); - Assert.notNull(handshakeHandler, "handshakeHandler is required"); + Assert.notNull(webSocketHandler, "webSocketHandler must not be null"); + Assert.notNull(handshakeHandler, "handshakeHandler must not be null"); this.webSocketHandler = decorateWebSocketHandler(webSocketHandler); this.handshakeHandler = new DefaultHandshakeHandler(); } + /** * Decorate the WebSocketHandler provided to the class constructor. - *

          - * By default {@link ExceptionWebSocketHandlerDecorator} and + * + *

          By default {@link ExceptionWebSocketHandlerDecorator} and * {@link LoggingWebSocketHandlerDecorator} are applied are added. */ protected WebSocketHandler decorateWebSocketHandler(WebSocketHandler handler) { diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractSockJsService.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractSockJsService.java index f01ccfd297..479226d31c 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractSockJsService.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.web.socket.sockjs; import java.io.IOException; @@ -27,6 +28,7 @@ import java.util.List; import java.util.Random; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.TimeUnit; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -48,8 +50,8 @@ import org.springframework.web.socket.WebSocketHandler; * support, SockJS path resolution, and processing for static SockJS requests (e.g. * "/info", "/iframe.html", etc). Sub-classes are responsible for handling transport * requests. - *

          - * It is expected that this service is mapped correctly to one or more prefixes such as + * + *

          It is expected that this service is mapped correctly to one or more prefixes such as * "/echo" including all sub-URLs (e.g. "/echo/**"). A SockJS service itself is generally * unaware of request mapping details but nevertheless must be able to extract the SockJS * path, which is the portion of the request path following the prefix. In most cases, @@ -63,7 +65,7 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf protected final Log logger = LogFactory.getLog(getClass()); - private static final int ONE_YEAR = 365 * 24 * 60 * 60; + private static final long ONE_YEAR = TimeUnit.DAYS.toSeconds(365); private String name = "SockJSService@" + ObjectUtils.getIdentityHexString(this); @@ -88,10 +90,11 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf public AbstractSockJsService(TaskScheduler scheduler) { - Assert.notNull(scheduler, "scheduler is required"); + Assert.notNull(scheduler, "scheduler must not be null"); this.taskScheduler = scheduler; } + /** * A unique name for the service mainly for logging purposes. */ @@ -107,8 +110,8 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf * Use this property to configure one or more prefixes that this SockJS service is * allowed to serve. The prefix (e.g. "/echo") is needed to extract the SockJS * specific portion of the URL (e.g. "${prefix}/info", "${prefix}/iframe.html", etc). - *

          - * This property is not strictly required. In most cases, the SockJS path can be + * + *

          This property is not strictly required. In most cases, the SockJS path can be * auto-detected since the initial request from the SockJS client is of the form * "{prefix}/info". Assuming the SockJS service is mapped correctly (e.g. using * Ant-style pattern "/echo/**") this should work fine. This property can be used @@ -144,8 +147,8 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf * a domain local to the SockJS server. The iframe does need to load the * SockJS javascript client library and this option allows configuring its * url. - *

          - * By default this is set to point to + * + *

          By default this is set to point to * "https://d1fxtkz8shb9d2.cloudfront.net/sockjs-0.3.4.min.js". */ public AbstractSockJsService setSockJsClientLibraryUrl(String clientLibraryUrl) { @@ -168,15 +171,15 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf @Override public int getStreamBytesLimit() { - return streamBytesLimit; + return this.streamBytesLimit; } /** * Some load balancers do sticky sessions, but only if there is a JSESSIONID * cookie. Even if it is set to a dummy value, it doesn't matter since * session information is added by the load balancer. - *

          - * Set this option to indicate if a JSESSIONID cookie should be created. The + * + *

          Set this option to indicate if a JSESSIONID cookie should be created. The * default value is "true". */ public AbstractSockJsService setJsessionIdCookieRequired(boolean jsessionIdCookieRequired) { @@ -211,8 +214,8 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf * The amount of time in milliseconds before a client is considered * disconnected after not having a receiving connection, i.e. an active * connection over which the server can send data to the client. - *

          - * The default value is 5000. + * + *

          The default value is 5000. */ public void setDisconnectDelay(long disconnectDelay) { this.disconnectDelay = disconnectDelay; @@ -228,8 +231,8 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf /** * Some load balancers don't support websockets. This option can be used to * disable the WebSocket transport on the server side. - *

          - * The default value is "true". + * + *

          The default value is "true". */ public void setWebSocketsEnabled(boolean webSocketsEnabled) { this.webSocketsEnabled = webSocketsEnabled; @@ -245,12 +248,6 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf /** * TODO - * - * @param request - * @param response - * @param sockJsPath - * - * @throws Exception */ @Override public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler handler) @@ -441,6 +438,7 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf void handle(ServerHttpRequest request, ServerHttpResponse response) throws IOException; } + private static final Random random = new Random(); private final SockJsRequestHandler infoHandler = new SockJsRequestHandler() { diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractSockJsSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractSockJsSession.java index c98daf7f0c..c5e35a6f20 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractSockJsSession.java @@ -34,7 +34,6 @@ import org.springframework.web.socket.WebSocketMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.adapter.ConfigurableWebSocketSession; - /** * An abstract base class SockJS sessions implementing {@link WebSocketSession}. * @@ -58,24 +57,26 @@ public abstract class AbstractSockJsSession implements ConfigurableWebSocketSess private final SockJsConfiguration sockJsConfig; - private WebSocketHandler handler; + private final WebSocketHandler handler; private State state = State.NEW; - private long timeCreated = System.currentTimeMillis(); + private final long timeCreated = System.currentTimeMillis(); - private long timeLastActive = timeCreated; + private long timeLastActive = this.timeCreated; private ScheduledFuture heartbeatTask; /** - * @param sessionId + * @param sessionId the session ID + * @param config the sockJS configuration * @param webSocketHandler the recipient of SockJS messages */ - public AbstractSockJsSession(String sessionId, SockJsConfiguration config, WebSocketHandler webSocketHandler) { - Assert.notNull(sessionId, "sessionId is required"); - Assert.notNull(webSocketHandler, "webSocketHandler is required"); + public AbstractSockJsSession(String sessionId, SockJsConfiguration config, + WebSocketHandler webSocketHandler) { + Assert.notNull(sessionId, "sessionId must not be null"); + Assert.notNull(webSocketHandler, "webSocketHandler must not be null"); this.id = sessionId; this.handler = webSocketHandler; this.sockJsConfig = config; @@ -191,7 +192,7 @@ public abstract class AbstractSockJsSession implements ConfigurableWebSocketSess /** * Invoked in reaction to the underlying connection being closed by the remote side * (or the WebSocket container) in order to perform cleanup and notify the - * {@link TextMessageHandler}. This is in contrast to {@link #close()} that pro-actively + * {@link WebSocketHandler}. This is in contrast to {@link #close()} that pro-actively * closes the connection. */ public final void delegateConnectionClosed(CloseStatus status) throws Exception { @@ -224,7 +225,8 @@ public abstract class AbstractSockJsSession implements ConfigurableWebSocketSess /** * {@inheritDoc} - *

          Performs cleanup and notifies the {@link SockJsHandler}. + * + *

          Performs cleanup and notifies the {@link WebSocketHandler}. */ @Override public final void close() throws IOException { @@ -233,7 +235,8 @@ public abstract class AbstractSockJsSession implements ConfigurableWebSocketSess /** * {@inheritDoc} - *

          Performs cleanup and notifies the {@link SockJsHandler}. + * + *

          Performs cleanup and notifies the {@link WebSocketHandler}. */ @Override public final void close(CloseStatus status) throws IOException { @@ -329,7 +332,7 @@ public abstract class AbstractSockJsSession implements ConfigurableWebSocketSess } protected void scheduleHeartbeat() { - Assert.notNull(this.sockJsConfig.getTaskScheduler(), "heartbeatScheduler not configured"); + Assert.state(this.sockJsConfig.getTaskScheduler() != null, "heartbeatScheduler not configured"); cancelHeartbeat(); if (!isActive()) { return; diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsConfiguration.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsConfiguration.java index 9867b5c889..ceba86f242 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsConfiguration.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsConfiguration.java @@ -35,8 +35,8 @@ public interface SockJsConfiguration { * closed. After that client will open a new request. Setting this value to * one effectively disables streaming and will make streaming transports to * behave like polling transports. - *

          - * The default value is 128K (i.e. 128 * 1024). + * + *

          The default value is 128K (i.e. 128 * 1024). */ public int getStreamBytesLimit(); @@ -44,8 +44,8 @@ public interface SockJsConfiguration { * The amount of time in milliseconds when the server has not sent any * messages and after which the server should send a heartbeat frame to the * client in order to keep the connection from breaking. - *

          - * The default value is 25,000 (25 seconds). + * + *

          The default value is 25,000 (25 seconds). */ public long getHeartbeatTime(); diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsFrame.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsFrame.java index c68687257f..b7620d2348 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsFrame.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsFrame.java @@ -44,7 +44,7 @@ public class SockJsFrame { private SockJsFrame(String content) { - Assert.notNull("content is required"); + Assert.notNull("content must not be null"); this.content = content; } @@ -144,7 +144,7 @@ public class SockJsFrame { } public static String prepareContent(String... messages) { - Assert.notNull(messages, "messages required"); + Assert.notNull(messages, "messages must not be null"); StringBuilder sb = new StringBuilder(); sb.append("a["); for (int i=0; i < messages.length; i++) { @@ -172,14 +172,12 @@ public class SockJsFrame { private final String format; public DefaultFrameFormat(String format) { - Assert.notNull(format, "format is required"); + Assert.notNull(format, "format must not be null"); this.format = format; } /** - * - * @param format a String with a single %s formatting character where the - * frame content is to be inserted; e.g. "data: %s\r\n\r\n" + * @param frame the SockJs frame. * @return new SockJsFrame instance with the formatted content */ @Override diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsService.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsService.java index c7140c9442..ad282ce231 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsService.java @@ -32,7 +32,6 @@ import org.springframework.web.socket.WebSocketHandler; */ public interface SockJsService { - void handleRequest(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler handler) throws IOException, TransportErrorException; diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsSessionFactory.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsSessionFactory.java index ca4aa247bc..0feee2d76c 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsSessionFactory.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsSessionFactory.java @@ -22,7 +22,6 @@ import org.springframework.web.socket.WebSocketHandler; * A factory for creating a SockJS session. {@link TransportHandler}s typically also serve * as SockJS session factories. * - * @param The type of session being created * @author Rossen Stoyanchev * @since 4.0 */ diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/TransportErrorException.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/TransportErrorException.java index f791d8f758..912712600a 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/TransportErrorException.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/TransportErrorException.java @@ -19,15 +19,14 @@ package org.springframework.web.socket.sockjs; import org.springframework.core.NestedRuntimeException; import org.springframework.web.socket.WebSocketHandler; - /** - * Raised when a TransportHandler fails during request processing. - *

          - * If the underlying exception occurs while sending messages to the client, the session is - * closed and the {@link WebSocketHandler} notified. - *

          - * If the underlying exception occurs while processing an incoming HTTP request, including - * over HTTP POST, the session will remain open. Only the incoming request is rejected. + * Raised when a TransportHandler fails during request processing. If the underlying + * exception occurs while sending messages to the client, the session is closed and the + * {@link WebSocketHandler} notified. + * + *

          If the underlying exception occurs while processing an incoming HTTP request, + * including over HTTP POST, the session will remain open. Only the incoming request is + * rejected. * * @author Rossen Stoyanchev * @since 4.0 @@ -43,7 +42,7 @@ public class TransportErrorException extends NestedRuntimeException { } public String getSockJsSessionId() { - return sockJsSessionId; + return this.sockJsSessionId; } @Override diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/TransportType.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/TransportType.java index 2a236176cc..a9b53a84c9 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/TransportType.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/TransportType.java @@ -17,6 +17,7 @@ package org.springframework.web.socket.sockjs; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -54,12 +55,13 @@ public enum TransportType { private final List headerHints; - private static final Map transportTypes = new HashMap(); - + private static final Map TRANSPORT_TYPES; static { + Map transportTypes = new HashMap(); for (TransportType type : values()) { transportTypes.put(type.value, type); } + TRANSPORT_TYPES = Collections.unmodifiableMap(transportTypes); } @@ -94,7 +96,7 @@ public enum TransportType { } public static TransportType fromValue(String value) { - return transportTypes.get(value); + return TRANSPORT_TYPES.get(value); } @Override diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/DefaultSockJsService.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/DefaultSockJsService.java index 6009d2c0ef..5e8c42097f 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/DefaultSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/DefaultSockJsService.java @@ -120,6 +120,7 @@ public class DefaultSockJsService extends AbstractSockJsService { } } + protected final Set getDefaultTransportHandlers() { Set result = new HashSet(); result.add(new XhrPollingTransportHandler()); diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/SockJsHttpRequestHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/SockJsHttpRequestHandler.java index 29c6dea9ea..3ae8cf7c17 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/SockJsHttpRequestHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/SockJsHttpRequestHandler.java @@ -34,14 +34,13 @@ import org.springframework.web.socket.support.ExceptionWebSocketHandlerDecorator import org.springframework.web.socket.support.LoggingWebSocketHandlerDecorator; /** - * An {@link HttpRequestHandler} for processing SockJS requests. - *

          - * This is the main class to use when configuring a SockJS service at a specific URL. It - * is a very thin wrapper around a {@link SockJsService} and a {@link WebSocketHandler} - * instance also adapting the {@link HttpServletRequest} and {@link HttpServletResponse} - * to {@link ServerHttpRequest} and {@link ServerHttpResponse} respectively. - *

          - * The {@link #decorateWebSocketHandler(WebSocketHandler)} method decorates the given + * An {@link HttpRequestHandler} for processing SockJS requests. This is the main class + * to use when configuring a SockJS service at a specific URL. It is a very thin wrapper + * around a {@link SockJsService} and a {@link WebSocketHandler} instance also adapting + * the {@link HttpServletRequest} and {@link HttpServletResponse} to + * {@link ServerHttpRequest} and {@link ServerHttpResponse} respectively. + * + *

          The {@link #decorateWebSocketHandler(WebSocketHandler)} method decorates the given * WebSocketHandler with a logging and exception handling decorators. This method can be * overridden to change that. * @@ -56,19 +55,22 @@ public class SockJsHttpRequestHandler implements HttpRequestHandler { /** - * Class constructor with {@link SockJsHandler} instance ... + * Create a new {@link SockJsHttpRequestHandler}. + * @param sockJsService the SockJS service + * @param webSocketHandler the websocket handler */ public SockJsHttpRequestHandler(SockJsService sockJsService, WebSocketHandler webSocketHandler) { - Assert.notNull(sockJsService, "sockJsService is required"); - Assert.notNull(webSocketHandler, "webSocketHandler is required"); + Assert.notNull(sockJsService, "sockJsService must not be null"); + Assert.notNull(webSocketHandler, "webSocketHandler must not be null"); this.sockJsService = sockJsService; this.webSocketHandler = decorateWebSocketHandler(webSocketHandler); } + /** * Decorate the WebSocketHandler provided to the class constructor. - *

          - * By default {@link ExceptionWebSocketHandlerDecorator} and + * + *

          By default {@link ExceptionWebSocketHandlerDecorator} and * {@link LoggingWebSocketHandlerDecorator} are applied are added. */ protected WebSocketHandler decorateWebSocketHandler(WebSocketHandler handler) { diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/AbstractHttpSockJsSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/AbstractHttpSockJsSession.java index 194a742db8..43827d415c 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/AbstractHttpSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/AbstractHttpSockJsSession.java @@ -54,6 +54,7 @@ public abstract class AbstractHttpSockJsSession extends AbstractSockJsSession { super(sessionId, config, handler); } + public synchronized void setInitialRequest(ServerHttpRequest request, ServerHttpResponse response, FrameFormat frameFormat) throws TransportErrorException { diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/EventSourceTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/EventSourceTransportHandler.java index 5e1d180df8..39b5d83a9a 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/EventSourceTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/EventSourceTransportHandler.java @@ -48,7 +48,7 @@ public class EventSourceTransportHandler extends AbstractHttpSendingTransportHan @Override public StreamingSockJsSession createSession(String sessionId, WebSocketHandler handler) { - Assert.notNull(getSockJsConfig(), "This transport requires SockJsConfiguration"); + Assert.state(getSockJsConfig() != null, "This transport requires SockJsConfiguration"); return new StreamingSockJsSession(sessionId, getSockJsConfig(), handler) { @Override protected void writePrelude() throws IOException { diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/HtmlFileTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/HtmlFileTransportHandler.java index bcc395a4fe..e3dfd012bb 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/HtmlFileTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/HtmlFileTransportHandler.java @@ -45,6 +45,10 @@ public class HtmlFileTransportHandler extends AbstractHttpSendingTransportHandle private static final String PARTIAL_HTML_CONTENT; + // Safari needs at least 1024 bytes to parse the website. + // http://code.google.com/p/browsersec/wiki/Part2#Survey_of_content_sniffing_behaviors + private static final int MINIMUM_PARTIAL_HTML_CONTENT_LENGTH = 1024; + static { StringBuilder sb = new StringBuilder( "\n" + @@ -61,11 +65,8 @@ public class HtmlFileTransportHandler extends AbstractHttpSendingTransportHandle " " ); - // Safari needs at least 1024 bytes to parse the website. - // http://code.google.com/p/browsersec/wiki/Part2#Survey_of_content_sniffing_behaviors - int spaces = 1024 - sb.length(); - for (int i=0; i < spaces; i++) { - sb.append(' '); + while(sb.length() < MINIMUM_PARTIAL_HTML_CONTENT_LENGTH) { + sb.append(" "); } PARTIAL_HTML_CONTENT = sb.toString(); @@ -84,7 +85,7 @@ public class HtmlFileTransportHandler extends AbstractHttpSendingTransportHandle @Override public StreamingSockJsSession createSession(String sessionId, WebSocketHandler handler) { - Assert.notNull(getSockJsConfig(), "This transport requires SockJsConfiguration"); + Assert.state(getSockJsConfig() != null, "This transport requires SockJsConfiguration"); return new StreamingSockJsSession(sessionId, getSockJsConfig(), handler) { diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/JsonpPollingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/JsonpPollingTransportHandler.java index e04c527c5d..4ebf1751fe 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/JsonpPollingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/JsonpPollingTransportHandler.java @@ -51,7 +51,7 @@ public class JsonpPollingTransportHandler extends AbstractHttpSendingTransportHa @Override public PollingSockJsSession createSession(String sessionId, WebSocketHandler handler) { - Assert.notNull(getSockJsConfig(), "This transport requires SockJsConfiguration"); + Assert.state(getSockJsConfig() != null, "This transport requires SockJsConfiguration"); return new PollingSockJsSession(sessionId, getSockJsConfig(), handler); } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/PollingSockJsSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/PollingSockJsSession.java index f7055ccc99..b35bd89d5e 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/PollingSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/PollingSockJsSession.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.web.socket.sockjs.transport; import java.io.IOException; @@ -32,6 +33,7 @@ public class PollingSockJsSession extends AbstractHttpSockJsSession { super(sessionId, config, handler); } + @Override protected void flushCache() throws IOException { cancelHeartbeat(); diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/SockJsWebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/SockJsWebSocketHandler.java index b463d91509..4f1d3e1e95 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/SockJsWebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/SockJsWebSocketHandler.java @@ -26,13 +26,12 @@ import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.adapter.TextWebSocketHandlerAdapter; import org.springframework.web.socket.sockjs.SockJsConfiguration; - /** * An implementation of {@link WebSocketHandler} that adds SockJS messages frames, sends * SockJS heartbeat messages, and delegates lifecycle events and messages to a target * {@link WebSocketHandler}. - *

          - * Methods in this class allow exceptions from the wrapped {@link WebSocketHandler} to + * + *

          Methods in this class allow exceptions from the wrapped {@link WebSocketHandler} to * propagate. However, any exceptions resulting from SockJS message handling (e.g. while * sending SockJS frames or heartbeat messages) are caught and treated as transport * errors, i.e. routed to the @@ -46,7 +45,7 @@ public class SockJsWebSocketHandler extends TextWebSocketHandlerAdapter { private final SockJsConfiguration sockJsConfig; - private WebSocketServerSockJsSession session; + private final WebSocketServerSockJsSession session; private final AtomicInteger sessionCount = new AtomicInteger(0); @@ -54,9 +53,9 @@ public class SockJsWebSocketHandler extends TextWebSocketHandlerAdapter { public SockJsWebSocketHandler(SockJsConfiguration config, WebSocketHandler webSocketHandler, WebSocketServerSockJsSession session) { - Assert.notNull(config, "sockJsConfig is required"); - Assert.notNull(webSocketHandler, "webSocketHandler is required"); - Assert.notNull(session, "session is required"); + Assert.notNull(config, "config must not be null"); + Assert.notNull(webSocketHandler, "webSocketHandler must not be null"); + Assert.notNull(session, "session must not be null"); this.sockJsConfig = config; this.session = session; diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/WebSocketServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/WebSocketServerSockJsSession.java index a6525ea191..d8b451492c 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/WebSocketServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/WebSocketServerSockJsSession.java @@ -29,7 +29,6 @@ import org.springframework.web.socket.sockjs.SockJsFrame; import com.fasterxml.jackson.databind.ObjectMapper; - /** * A SockJS session for use with the WebSocket transport. * @@ -48,6 +47,7 @@ public class WebSocketServerSockJsSession extends AbstractSockJsSession { super(sessionId, config, handler); } + public void initWebSocketSession(WebSocketSession session) throws Exception { this.webSocketSession = session; try { @@ -75,7 +75,7 @@ public class WebSocketServerSockJsSession extends AbstractSockJsSession { } String[] messages; try { - messages = objectMapper.readValue(payload, String[].class); + messages = this.objectMapper.readValue(payload, String[].class); } catch (IOException ex) { logger.error("Broken data received. Terminating WebSocket connection abruptly", ex); diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/WebSocketTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/WebSocketTransportHandler.java index 7e44e6e878..9bb00e4563 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/WebSocketTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/WebSocketTransportHandler.java @@ -31,12 +31,11 @@ import org.springframework.web.socket.sockjs.TransportErrorException; import org.springframework.web.socket.sockjs.TransportHandler; import org.springframework.web.socket.sockjs.TransportType; - /** * A WebSocket {@link TransportHandler}. Uses {@link SockJsWebSocketHandler} and * {@link WebSocketServerSockJsSession} to add SockJS processing. - *

          - * Also implements {@link HandshakeHandler} to support raw WebSocket communication at + * + *

          Also implements {@link HandshakeHandler} to support raw WebSocket communication at * SockJS URL "/websocket". * * @author Rossen Stoyanchev @@ -51,10 +50,11 @@ public class WebSocketTransportHandler implements ConfigurableTransportHandler, public WebSocketTransportHandler(HandshakeHandler handshakeHandler) { - Assert.notNull(handshakeHandler, "handshakeHandler is required"); + Assert.notNull(handshakeHandler, "handshakeHandler must not be null"); this.handshakeHandler = handshakeHandler; } + @Override public TransportType getTransportType() { return TransportType.WEBSOCKET; diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/XhrPollingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/XhrPollingTransportHandler.java index e14ee70d5c..8c784a2e83 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/XhrPollingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/XhrPollingTransportHandler.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.web.socket.sockjs.transport; import java.nio.charset.Charset; @@ -26,7 +27,6 @@ import org.springframework.web.socket.sockjs.SockJsFrame.FrameFormat; import org.springframework.web.socket.sockjs.TransportHandler; import org.springframework.web.socket.sockjs.TransportType; - /** * A {@link TransportHandler} based on XHR (long) polling. * @@ -35,7 +35,6 @@ import org.springframework.web.socket.sockjs.TransportType; */ public class XhrPollingTransportHandler extends AbstractHttpSendingTransportHandler { - @Override public TransportType getTransportType() { return TransportType.XHR; @@ -53,7 +52,7 @@ public class XhrPollingTransportHandler extends AbstractHttpSendingTransportHand @Override public PollingSockJsSession createSession(String sessionId, WebSocketHandler handler) { - Assert.notNull(getSockJsConfig(), "This transport requires SockJsConfiguration"); + Assert.state(getSockJsConfig() != null, "This transport requires SockJsConfiguration"); return new PollingSockJsSession(sessionId, getSockJsConfig(), handler); } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/XhrStreamingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/XhrStreamingTransportHandler.java index 16befee5eb..e0e7881168 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/XhrStreamingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/XhrStreamingTransportHandler.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.web.socket.sockjs.transport; import java.io.IOException; @@ -27,7 +28,6 @@ import org.springframework.web.socket.sockjs.SockJsFrame.FrameFormat; import org.springframework.web.socket.sockjs.TransportHandler; import org.springframework.web.socket.sockjs.TransportType; - /** * A {@link TransportHandler} that sends messages over an HTTP streaming request. * @@ -36,7 +36,6 @@ import org.springframework.web.socket.sockjs.TransportType; */ public class XhrStreamingTransportHandler extends AbstractHttpSendingTransportHandler { - @Override public TransportType getTransportType() { return TransportType.XHR_STREAMING; @@ -49,7 +48,7 @@ public class XhrStreamingTransportHandler extends AbstractHttpSendingTransportHa @Override public StreamingSockJsSession createSession(String sessionId, WebSocketHandler handler) { - Assert.notNull(getSockJsConfig(), "This transport requires SockJsConfiguration"); + Assert.state(getSockJsConfig() != null, "This transport requires SockJsConfiguration"); return new StreamingSockJsSession(sessionId, getSockJsConfig(), handler) { diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/XhrTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/XhrTransportHandler.java index b7e8768ea8..eff26d2611 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/XhrTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/XhrTransportHandler.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.web.socket.sockjs.transport; import java.io.IOException; @@ -22,7 +23,6 @@ import org.springframework.http.server.ServerHttpRequest; import org.springframework.web.socket.sockjs.TransportHandler; import org.springframework.web.socket.sockjs.TransportType; - /** * A {@link TransportHandler} that receives messages over HTTP. * @@ -30,7 +30,6 @@ import org.springframework.web.socket.sockjs.TransportType; */ public class XhrTransportHandler extends AbstractHttpReceivingTransportHandler { - @Override public TransportType getTransportType() { return TransportType.XHR_SEND; diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/package-info.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/package-info.java index 976e512ad3..a9ea15cede 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/package-info.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/package-info.java @@ -18,9 +18,8 @@ * Server-side support for SockJS transports including * {@link org.springframework.web.socket.sockjs.TransportHandler} implementations * for processing incoming requests and their - * {@link org.springframework.sockjs.SockJsSession} counterparts for + * {@link org.springframework.web.socket.sockjs.AbstractSockJsSession session} counterparts for * sending messages over the various transports. - * */ package org.springframework.web.socket.sockjs.transport; diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/support/BeanCreatingHandlerProvider.java b/spring-websocket/src/main/java/org/springframework/web/socket/support/BeanCreatingHandlerProvider.java index 298e8cf141..4e481444a9 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/support/BeanCreatingHandlerProvider.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/support/BeanCreatingHandlerProvider.java @@ -29,7 +29,7 @@ import org.springframework.util.Assert; * Instantiates a target handler through a Spring {@link BeanFactory} and also provides * an equivalent destroy method. Mainly for internal use to assist with initializing and * destroying handlers with per-connection lifecycle. - * + * * @author Rossen Stoyanchev * @since 4.0 */ @@ -43,7 +43,7 @@ public class BeanCreatingHandlerProvider implements BeanFactoryAware { public BeanCreatingHandlerProvider(Class handlerType) { - Assert.notNull(handlerType, "handlerType is required"); + Assert.notNull(handlerType, "handlerType must not be null"); this.handlerType = handlerType; } @@ -83,7 +83,7 @@ public class BeanCreatingHandlerProvider implements BeanFactoryAware { @Override public String toString() { - return "BeanCreatingHandlerProvider [handlerClass=" + handlerType + "]"; + return "BeanCreatingHandlerProvider [handlerClass=" + this.handlerType + "]"; } } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/support/ExceptionWebSocketHandlerDecorator.java b/spring-websocket/src/main/java/org/springframework/web/socket/support/ExceptionWebSocketHandlerDecorator.java index 35b3d33098..82d450459f 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/support/ExceptionWebSocketHandlerDecorator.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/support/ExceptionWebSocketHandlerDecorator.java @@ -23,7 +23,6 @@ import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketMessage; import org.springframework.web.socket.WebSocketSession; - /** * An exception handling {@link WebSocketHandlerDecorator}. Traps all {@link Throwable} * instances that escape from the decorated handler and closes the session with @@ -34,13 +33,14 @@ import org.springframework.web.socket.WebSocketSession; */ public class ExceptionWebSocketHandlerDecorator extends WebSocketHandlerDecorator { - private Log logger = LogFactory.getLog(ExceptionWebSocketHandlerDecorator.class); + private final Log logger = LogFactory.getLog(ExceptionWebSocketHandlerDecorator.class); public ExceptionWebSocketHandlerDecorator(WebSocketHandler delegate) { super(delegate); } + @Override public void afterConnectionEstablished(WebSocketSession session) { try { diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/support/LoggingWebSocketHandlerDecorator.java b/spring-websocket/src/main/java/org/springframework/web/socket/support/LoggingWebSocketHandlerDecorator.java index 1fecb8fd80..d5b12fa9e7 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/support/LoggingWebSocketHandlerDecorator.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/support/LoggingWebSocketHandlerDecorator.java @@ -23,7 +23,6 @@ import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketMessage; import org.springframework.web.socket.WebSocketSession; - /** * A {@link WebSocketHandlerDecorator} that adds logging to WebSocket lifecycle events. * @@ -32,7 +31,7 @@ import org.springframework.web.socket.WebSocketSession; */ public class LoggingWebSocketHandlerDecorator extends WebSocketHandlerDecorator { - private Log logger = LogFactory.getLog(LoggingWebSocketHandlerDecorator.class); + private final Log logger = LogFactory.getLog(LoggingWebSocketHandlerDecorator.class); public LoggingWebSocketHandlerDecorator(WebSocketHandler delegate) { diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/support/WebSocketHandlerDecorator.java b/spring-websocket/src/main/java/org/springframework/web/socket/support/WebSocketHandlerDecorator.java index 20fcec11d6..f4612700e6 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/support/WebSocketHandlerDecorator.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/support/WebSocketHandlerDecorator.java @@ -22,7 +22,6 @@ import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketMessage; import org.springframework.web.socket.WebSocketSession; - /** * @author Rossen Stoyanchev * @since 4.0 @@ -33,7 +32,7 @@ public class WebSocketHandlerDecorator implements WebSocketHandler { public WebSocketHandlerDecorator(WebSocketHandler delegate) { - Assert.notNull(delegate, "delegate is required"); + Assert.notNull(delegate, "delegate must not be null"); this.delegate = delegate; } @@ -42,6 +41,14 @@ public class WebSocketHandlerDecorator implements WebSocketHandler { return this.delegate; } + public WebSocketHandler getLastHandler() { + WebSocketHandler result = this.delegate; + while (result instanceof WebSocketHandlerDecorator) { + result = ((WebSocketHandlerDecorator) result).getDelegate(); + } + return result; + } + @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { this.delegate.afterConnectionEstablished(session); @@ -67,7 +74,6 @@ public class WebSocketHandlerDecorator implements WebSocketHandler { return this.delegate.supportsPartialMessages(); } - @Override public String toString() { return getClass().getSimpleName() + " [delegate=" + this.delegate + "]"; diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/AbstractHttpRequestTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/AbstractHttpRequestTests.java index 2965f4ddbf..c33063dda6 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/AbstractHttpRequestTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/AbstractHttpRequestTests.java @@ -24,7 +24,6 @@ import org.springframework.http.server.ServletServerHttpResponse; import org.springframework.mock.web.test.MockHttpServletRequest; import org.springframework.mock.web.test.MockHttpServletResponse; - /** * @author Rossen Stoyanchev */ diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/adapter/JettyWebSocketListenerAdapterTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/adapter/JettyWebSocketListenerAdapterTests.java index b13326895e..e55ec2a10f 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/adapter/JettyWebSocketListenerAdapterTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/adapter/JettyWebSocketListenerAdapterTests.java @@ -24,7 +24,6 @@ import org.springframework.web.socket.WebSocketHandler; import static org.mockito.Mockito.*; - /** * Test fixture for {@link JettyWebSocketListenerAdapter}. * diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/adapter/StandardEndpointAdapterTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/adapter/StandardEndpointAdapterTests.java index 4c9c64fe43..b38a39625c 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/adapter/StandardEndpointAdapterTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/adapter/StandardEndpointAdapterTests.java @@ -30,7 +30,6 @@ import static org.junit.Assert.*; import static org.mockito.Matchers.*; import static org.mockito.Mockito.*; - /** * Test fixture for {@link StandardEndpointAdapter}. * @@ -52,30 +51,30 @@ public class StandardEndpointAdapterTests { this.session = mock(Session.class); this.webSocketHandler = mock(WebSocketHandler.class); this.webSocketSession = new StandardWebSocketSessionAdapter(); - this.adapter = new StandardEndpointAdapter(webSocketHandler, webSocketSession); + this.adapter = new StandardEndpointAdapter(this.webSocketHandler, this.webSocketSession); } @Test public void onOpen() throws Throwable { - this.adapter.onOpen(session, null); + this.adapter.onOpen(this.session, null); verify(this.webSocketHandler).afterConnectionEstablished(this.webSocketSession); - verify(session, atLeast(2)).addMessageHandler(any(MessageHandler.Whole.class)); + verify(this.session, atLeast(2)).addMessageHandler(any(MessageHandler.Whole.class)); - when(session.getId()).thenReturn("123"); + when(this.session.getId()).thenReturn("123"); assertEquals("123", this.webSocketSession.getId()); } @Test public void onClose() throws Throwable { - this.adapter.onClose(session, new CloseReason(CloseCodes.NORMAL_CLOSURE, "reason")); + this.adapter.onClose(this.session, new CloseReason(CloseCodes.NORMAL_CLOSURE, "reason")); verify(this.webSocketHandler).afterConnectionClosed(this.webSocketSession, CloseStatus.NORMAL.withReason("reason")); } @Test public void onError() throws Throwable { Exception exception = new Exception(); - this.adapter.onError(session, exception); + this.adapter.onError(this.session, exception); verify(this.webSocketHandler).handleTransportError(this.webSocketSession, exception); } diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/client/WebSocketConnectionManagerTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/client/WebSocketConnectionManagerTests.java index 0d8ed6dcdd..8e08674a2b 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/client/WebSocketConnectionManagerTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/client/WebSocketConnectionManagerTests.java @@ -33,7 +33,6 @@ import org.springframework.web.socket.support.WebSocketHandlerDecorator; import static org.junit.Assert.*; import static org.mockito.Mockito.*; - /** * Test fixture for {@link WebSocketConnectionManager}. * @@ -41,7 +40,6 @@ import static org.mockito.Mockito.*; */ public class WebSocketConnectionManagerTests { - @Test public void openConnection() throws Exception { diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/client/endpoint/StandardWebSocketClientTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/client/endpoint/StandardWebSocketClientTests.java index 5e16a560f7..4e65f7663c 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/client/endpoint/StandardWebSocketClientTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/client/endpoint/StandardWebSocketClientTests.java @@ -38,7 +38,6 @@ import org.springframework.web.socket.adapter.WebSocketHandlerAdapter; import static org.junit.Assert.*; import static org.mockito.Mockito.*; - /** * Test fixture for {@link StandardWebSocketClient}. * @@ -46,7 +45,6 @@ import static org.mockito.Mockito.*; */ public class StandardWebSocketClientTests { - @Test public void doHandshake() throws Exception { diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/server/endpoint/ServerEndpointExporterTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/server/endpoint/ServerEndpointExporterTests.java index 99881546ee..8b1ed43169 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/server/endpoint/ServerEndpointExporterTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/server/endpoint/ServerEndpointExporterTests.java @@ -31,7 +31,6 @@ import org.springframework.web.context.support.AnnotationConfigWebApplicationCon import static org.mockito.Mockito.*; - /** * Test fixture for {@link ServerEndpointExporter}. * @@ -51,7 +50,7 @@ public class ServerEndpointExporterTests { this.serverContainer = mock(ServerContainer.class); MockServletContext servletContext = new MockServletContext(); - servletContext.setAttribute("javax.websocket.server.ServerContainer", serverContainer); + servletContext.setAttribute("javax.websocket.server.ServerContainer", this.serverContainer); this.webAppContext = new AnnotationConfigWebApplicationContext(); this.webAppContext.register(Config.class); diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/server/endpoint/ServerEndpointRegistrationTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/server/endpoint/ServerEndpointRegistrationTests.java index 47280fecf9..aa3bbde2ff 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/server/endpoint/ServerEndpointRegistrationTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/server/endpoint/ServerEndpointRegistrationTests.java @@ -29,7 +29,6 @@ import org.springframework.context.annotation.Configuration; import static org.junit.Assert.*; - /** * Test fixture for {@link ServerEndpointRegistration}. * diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/server/endpoint/SpringConfiguratorTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/server/endpoint/SpringConfiguratorTests.java index 433c2c8411..87f2bcf9f4 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/server/endpoint/SpringConfiguratorTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/server/endpoint/SpringConfiguratorTests.java @@ -32,10 +32,8 @@ import org.springframework.web.context.support.AnnotationConfigWebApplicationCon import static org.junit.Assert.*; - public class SpringConfiguratorTests { - private MockServletContext servletContext; private ContextLoader contextLoader; @@ -50,7 +48,7 @@ public class SpringConfiguratorTests { this.webAppContext = new AnnotationConfigWebApplicationContext(); this.webAppContext.register(Config.class); - this.contextLoader = new ContextLoader(webAppContext); + this.contextLoader = new ContextLoader(this.webAppContext); this.contextLoader.initWebApplicationContext(this.servletContext); } diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/AbstractSockJsServiceTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/AbstractSockJsServiceTests.java index 580ac3ad39..13cb2e87e4 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/AbstractSockJsServiceTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/AbstractSockJsServiceTests.java @@ -180,7 +180,7 @@ public class AbstractSockJsServiceTests extends AbstractHttpRequestTests { assertTrue(this.servletResponse.getContentAsString().startsWith("\n")); assertEquals(496, this.servletResponse.getContentLength()); assertEquals("public, max-age=31536000", this.response.getHeaders().getCacheControl()); - assertEquals("\"0da1ed070012f304e47b83c81c48ad620\"", response.getHeaders().getETag()); + assertEquals("\"0da1ed070012f304e47b83c81c48ad620\"", this.response.getHeaders().getETag()); } @Test diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/AbstractSockJsSessionTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/AbstractSockJsSessionTests.java index a91ab39976..b73d7d8b1d 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/AbstractSockJsSessionTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/AbstractSockJsSessionTests.java @@ -29,7 +29,6 @@ import static org.junit.Assert.*; import static org.mockito.Matchers.*; import static org.mockito.Mockito.*; - /** * Test fixture for {@link AbstractSockJsSession}. * diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/BaseAbstractSockJsSessionTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/BaseAbstractSockJsSessionTests.java index 9d4f50c60d..58432bd0aa 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/BaseAbstractSockJsSessionTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/BaseAbstractSockJsSessionTests.java @@ -23,7 +23,6 @@ import org.springframework.web.socket.WebSocketHandler; import static org.junit.Assert.*; import static org.mockito.Mockito.*; - /** * Base class for {@link AbstractSockJsSession} classes. * diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/StubSockJsConfig.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/StubSockJsConfig.java index 5b00284639..050c920540 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/StubSockJsConfig.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/StubSockJsConfig.java @@ -19,7 +19,6 @@ package org.springframework.web.socket.sockjs; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; - /** * @author Rossen Stoyanchev */ @@ -34,7 +33,7 @@ public class StubSockJsConfig implements SockJsConfiguration { @Override public int getStreamBytesLimit() { - return streamBytesLimit; + return this.streamBytesLimit; } public void setStreamBytesLimit(int streamBytesLimit) { @@ -43,7 +42,7 @@ public class StubSockJsConfig implements SockJsConfiguration { @Override public long getHeartbeatTime() { - return heartbeatTime; + return this.heartbeatTime; } public void setHeartbeatTime(long heartbeatTime) { @@ -52,7 +51,7 @@ public class StubSockJsConfig implements SockJsConfiguration { @Override public TaskScheduler getTaskScheduler() { - return taskScheduler; + return this.taskScheduler; } public void setTaskScheduler(TaskScheduler taskScheduler) { diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/TestSockJsSession.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/TestSockJsSession.java index 1bb00fb037..684db0d116 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/TestSockJsSession.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/TestSockJsSession.java @@ -23,7 +23,6 @@ import java.util.List; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.WebSocketHandler; - /** * @author Rossen Stoyanchev */ @@ -95,7 +94,7 @@ public class TestSockJsSession extends AbstractSockJsSession { protected void writeFrameInternal(SockJsFrame frame) throws Exception { this.sockJsFramesWritten.add(frame); if (this.exceptionOnWriteFrame != null) { - throw exceptionOnWriteFrame; + throw this.exceptionOnWriteFrame; } } diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/TransportTypeTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/TransportTypeTests.java index 9f4b34c17f..1e06d1d66e 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/TransportTypeTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/TransportTypeTests.java @@ -20,13 +20,11 @@ import org.junit.Test; import static org.junit.Assert.*; - /** * @author Rossen Stoyanchev */ public class TransportTypeTests { - @Test public void testFromValue() { assertEquals(TransportType.WEBSOCKET, TransportType.fromValue("websocket")); diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/support/DefaultSockJsServiceTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/support/DefaultSockJsServiceTests.java index 4c91e582e1..31fb40c6c1 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/support/DefaultSockJsServiceTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/support/DefaultSockJsServiceTests.java @@ -48,7 +48,6 @@ import static org.mockito.Mockito.*; */ public class DefaultSockJsServiceTests extends AbstractHttpRequestTests { - @Override @Before public void setUp() { diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/HttpSendingTransportHandlerTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/HttpSendingTransportHandlerTests.java index 68383088a3..01f4afc301 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/HttpSendingTransportHandlerTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/HttpSendingTransportHandlerTests.java @@ -62,24 +62,24 @@ public class HttpSendingTransportHandlerTests extends AbstractHttpRequestTests public void handleRequestXhr() throws Exception { XhrPollingTransportHandler transportHandler = new XhrPollingTransportHandler(); - transportHandler.setSockJsConfiguration(sockJsConfig); + transportHandler.setSockJsConfiguration(this.sockJsConfig); - AbstractSockJsSession session = transportHandler.createSession("1", webSocketHandler); - transportHandler.handleRequest(request, response, webSocketHandler, session); + AbstractSockJsSession session = transportHandler.createSession("1", this.webSocketHandler); + transportHandler.handleRequest(this.request, this.response, this.webSocketHandler, session); assertEquals("application/javascript;charset=UTF-8", this.response.getHeaders().getContentType().toString()); assertEquals("o\n", this.servletResponse.getContentAsString()); assertFalse("Polling request should complete after open frame", this.servletRequest.isAsyncStarted()); - verify(webSocketHandler).afterConnectionEstablished(session); + verify(this.webSocketHandler).afterConnectionEstablished(session); resetResponse(); - transportHandler.handleRequest(request, response, webSocketHandler, session); + transportHandler.handleRequest(this.request, this.response, this.webSocketHandler, session); assertTrue("Polling request should remain open", this.servletRequest.isAsyncStarted()); verify(this.taskScheduler).schedule(any(Runnable.class), any(Date.class)); resetRequestAndResponse(); - transportHandler.handleRequest(request, response, webSocketHandler, session); + transportHandler.handleRequest(this.request, this.response, this.webSocketHandler, session); assertFalse("Request should have been rejected", this.servletRequest.isAsyncStarted()); assertEquals("c[2010,\"Another connection still open\"]\n", this.servletResponse.getContentAsString()); @@ -89,70 +89,70 @@ public class HttpSendingTransportHandlerTests extends AbstractHttpRequestTests public void jsonpTransport() throws Exception { JsonpPollingTransportHandler transportHandler = new JsonpPollingTransportHandler(); - transportHandler.setSockJsConfiguration(sockJsConfig); - PollingSockJsSession session = transportHandler.createSession("1", webSocketHandler); + transportHandler.setSockJsConfiguration(this.sockJsConfig); + PollingSockJsSession session = transportHandler.createSession("1", this.webSocketHandler); - transportHandler.handleRequest(request, response, webSocketHandler, session); + transportHandler.handleRequest(this.request, this.response, this.webSocketHandler, session); assertEquals(500, this.servletResponse.getStatus()); assertEquals("\"callback\" parameter required", this.servletResponse.getContentAsString()); resetRequestAndResponse(); this.servletRequest.addParameter("c", "callback"); - transportHandler.handleRequest(request, response, webSocketHandler, session); + transportHandler.handleRequest(this.request, this.response, this.webSocketHandler, session); assertEquals("application/javascript;charset=UTF-8", this.response.getHeaders().getContentType().toString()); assertFalse("Polling request should complete after open frame", this.servletRequest.isAsyncStarted()); - verify(webSocketHandler).afterConnectionEstablished(session); + verify(this.webSocketHandler).afterConnectionEstablished(session); } @Test public void handleRequestXhrStreaming() throws Exception { XhrStreamingTransportHandler transportHandler = new XhrStreamingTransportHandler(); - transportHandler.setSockJsConfiguration(sockJsConfig); - AbstractSockJsSession session = transportHandler.createSession("1", webSocketHandler); + transportHandler.setSockJsConfiguration(this.sockJsConfig); + AbstractSockJsSession session = transportHandler.createSession("1", this.webSocketHandler); - transportHandler.handleRequest(request, response, webSocketHandler, session); + transportHandler.handleRequest(this.request, this.response, this.webSocketHandler, session); assertEquals("application/javascript;charset=UTF-8", this.response.getHeaders().getContentType().toString()); assertTrue("Streaming request not started", this.servletRequest.isAsyncStarted()); - verify(webSocketHandler).afterConnectionEstablished(session); + verify(this.webSocketHandler).afterConnectionEstablished(session); } @Test public void htmlFileTransport() throws Exception { HtmlFileTransportHandler transportHandler = new HtmlFileTransportHandler(); - transportHandler.setSockJsConfiguration(sockJsConfig); - StreamingSockJsSession session = transportHandler.createSession("1", webSocketHandler); + transportHandler.setSockJsConfiguration(this.sockJsConfig); + StreamingSockJsSession session = transportHandler.createSession("1", this.webSocketHandler); - transportHandler.handleRequest(request, response, webSocketHandler, session); + transportHandler.handleRequest(this.request, this.response, this.webSocketHandler, session); assertEquals(500, this.servletResponse.getStatus()); assertEquals("\"callback\" parameter required", this.servletResponse.getContentAsString()); resetRequestAndResponse(); this.servletRequest.addParameter("c", "callback"); - transportHandler.handleRequest(request, response, webSocketHandler, session); + transportHandler.handleRequest(this.request, this.response, this.webSocketHandler, session); assertEquals("text/html;charset=UTF-8", this.response.getHeaders().getContentType().toString()); assertTrue("Streaming request not started", this.servletRequest.isAsyncStarted()); - verify(webSocketHandler).afterConnectionEstablished(session); + verify(this.webSocketHandler).afterConnectionEstablished(session); } @Test public void eventSourceTransport() throws Exception { EventSourceTransportHandler transportHandler = new EventSourceTransportHandler(); - transportHandler.setSockJsConfiguration(sockJsConfig); - StreamingSockJsSession session = transportHandler.createSession("1", webSocketHandler); + transportHandler.setSockJsConfiguration(this.sockJsConfig); + StreamingSockJsSession session = transportHandler.createSession("1", this.webSocketHandler); - transportHandler.handleRequest(request, response, webSocketHandler, session); + transportHandler.handleRequest(this.request, this.response, this.webSocketHandler, session); assertEquals("text/event-stream;charset=UTF-8", this.response.getHeaders().getContentType().toString()); assertTrue("Streaming request not started", this.servletRequest.isAsyncStarted()); - verify(webSocketHandler).afterConnectionEstablished(session); + verify(this.webSocketHandler).afterConnectionEstablished(session); } @Test @@ -162,23 +162,23 @@ public class HttpSendingTransportHandlerTests extends AbstractHttpRequestTests SockJsFrame frame = SockJsFrame.openFrame(); - FrameFormat format = new XhrPollingTransportHandler().getFrameFormat(request); + FrameFormat format = new XhrPollingTransportHandler().getFrameFormat(this.request); SockJsFrame formatted = format.format(frame); assertEquals(frame.getContent() + "\n", formatted.getContent()); - format = new XhrStreamingTransportHandler().getFrameFormat(request); + format = new XhrStreamingTransportHandler().getFrameFormat(this.request); formatted = format.format(frame); assertEquals(frame.getContent() + "\n", formatted.getContent()); - format = new HtmlFileTransportHandler().getFrameFormat(request); + format = new HtmlFileTransportHandler().getFrameFormat(this.request); formatted = format.format(frame); assertEquals("\r\n", formatted.getContent()); - format = new EventSourceTransportHandler().getFrameFormat(request); + format = new EventSourceTransportHandler().getFrameFormat(this.request); formatted = format.format(frame); assertEquals("data: " + frame.getContent() + "\r\n\r\n", formatted.getContent()); - format = new JsonpPollingTransportHandler().getFrameFormat(request); + format = new JsonpPollingTransportHandler().getFrameFormat(this.request); formatted = format.format(frame); assertEquals("callback(\"" + frame.getContent() + "\");\r\n", formatted.getContent()); } diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/WebSocketServerSockJsSessionTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/WebSocketServerSockJsSessionTests.java index a145541a8a..f6e8f99407 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/WebSocketServerSockJsSessionTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/WebSocketServerSockJsSessionTests.java @@ -36,7 +36,6 @@ import static org.junit.Assert.*; import static org.mockito.Matchers.*; import static org.mockito.Mockito.*; - /** * Test fixture for {@link WebSocketServerSockJsSession}. * diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/support/BeanCreatingHandlerProviderTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/support/BeanCreatingHandlerProviderTests.java index 0e66bbad9b..6267b75889 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/support/BeanCreatingHandlerProviderTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/support/BeanCreatingHandlerProviderTests.java @@ -26,8 +26,6 @@ import org.springframework.context.annotation.Configuration; import static org.junit.Assert.*; - - /** * Test fixture for {@link BeanCreatingHandlerProvider}. * diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/support/TestWebSocketSession.java b/spring-websocket/src/test/java/org/springframework/web/socket/support/TestWebSocketSession.java index d740d2ed1c..a567c28573 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/support/TestWebSocketSession.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/support/TestWebSocketSession.java @@ -26,7 +26,6 @@ import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.WebSocketMessage; import org.springframework.web.socket.WebSocketSession; - /** * A {@link WebSocketSession} for use in tests. * @@ -58,7 +57,7 @@ public class TestWebSocketSession implements WebSocketSession { */ @Override public String getId() { - return id; + return this.id; } /** @@ -73,7 +72,7 @@ public class TestWebSocketSession implements WebSocketSession { */ @Override public URI getUri() { - return uri; + return this.uri; } /** @@ -88,7 +87,7 @@ public class TestWebSocketSession implements WebSocketSession { */ @Override public boolean isSecure() { - return secure; + return this.secure; } /** @@ -103,7 +102,7 @@ public class TestWebSocketSession implements WebSocketSession { */ @Override public Principal getPrincipal() { - return principal; + return this.principal; } /** @@ -118,7 +117,7 @@ public class TestWebSocketSession implements WebSocketSession { */ @Override public String getRemoteHostName() { - return remoteHostName; + return this.remoteHostName; } /** @@ -133,7 +132,7 @@ public class TestWebSocketSession implements WebSocketSession { */ @Override public String getRemoteAddress() { - return remoteAddress; + return this.remoteAddress; } /** @@ -148,7 +147,7 @@ public class TestWebSocketSession implements WebSocketSession { */ @Override public boolean isOpen() { - return open; + return this.open; } /** diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/support/WebSocketHandlerDecoratorTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/support/WebSocketHandlerDecoratorTests.java new file mode 100644 index 0000000000..b75e8e8623 --- /dev/null +++ b/spring-websocket/src/test/java/org/springframework/web/socket/support/WebSocketHandlerDecoratorTests.java @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.socket.support; + +import org.junit.Test; +import org.springframework.web.socket.adapter.WebSocketHandlerAdapter; + +import static org.junit.Assert.*; + +/** + * Test fixture for {@link WebSocketHandlerDecorator}. + * + * @author Rossen Stoyanchev + */ +public class WebSocketHandlerDecoratorTests { + + + @Test + public void getLastHandler() { + WebSocketHandlerAdapter h1 = new WebSocketHandlerAdapter(); + WebSocketHandlerDecorator h2 = new WebSocketHandlerDecorator(h1); + WebSocketHandlerDecorator h3 = new WebSocketHandlerDecorator(h2); + + assertSame(h1, h3.getLastHandler()); + } + +} diff --git a/src/reference/docbook/jdbc.xml b/src/reference/docbook/jdbc.xml index 39ff7f3b75..6468005153 100644 --- a/src/reference/docbook/jdbc.xml +++ b/src/reference/docbook/jdbc.xml @@ -304,12 +304,12 @@ Here is a simple query for getting the number of rows in a relation: - int rowCount = this.jdbcTemplate.queryForObject("select count(*) from t_actor", int.class); + int rowCount = this.jdbcTemplate.queryForObject("select count(*) from t_actor", Integer.class); A simple query using a bind variable: int countOfActorsNamedJoe = this.jdbcTemplate.queryForObject( - "select count(*) from t_actor where first_name = ?", int.class, "Joe"); + "select count(*) from t_actor where first_name = ?", Integer.class, "Joe"); Querying for a String: @@ -383,7 +383,7 @@ private static final class ActorMapper implements RowMapper<Actor> { "Leonor", "Watling"); this.jdbcTemplate.update( - "update t_actor set = ? where id = ?", + "update t_actor set last_name = ? where id = ?", "Banjo", 5276L); this.jdbcTemplate.update( @@ -567,7 +567,7 @@ public int countOfActorsByFirstName(String firstName) { SqlParameterSource namedParameters = new MapSqlParameterSource("first_name", firstName); - return this.namedParameterJdbcTemplate.queryForObject(sql, int.class, namedParameters); + return this.namedParameterJdbcTemplate.queryForObject(sql, Integer.class, namedParameters); } Notice the use of the named parameter notation in the value @@ -600,7 +600,7 @@ public int countOfActorsByFirstName(String firstName) { Map<String, String> namedParameters = Collections.singletonMap("first_name", firstName); - return this.namedParameterJdbcTemplate.queryForObject(sql, int.class, namedParameters); + return this.namedParameterJdbcTemplate.queryForObject(sql, Integer.class, namedParameters); } One nice feature related to the @@ -664,7 +664,7 @@ public int countOfActors(Actor exampleActor) { SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(exampleActor); - return this.namedParameterJdbcTemplate.queryForObject(sql, int.class, namedParameters); + return this.namedParameterJdbcTemplate.queryForObject(sql, Integer.class, namedParameters); } Remember that the @@ -860,7 +860,7 @@ public class RunAQuery { } public int getCount() { - return this.jdbcTemplate.queryForObject("select count(*) from mytable", int.class); + return this.jdbcTemplate.queryForObject("select count(*) from mytable", Integer.class); } public String getName() { diff --git a/src/reference/docbook/mvc.xml b/src/reference/docbook/mvc.xml index 1b8a2e0c28..6a2415de90 100644 --- a/src/reference/docbook/mvc.xml +++ b/src/reference/docbook/mvc.xml @@ -5072,12 +5072,12 @@ public class WebConfig extends WebMvcConfigurerAdapter { <mvc:interceptors> <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" /> <mvc:interceptor> - <mapping path="/**"/> - <exclude-mapping path="/admin/**"/> + <mvc:mapping path="/**"/> + <mvc:exclude-mapping path="/admin/**"/> <bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor" /> </mvc:interceptor> <mvc:interceptor> - <mapping path="/secure/*"/> + <mvc:mapping path="/secure/*"/> <bean class="org.example.SecurityInterceptor" /> </mvc:interceptor> </mvc:interceptors> diff --git a/src/reference/docbook/testing.xml b/src/reference/docbook/testing.xml index 72bb4d3c8f..ec78308b38 100644 --- a/src/reference/docbook/testing.xml +++ b/src/reference/docbook/testing.xml @@ -197,7 +197,6 @@ TestContext framework is agnostic of the actual testing framework in use, thus allowing instrumentation of tests in various environments including JUnit, TestNG, and so on. -

          @@ -444,13 +443,13 @@ @ContextConfiguration("/test-config.xml") public class XmlApplicationContextTests { - // class body... + // class body... } @ContextConfiguration(classes = TestConfig.class) public class ConfigClassApplicationContextTests { - // class body... + // class body... } As an alternative or in addition to declaring resource @@ -462,7 +461,7 @@ public class ConfigClassApplicationContextTests { @ContextConfiguration(initializers = CustomContextIntializer.class) public class ContextInitializerTests { - // class body... + // class body... } @ContextConfiguration may @@ -477,7 +476,7 @@ public class ContextInitializerTests { role="bold">locations = "/test-context.xml", loader = CustomContextLoader.class) public class CustomLoaderXmlApplicationContextTests { - // class body... + // class body... } @@ -514,7 +513,7 @@ public class CustomLoaderXmlApplicationContextTests { @ContextConfiguration @WebAppConfiguration public class WebAppTests { - // class body... + // class body... } To override the default, specify a different base resource @@ -527,7 +526,7 @@ public class WebAppTests { @ContextConfiguration @WebAppConfiguration("classpath:test-web-resources") public class WebAppTests { - // class body... + // class body... } Note that @WebAppConfiguration @@ -559,7 +558,7 @@ public class WebAppTests { @ContextConfiguration("/child-config.xml") }) public class ContextHierarchyTests { - // class body... + // class body... } @WebAppConfiguration @@ -568,7 +567,7 @@ public class ContextHierarchyTests { @ContextConfiguration(classes = WebConfig.class) }) public class WebIntegrationTests { - // class body... + // class body... } If you need to merge or override the configuration for a given @@ -594,19 +593,24 @@ public class WebIntegrationTests { @ContextConfiguration @ActiveProfiles("dev") public class DeveloperTests { - // class body... + // class body... } @ContextConfiguration @ActiveProfiles({"dev", "integration"}) public class DeveloperIntegrationTests { - // class body... + // class body... } @ActiveProfiles provides support for inheriting active bean definition - profiles declared by superclasses by default. + profiles declared by superclasses by default. It is also possible + to resolve active bean definition profiles programmatically by + implementing a custom + ActiveProfilesResolver and + registering it via the resolver attribute of + @ActiveProfiles. See @@ -649,19 +653,20 @@ public class DeveloperIntegrationTests { @DirtiesContext public class ContextDirtyingTests { - // some tests that result in the Spring container being dirtied + // some tests that result in the Spring container being dirtied } After each test method in the current test class, when declared on a class with class mode set to - AFTER_EACH_TEST_METHOD.@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) + AFTER_EACH_TEST_METHOD. + + @DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) public class ContextDirtyingTests { - // some tests that result in the Spring container being dirtied -} + // some tests that result in the Spring container being dirtied +} @@ -670,7 +675,7 @@ public class ContextDirtyingTests { @DirtiesContext @Test public void testProcessWhichDirtiesAppCtx() { - // some logic that results in the Spring container being dirtied + // some logic that results in the Spring container being dirtied } @@ -696,7 +701,7 @@ public void testProcessWhichDirtiesAppCtx() { @ContextConfiguration("/child-config.xml") }) public class BaseTests { - // class body... + // class body... } public class ExtendedTests extends BaseTests { @@ -704,7 +709,7 @@ public class ExtendedTests extends BaseTests { @Test @DirtiesContext(hierarchyMode = HierarchyMode.CURRENT_LEVEL) public void test() { - // some logic that results in the child context being dirtied + // some logic that results in the child context being dirtied } } @@ -729,7 +734,7 @@ public class ExtendedTests extends BaseTests { @ContextConfiguration @TestExecutionListeners({CustomTestExecutionListener.class, AnotherTestExecutionListener.class}) public class CustomTestExecutionListenerTests { - // class body... + // class body... } @TestExecutionListeners @@ -761,7 +766,7 @@ public class CustomTestExecutionListenerTests { @TransactionConfiguration(transactionManager = "txMgr", defaultRollback = false) public class CustomConfiguredTransactionalTests { - // class body... + // class body... } @@ -793,7 +798,7 @@ public class CustomConfiguredTransactionalTests { @Rollback(false) @Test public void testProcessWithoutRollback() { - // ... + // ... } @@ -809,7 +814,7 @@ public void testProcessWithoutRollback() { @BeforeTransaction public void beforeTransaction() { - // logic to be executed before a transaction is started + // logic to be executed before a transaction is started } @@ -825,7 +830,7 @@ public void testProcessWithoutRollback() { @AfterTransaction public void afterTransaction() { - // logic to be executed after a transaction has ended + // logic to be executed after a transaction has ended } @@ -944,7 +949,7 @@ public void testProcessWithoutRollback() { role="bold">name="java.vendor", value="Sun Microsystems Inc.") @Test public void testProcessWhichRunsOnlyOnSunJvm() { - // some logic that should run only on Java VMs from Sun Microsystems + // some logic that should run only on Java VMs from Sun Microsystems } Alternatively, you can configure @@ -957,7 +962,7 @@ public void testProcessWhichRunsOnlyOnSunJvm() { role="bold">name="test-groups", values={"unit-tests", "integration-tests"}) @Test public void testProcessWhichRunsForUnitOrIntegrationTestGroups() { - // some logic that should run only for unit and integration test groups + // some logic that should run only for unit and integration test groups } @@ -977,7 +982,7 @@ public void testProcessWhichRunsForUnitOrIntegrationTestGroups() { @ProfileValueSourceConfiguration(CustomProfileValueSource.class) public class CustomProfileValueSourceTests { - // class body... + // class body... } @@ -997,7 +1002,7 @@ public class CustomProfileValueSourceTests { @Timed(millis=1000) public void testProcessWithOneSecondTimeout() { - // some logic that should not take longer than 1 second to execute + // some logic that should not take longer than 1 second to execute } Spring's @Timed annotation has @@ -1030,7 +1035,7 @@ public void testProcessWithOneSecondTimeout() { @Repeat(10) @Test public void testProcessRepeatedly() { - // ... + // ... } @@ -1286,7 +1291,7 @@ public class MyTest { @Autowired private ApplicationContext applicationContext; - // class body... + // class body... } Similarly, if your test is configured to load a @@ -1301,7 +1306,7 @@ public class MyWebAppTest { @Autowired private WebApplicationContext wac; - // class body... + // class body... } Dependency injection via @@ -1355,11 +1360,11 @@ public class MyWebAppTest { is. @RunWith(SpringJUnit4ClassRunner.class) -// ApplicationContext will be loaded from "/app-config.xml" and -// "/test-config.xml" in the root of the classpath +// ApplicationContext will be loaded from "/app-config.xml" and +// "/test-config.xml" in the root of the classpath @ContextConfiguration(locations={"/app-config.xml", "/test-config.xml"}) public class MyTest { - // class body... + // class body... } @ContextConfiguration supports an @@ -1374,7 +1379,7 @@ public class MyTest { @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration({"/app-config.xml", "/test-config.xml"}) public class MyTest { - // class body... + // class body... } If you omit both the locations and @@ -1391,11 +1396,11 @@ public class MyTest { package com.example; @RunWith(SpringJUnit4ClassRunner.class) -// ApplicationContext will be loaded from -// "classpath:/com/example/MyTest-context.xml" +// ApplicationContext will be loaded from +// "classpath:/com/example/MyTest-context.xml" @ContextConfiguration public class MyTest { - // class body... + // class body... }
          @@ -1410,10 +1415,10 @@ public class MyTest { references to annotated classes. @RunWith(SpringJUnit4ClassRunner.class) -// ApplicationContext will be loaded from AppConfig and TestConfig +// ApplicationContext will be loaded from AppConfig and TestConfig @ContextConfiguration(classes = {AppConfig.class, TestConfig.class}) public class MyTest { - // class body... + // class body... } If you omit the classes attribute from the @@ -1433,19 +1438,19 @@ public class MyTest { configuration class if desired. @RunWith(SpringJUnit4ClassRunner.class) -// ApplicationContext will be loaded from the -// static inner Config class +// ApplicationContext will be loaded from the +// static inner Config class @ContextConfiguration public class OrderServiceTest { @Configuration static class Config { - // this bean will be injected into the OrderServiceTest class + // this bean will be injected into the OrderServiceTest class @Bean public OrderService orderService() { OrderService orderService = new OrderServiceImpl(); - // set properties, etc. + // set properties, etc. return orderService; } } @@ -1455,7 +1460,7 @@ public class OrderServiceTest { @Test public void testOrderService() { - // test the orderService + // test the orderService } } @@ -1520,13 +1525,13 @@ public class OrderServiceTest { Spring's @Order annotation. @RunWith(SpringJUnit4ClassRunner.class) -// ApplicationContext will be loaded from TestConfig -// and initialized by TestAppCtxInitializer +// ApplicationContext will be loaded from TestConfig +// and initialized by TestAppCtxInitializer @ContextConfiguration( classes = TestConfig.class, initializers = TestAppCtxInitializer.class) public class MyTest { - // class body... + // class body... } It is also possible to omit the declaration of XML configuration @@ -1539,11 +1544,11 @@ public class MyTest { or configuration classes. @RunWith(SpringJUnit4ClassRunner.class) -// ApplicationContext will be initialized by EntireAppInitializer -// which presumably registers beans in the context +// ApplicationContext will be initialized by EntireAppInitializer +// which presumably registers beans in the context @ContextConfiguration(initializers = EntireAppInitializer.class) public class MyTest { - // class body... + // class body... } @@ -1585,18 +1590,18 @@ public class MyTest { "base-config.xml". @RunWith(SpringJUnit4ClassRunner.class) -// ApplicationContext will be loaded from "/base-config.xml" -// in the root of the classpath +// ApplicationContext will be loaded from "/base-config.xml" +// in the root of the classpath @ContextConfiguration("/base-config.xml") public class BaseTest { - // class body... + // class body... } -// ApplicationContext will be loaded from "/base-config.xml" and -// "/extended-config.xml" in the root of the classpath +// ApplicationContext will be loaded from "/base-config.xml" and +// "/extended-config.xml" in the root of the classpath @ContextConfiguration("/extended-config.xml") public class ExtendedTest extends BaseTest { - // class body... + // class body... } Similarly, in the following example that uses annotated classes, @@ -1609,16 +1614,16 @@ public class ExtendedTest extends BaseTest { BaseConfig. @RunWith(SpringJUnit4ClassRunner.class) -// ApplicationContext will be loaded from BaseConfig +// ApplicationContext will be loaded from BaseConfig @ContextConfiguration(classes = BaseConfig.class) public class BaseTest { - // class body... + // class body... } -// ApplicationContext will be loaded from BaseConfig and ExtendedConfig +// ApplicationContext will be loaded from BaseConfig and ExtendedConfig @ContextConfiguration(classes = ExtendedConfig.class) public class ExtendedTest extends BaseTest { - // class body... + // class body... } In the following example that uses context initializers, the @@ -1632,17 +1637,17 @@ public class ExtendedTest extends BaseTest { Spring's @Order annotation. @RunWith(SpringJUnit4ClassRunner.class) -// ApplicationContext will be initialized by BaseInitializer +// ApplicationContext will be initialized by BaseInitializer @ContextConfiguration(initializers=BaseInitializer.class) public class BaseTest { - // class body... + // class body... } -// ApplicationContext will be initialized by BaseInitializer -// and ExtendedInitializer +// ApplicationContext will be initialized by BaseInitializer +// and ExtendedInitializer @ContextConfiguration(initializers=ExtendedInitializer.class) public class ExtendedTest extends BaseTest { - // class body... + // class body... } @@ -1814,7 +1819,7 @@ public class TransferServiceTest { @Test public void testTransferService() { - // test the transferService + // test the transferService } } @@ -1849,7 +1854,113 @@ public class TransferServiceTest { @ContextConfiguration annotation. The body of the test class itself remains completely unchanged. - + It is often the case that a single set of profiles is used + across multiple test classes within a given project. Thus, to avoid + duplicate declarations of the + @ActiveProfiles annotation it is + possible to declare @ActiveProfiles + once on a base class, and subclasses will automatically inherit the + @ActiveProfiles configuration from the + base class. In the following example, the declaration of + @ActiveProfiles (as well as other + annotations) has been moved to an abstract superclass, + AbstractIntegrationTest. + + package com.bank.service; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration( + classes = { + TransferServiceConfig.class, + StandaloneDataConfig.class, + JndiDataConfig.class}) +@ActiveProfiles("dev") +public abstract class AbstractIntegrationTest { +} + + package com.bank.service; + +// "dev" profile inherited from superclass +public class TransferServiceTest extends AbstractIntegrationTest { + + @Autowired + private TransferService transferService; + + @Test + public void testTransferService() { + // test the transferService + } +} + + @ActiveProfiles also supports an + inheritProfiles attribute that can be used to + disable the inheritance of active profiles. + + package com.bank.service; + +// "dev" profile overridden with "production" +@ActiveProfiles(profiles = "production", inheritProfiles = false) +public class ProductionTransferServiceTest extends AbstractIntegrationTest { + // test body +} + + Furthermore, it is sometimes necessary to resolve active + profiles for tests programmatically instead of + declaratively — for example, based on: + + + + the current operating system + + + + whether tests are being executed on a continuous integration + build server + + + + the presence of certain environment variables + + + + the presence of custom class-level annotations + + + + etc. + + + + To resolve active bean definition profiles programmatically, + simply implement a custom + ActiveProfilesResolver and register it + via the resolver attribute of + @ActiveProfiles. The following example + demonstrates how to implement and register a custom + OperatingSystemActiveProfilesResolver. For + further information, refer to the respective Javadoc. + + package com.bank.service; + +// "dev" profile overridden programmatically via a custom resolver +@ActiveProfiles( + resolver = OperatingSystemActiveProfilesResolver.class, + inheritProfiles = false) +public class TransferServiceTest extends AbstractIntegrationTest { + // test body +} + + package com.bank.service.test; + +public class OperatingSystemActiveProfilesResolver implements ActiveProfilesResolver { + + @Override + String[] resolve(Class<?> testClass) { + String profile = ...; + // determine the value of profile based on the operating system + return new String[] {profile}; + } +}
          @@ -2416,11 +2527,11 @@ public class ExtendedTests extends BaseTests {} injection. @RunWith(SpringJUnit4ClassRunner.class) -// specifies the Spring configuration to load for this test fixture +// specifies the Spring configuration to load for this test fixture @ContextConfiguration("repository-config.xml") public class HibernateTitleRepositoryTests { - // this instance will be dependency injected by type + // this instance will be dependency injected by type @Autowired private HibernateTitleRepository titleRepository; @@ -2436,11 +2547,11 @@ public class HibernateTitleRepositoryTests { below. @RunWith(SpringJUnit4ClassRunner.class) -// specifies the Spring configuration to load for this test fixture +// specifies the Spring configuration to load for this test fixture @ContextConfiguration("repository-config.xml") public class HibernateTitleRepositoryTests { - // this instance will be dependency injected by type + // this instance will be dependency injected by type private HibernateTitleRepository titleRepository; @Autowired @@ -2490,7 +2601,7 @@ public class HibernateTitleRepositoryTests { specific target bean as follows, but make sure to delegate to the overridden method in the superclass as well. - // ... + // ... @Autowired @Override @@ -2498,7 +2609,7 @@ public class HibernateTitleRepositoryTests { super.setDataSource(dataSource); } -// ... +// ... The specified qualifier value indicates the specific DataSource bean to inject, narrowing @@ -2749,29 +2860,29 @@ public class FictitiousTransactionalTest { @BeforeTransaction public void verifyInitialDatabaseState() { - // logic to verify the initial state before a transaction is started + // logic to verify the initial state before a transaction is started } @Before public void setUpTestDataWithinTransaction() { - // set up test data within the transaction + // set up test data within the transaction } @Test - // overrides the class-level defaultRollback setting + // overrides the class-level defaultRollback setting @Rollback(true) public void modifyDatabaseWithinTransaction() { - // logic which uses the test data and modifies database state + // logic which uses the test data and modifies database state } @After public void tearDownWithinTransaction() { - // execute "tear down" logic within the transaction + // execute "tear down" logic within the transaction } @AfterTransaction public void verifyFinalDatabaseState() { - // logic to verify the final state after transaction has rolled back + // logic to verify the final state after transaction has rolled back } } @@ -2793,7 +2904,7 @@ public class FictitiousTransactionalTest { frameworks that maintain an in-memory unit of work. - // ... + // ... @Autowired private SessionFactory sessionFactory; @@ -2812,7 +2923,7 @@ public void updateWithSessionFlush() { sessionFactory.getCurrentSession().flush(); } -// ... +// ...
          @@ -2922,7 +3033,7 @@ public class SimpleTest { @Test public void testMethod() { - // execute test logic... + // execute test logic... } } @@ -3226,8 +3337,7 @@ public class MyWebTests { <bean id="accountService" class="org.mockito.Mockito" factory-method="mock"> <constructor-arg value="org.example.AccountService"/> -</bean> - +</bean> Then you can inject the mock service into the test in order set up and verify expectations: @@ -3274,26 +3384,22 @@ public class AccountTests { additional builder-style methods corresponding to properties of MockHttpServletRequest. For example: - mockMvc.perform(post("/hotels/{id}", 42).accept(MediaType.APPLICATION_JSON)); - + mockMvc.perform(post("/hotels/{id}", 42).accept(MediaType.APPLICATION_JSON)); In addition to all the HTTP methods, you can also perform file upload requests, which internally creates an instance of MockMultipartHttpServletRequest: - mockMvc.perform(fileUpload("/doc").file("a1", "ABC".getBytes("UTF-8"))); - + mockMvc.perform(fileUpload("/doc").file("a1", "ABC".getBytes("UTF-8"))); Query string parameters can be specified in the URI template: - mockMvc.perform(get("/hotels?foo={foo}", "bar")); - + mockMvc.perform(get("/hotels?foo={foo}", "bar")); Or by adding Servlet request parameters: - mockMvc.perform(get("/hotels").param("foo", "bar")); - + mockMvc.perform(get("/hotels").param("foo", "bar")); If application code relies on Servlet request parameters, and doesn't check the query string, as is most often the case, then it @@ -3308,8 +3414,7 @@ public class AccountTests { servletPath accordingly so that request mappings will work: - mockMvc.perform(get("/app/main/hotels/{id}").contextPath("/app").servletPath("/main")) - + mockMvc.perform(get("/app/main/hotels/{id}").contextPath("/app").servletPath("/main")) Looking at the above example, it would be cumbersome to set the contextPath and servletPath with every performed request. That's why @@ -3326,9 +3431,7 @@ public class AccountTests { .defaultRequest(get("/") .contextPath("/app").servletPath("/main") .accept(MediaType.APPLICATION_JSON).build(); - } - -} + } The above properties will apply to every request performed through the MockMvc. If the same property is @@ -3345,8 +3448,7 @@ public class AccountTests { .andExpect(..) after call to perform the request: - mockMvc.perform(get("/accounts/1")).andExpect(status().isOk()); - + mockMvc.perform(get("/accounts/1")).andExpect(status().isOk()); MockMvcResultMatchers.* defines a number of static members, some of which return types with additional methods, @@ -3369,8 +3471,7 @@ public class AccountTests { mockMvc.perform(post("/persons")) .andExpect(status().isOk()) - .andExpect(model().attributeHasErrors("person")); - + .andExpect(model().attributeHasErrors("person")); Many times when writing tests, it's useful to dump the result of the performed request. This can be done as follows, where @@ -3518,7 +3619,7 @@ mockServer.verify(); shown below: import static org.junit.Assert.assertEquals; -// import ... +// import ... @ContextConfiguration public abstract class AbstractClinicTests extends AbstractTransactionalJUnit4SpringContextTests { @@ -3535,10 +3636,10 @@ public abstract class AbstractClinicTests extends Abstract assertEquals("Leary", v1.getLastName()); assertEquals(1, v1.getNrOfSpecialties()); assertEquals("radiology", (v1.getSpecialties().get(0)).getName()); - // ... + // ... } - // ... + // ... } Notes: @@ -3608,8 +3709,7 @@ public abstract class AbstractClinicTests extends Abstract AbstractClinicTests-context.xml. @ContextConfiguration -public class HibernateClinicTests extends AbstractClinicTests { } - +public class HibernateClinicTests extends AbstractClinicTests { } In a large-scale application, the Spring configuration is often split across multiple files. Consequently, configuration locations are