Merge branch 'master' into websocket-stomp
This commit is contained in:
@@ -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=",
|
||||
|
||||
2
gradlew
vendored
2
gradlew
vendored
@@ -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"
|
||||
|
||||
2
gradlew.bat
vendored
2
gradlew.bat
vendored
@@ -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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author 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.
|
||||
*
|
||||
* <p>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,
|
||||
|
||||
@@ -163,4 +163,11 @@ public class AnnotatedClassCacheableService implements CacheableService<Object>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,4 +70,6 @@ public interface CacheableService<T> {
|
||||
T multiConditionalCacheAndEvict(Object arg1);
|
||||
|
||||
T multiUpdate(Object arg1);
|
||||
|
||||
TestEntity putRefersToResult(TestEntity arg1);
|
||||
}
|
||||
|
||||
@@ -171,4 +171,11 @@ public class DefaultCacheableService implements CacheableService<Long> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
56
spring-aspects/src/test/java/org/springframework/cache/config/TestEntity.java
vendored
Normal file
56
spring-aspects/src/test/java/org/springframework/cache/config/TestEntity.java
vendored
Normal file
@@ -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<50>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;
|
||||
}
|
||||
}
|
||||
@@ -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<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType) {
|
||||
Set<String> beanNames = new LinkedHashSet<String>(getBeanDefinitionCount());
|
||||
beanNames.addAll(Arrays.asList(getBeanDefinitionNames()));
|
||||
beanNames.addAll(Arrays.asList(getSingletonNames()));
|
||||
Map<String, Object> results = new LinkedHashMap<String, Object>();
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
|
||||
|
||||
<beans>
|
||||
|
||||
<bean id="abstractFactoryBean" class="org.springframework.beans.factory.FactoryBeanTests$AbstractFactoryBean" abstract="true"/>
|
||||
|
||||
</beans>
|
||||
@@ -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<Object> {
|
||||
|
||||
@@ -152,6 +168,7 @@ public final class FactoryBeanTests {
|
||||
}
|
||||
|
||||
|
||||
@Component
|
||||
public static class BetaFactoryBean implements FactoryBean<Object> {
|
||||
|
||||
private Beta beta;
|
||||
@@ -176,4 +193,7 @@ public final class FactoryBeanTests {
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class AbstractFactoryBean implements FactoryBean<Object> {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<Integer> input = new ArrayList<Integer>();
|
||||
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.
|
||||
*
|
||||
* <pre>{@code
|
||||
* Tests support for parameterized static {@code factory-method} declarations such as
|
||||
* Mockito's {@code mock()} method which has the following signature.
|
||||
*
|
||||
* <pre>
|
||||
* {@code
|
||||
* public static <T> T mock(Class<T> classToMock)
|
||||
* }</pre>
|
||||
*
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <pre>
|
||||
* {@code
|
||||
* public <T> T createMock(Class<T> toMock)
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* 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<String, Runnable> beans = bf.getBeansOfType(Runnable.class);
|
||||
assertEquals(1, beans.size());
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public static class NamedUrlList extends LinkedList<URL> {
|
||||
@@ -722,4 +755,25 @@ public class BeanFactoryGenericsTests {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pseudo-implementation of EasyMock's {@code MocksControl} class.
|
||||
*/
|
||||
public static class MocksControl {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T createMock(Class<T> 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!");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<CacheManager> cacheManagerBeans;
|
||||
|
||||
@Autowired(required=false)
|
||||
private Collection<CachingConfigurer> 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
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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 extends CacheOperation> 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 extends CacheOperation> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<Cache> getCaches(CacheOperation operation) {
|
||||
Set<String> cacheNames = operation.getCacheNames();
|
||||
Collection<Cache> caches = new ArrayList<Cache>(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<CacheOperation> 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<CachePutRequest> cachePutRequests = new ArrayList<CachePutRequest>();
|
||||
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<CacheOperationContext> 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<CacheOperationContext> contexts,
|
||||
Object result, Collection<CachePutRequest> 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<CacheOperationContext> 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<Cache> getCaches(CacheOperation operation) {
|
||||
Set<String> cacheNames = operation.getCacheNames();
|
||||
Collection<Cache> caches = new ArrayList<Cache>(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<Class<? extends CacheOperation>, CacheOperationContext> contexts =
|
||||
new LinkedMultiValueMap<Class<? extends CacheOperation>, 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<CacheOperation> cacheOp = getCacheOperationSource().getCacheOperations(method, targetClass);
|
||||
|
||||
// analyze caching information
|
||||
if (!CollectionUtils.isEmpty(cacheOp)) {
|
||||
Map<String, Collection<CacheOperationContext>> 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<CacheOperationContext, Object> 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<CacheOperationContext> evictions) {
|
||||
inspectCacheEvicts(evictions, true, ExpressionEvaluator.NO_RESULT);
|
||||
}
|
||||
|
||||
private void inspectAfterCacheEvicts(Collection<CacheOperationContext> evictions,
|
||||
Object result) {
|
||||
inspectCacheEvicts(evictions, false, result);
|
||||
}
|
||||
|
||||
private void inspectCacheEvicts(Collection<CacheOperationContext> 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<CacheOperationContext> cacheables) {
|
||||
Map<CacheOperationContext, Object> cUpdates = new LinkedHashMap<CacheOperationContext, Object>(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<? extends CacheOperation> 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<CacheOperationContext, Object> cUpdates;
|
||||
final boolean updateRequired;
|
||||
final Object retVal;
|
||||
|
||||
CacheStatus(Map<CacheOperationContext, Object> cUpdates, boolean updateRequired, Object retVal) {
|
||||
this.cUpdates = cUpdates;
|
||||
this.updateRequired = updateRequired;
|
||||
this.retVal = retVal;
|
||||
public Collection<CacheOperationContext> get(Class<? extends CacheOperation> operationClass) {
|
||||
return this.contexts.getOrDefault(operationClass, Collections.<CacheOperationContext> emptyList());
|
||||
}
|
||||
}
|
||||
|
||||
private Map<CacheOperationContext, Object> inspectCacheUpdates(Collection<CacheOperationContext> updates) {
|
||||
|
||||
Map<CacheOperationContext, Object> cUpdates = new LinkedHashMap<CacheOperationContext, Object>(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<CacheOperationContext, Object> updates, Object retVal) {
|
||||
for (Map.Entry<CacheOperationContext, Object> entry : updates.entrySet()) {
|
||||
CacheOperationContext operationContext = entry.getKey();
|
||||
if(operationContext.canPutToCache(retVal)) {
|
||||
for (Cache cache : operationContext.getCaches()) {
|
||||
cache.put(entry.getValue(), retVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Collection<CacheOperationContext>> createOperationContext(Collection<CacheOperation> cacheOp,
|
||||
Method method, Object[] args, Object target, Class<?> targetClass) {
|
||||
Map<String, Collection<CacheOperationContext>> map = new LinkedHashMap<String, Collection<CacheOperationContext>>(3);
|
||||
|
||||
Collection<CacheOperationContext> cacheables = new ArrayList<CacheOperationContext>();
|
||||
Collection<CacheOperationContext> evicts = new ArrayList<CacheOperationContext>();
|
||||
Collection<CacheOperationContext> updates = new ArrayList<CacheOperationContext>();
|
||||
|
||||
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<Cache> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ package org.springframework.cache.interceptor;
|
||||
public class CacheEvictOperation extends CacheOperation {
|
||||
|
||||
private boolean cacheWide = false;
|
||||
|
||||
private boolean beforeInvocation = false;
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<String> cacheNames = Collections.emptySet();
|
||||
|
||||
private String condition = "";
|
||||
|
||||
private String key = "";
|
||||
|
||||
private String name = "";
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -27,15 +27,25 @@ import org.springframework.cache.interceptor.KeyGenerator;
|
||||
* Uses the constant value {@value #NULL_PARAM_KEY} for any
|
||||
* {@code null} parameters given.
|
||||
*
|
||||
* <p>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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<String, Collection<CacheOperation>> nameMap = new LinkedHashMap<String, Collection<CacheOperation>>();
|
||||
|
||||
|
||||
/**
|
||||
* Set a name/attribute map, consisting of method names
|
||||
* (e.g. "myMethod") and CacheOperation instances
|
||||
|
||||
73
spring-context/src/main/java/org/springframework/cache/interceptor/SimpleKey.java
vendored
Normal file
73
spring-context/src/main/java/org/springframework/cache/interceptor/SimpleKey.java
vendored
Normal file
@@ -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) + "]";
|
||||
}
|
||||
}
|
||||
50
spring-context/src/main/java/org/springframework/cache/interceptor/SimpleKeyGenerator.java
vendored
Normal file
50
spring-context/src/main/java/org/springframework/cache/interceptor/SimpleKeyGenerator.java
vendored
Normal file
@@ -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.
|
||||
*
|
||||
* <p>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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -31,6 +31,7 @@ public class SimpleCacheManager extends AbstractCacheManager {
|
||||
|
||||
private Collection<? extends Cache> caches;
|
||||
|
||||
|
||||
/**
|
||||
* Specify the collection of Cache instances to use for this CacheManager.
|
||||
*/
|
||||
|
||||
@@ -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.
|
||||
* <p>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<? extends Annotation>... 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);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<TypeFilter> excludeFilters = new LinkedList<TypeFilter>();
|
||||
|
||||
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.
|
||||
* <p>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.
|
||||
* <p>The default implementation checks whether the class is concrete
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
* <p>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.
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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
|
||||
*/
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
@@ -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<String[]> getConditionClasses(AnnotatedTypeMetadata metadata) {
|
||||
MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(
|
||||
CONDITIONAL_ANNOTATION, true);
|
||||
Object values = attributes == null ? null : attributes.get("value");
|
||||
return (List<String[]>) (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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
* <p>A <em>condition</em> 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).
|
||||
*
|
||||
* <p>The {@code @Conditional} annotation may be used in any of the following ways:
|
||||
* <ul>
|
||||
|
||||
@@ -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<String[]> getConditionClasses(AnnotatedTypeMetadata metadata) {
|
||||
MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(
|
||||
CONDITIONAL_ANNOTATION, true);
|
||||
Object values = attributes == null ? null : attributes.get("value");
|
||||
return (List<String[]>) (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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<String, Class<? extends BeanDefinitionReader>> importedResources =
|
||||
new LinkedHashMap<String, Class<? extends BeanDefinitionReader>>();
|
||||
|
||||
private final Set<ImportBeanDefinitionRegistrar> importBeanDefinitionRegistrars =
|
||||
new LinkedHashSet<ImportBeanDefinitionRegistrar>();
|
||||
|
||||
|
||||
/**
|
||||
* 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<ImportBeanDefinitionRegistrar> getImportBeanDefinitionRegistrars() {
|
||||
return Collections.unmodifiableSet(importBeanDefinitionRegistrars);
|
||||
}
|
||||
|
||||
public Map<String, Class<? extends BeanDefinitionReader>> 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 &&
|
||||
|
||||
@@ -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<ConfigurationClass> 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<ImportBeanDefinitionRegistrar> 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<ConfigurationClass, Boolean> skipped = new HashMap<ConfigurationClass, Boolean>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.*;
|
||||
*
|
||||
* <p>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).
|
||||
*
|
||||
* <p>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<ConfigurationClass> configurationClasses = new LinkedHashSet<ConfigurationClass>();
|
||||
@@ -121,6 +121,8 @@ class ConfigurationClassParser {
|
||||
|
||||
private final List<DeferredImportSelectorHolder> deferredImportSelectors = new LinkedList<DeferredImportSelectorHolder>();
|
||||
|
||||
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<BeanDefinitionHolder> scannedBeanDefinitions =
|
||||
this.componentScanParser.parse(componentScan, metadata.getClassName());
|
||||
if (!conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
|
||||
Set<BeanDefinitionHolder> 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<Object> imports = new LinkedHashSet<Object>();
|
||||
Set<Object> visited = new LinkedHashSet<Object>();
|
||||
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<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
|
||||
for (String resource : resources) {
|
||||
@@ -255,34 +262,22 @@ class ConfigurationClassParser {
|
||||
}
|
||||
|
||||
// process individual @Bean methods
|
||||
Set<MethodMetadata> beanMethods = metadata.getAnnotatedMethods(Bean.class.getName());
|
||||
Set<MethodMetadata> 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<SourceClass> getImports(SourceClass sourceClass) throws IOException {
|
||||
Set<SourceClass> imports = new LinkedHashSet<SourceClass>();
|
||||
Set<SourceClass> visited = new LinkedHashSet<SourceClass>();
|
||||
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.
|
||||
* <p>For example, it is common for a {@code @Configuration} class to declare direct
|
||||
* <p>
|
||||
* 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<Object> imports, Set<Object> 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<String, Object> 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<String, Object> 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<SourceClass> imports,
|
||||
Set<SourceClass> 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<SourceClass> 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<SourceClass> 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<SourceClass> asSourceClasses(String[] classNamess)
|
||||
throws ClassNotFoundException, IOException {
|
||||
List<SourceClass> annotatedClasses = new ArrayList<SourceClass>();
|
||||
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<SourceClass> getMemberClasses() throws IOException {
|
||||
List<SourceClass> members = new ArrayList<SourceClass>();
|
||||
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<SourceClass> getAnnotations() throws ClassNotFoundException, IOException {
|
||||
Set<SourceClass> annotations = new LinkedHashSet<SourceClass>();
|
||||
for(String annotation : getMetadata().getAnnotationTypes()) {
|
||||
annotations.add(getRelated(annotation));
|
||||
}
|
||||
return annotations;
|
||||
}
|
||||
|
||||
public Collection<SourceClass> getAnnotationAttributes(String annotationType,
|
||||
String attribute) throws ClassNotFoundException, IOException {
|
||||
Map<String, Object> annotationAttributes = getMetadata().getAnnotationAttributes(
|
||||
annotationType, true);
|
||||
if (annotationAttributes == null
|
||||
|| !annotationAttributes.containsKey(attribute)) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
String[] classNames = (String[]) annotationAttributes.get(attribute);
|
||||
Set<SourceClass> rtn = new LinkedHashSet<SourceClass>();
|
||||
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}.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author 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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>At the time that the condition is evaluated all {@code @Configuration}s
|
||||
* will have been parsed.
|
||||
*/
|
||||
REGISTER_BEAN
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author 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}:
|
||||
* <ul>
|
||||
* <li>{@link org.springframework.context.EnvironmentAware}</li>
|
||||
* <li>{@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}
|
||||
* <li>{@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}
|
||||
* <li>{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}
|
||||
|
||||
@@ -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}:
|
||||
* <ul>
|
||||
* <li>{@link org.springframework.context.EnvironmentAware}</li>
|
||||
* <li>{@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}</li>
|
||||
* <li>{@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}</li>
|
||||
* <li>{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}</li>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author 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<Registry>, 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 + "]");
|
||||
}
|
||||
|
||||
@@ -83,7 +83,12 @@ public class MethodValidationPostProcessor extends AbstractAdvisingBeanPostProce
|
||||
* <p>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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.springframework.cache.annotation.Caching;
|
||||
public class AnnotatedClassCacheableService implements CacheableService<Object> {
|
||||
|
||||
private final AtomicLong counter = new AtomicLong();
|
||||
|
||||
public static final AtomicLong nullInvocations = new AtomicLong();
|
||||
|
||||
@Override
|
||||
@@ -164,4 +165,11 @@ public class AnnotatedClassCacheableService implements CacheableService<Object>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,4 +70,6 @@ public interface CacheableService<T> {
|
||||
T multiConditionalCacheAndEvict(Object arg1);
|
||||
|
||||
T multiUpdate(Object arg1);
|
||||
|
||||
TestEntity putRefersToResult(TestEntity arg1);
|
||||
}
|
||||
|
||||
@@ -170,4 +170,11 @@ public class DefaultCacheableService implements CacheableService<Long> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
56
spring-context/src/test/java/org/springframework/cache/config/TestEntity.java
vendored
Normal file
56
spring-context/src/test/java/org/springframework/cache/config/TestEntity.java
vendored
Normal file
@@ -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<50>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;
|
||||
}
|
||||
}
|
||||
89
spring-context/src/test/java/org/springframework/cache/interceptor/SimpleKeyGeneratorTests.java
vendored
Normal file
89
spring-context/src/test/java/org/springframework/cache/interceptor/SimpleKeyGeneratorTests.java
vendored
Normal file
@@ -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)));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author 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 {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author 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
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author 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) {
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
// /**
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
targetName=wrappedAssemblerOne
|
||||
logicName=logicTwo
|
||||
realLogicName=realLogic
|
||||
jedi=true
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
<cache:cache-evict method="multiConditionalCacheAndEvict" cache="secondary"/>
|
||||
<cache:cache-put method="multiUpdate" cache="primary"/>
|
||||
<cache:cache-put method="multiUpdate" cache="secondary"/>
|
||||
<cache:cache-put method="putRefersToResult" cache="primary" key="#result.id"/>
|
||||
</cache:caching>
|
||||
</cache:advice>
|
||||
|
||||
@@ -82,6 +83,7 @@
|
||||
<cache:cache-evict method="multiConditionalCacheAndEvict" cache="secondary"/>
|
||||
<cache:cache-put method="multiUpdate" cache="primary"/>
|
||||
<cache:cache-put method="multiUpdate" cache="secondary"/>
|
||||
<cache:cache-put method="putRefersToResult" cache="primary" key="#result.id"/>
|
||||
</cache:caching>
|
||||
</cache:advice>
|
||||
|
||||
|
||||
@@ -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.
|
||||
* <p>Note: Unlike {@link FileSystemResource}, when building relative resources
|
||||
* via {@link #createRelative}, the relative path will be built <i>underneath</i> 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.
|
||||
* <p>Note: Unlike {@link FileSystemResource}, when building relative resources
|
||||
* via {@link #createRelative}, the relative path will be built <i>underneath</i> 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.
|
||||
* <p>Note: Unlike {@link FileSystemResource}, when building relative resources
|
||||
* via {@link #createRelative}, the relative path will be built <i>underneath</i> 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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -42,6 +42,7 @@ import java.net.URL;
|
||||
* @see UrlResource
|
||||
* @see ByteArrayResource
|
||||
* @see InputStreamResource
|
||||
* @see PathResource
|
||||
*/
|
||||
public interface Resource extends InputStreamSource {
|
||||
|
||||
|
||||
@@ -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}.
|
||||
*
|
||||
* <p>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}.
|
||||
*
|
||||
* <h4>Rationale</h4>
|
||||
*
|
||||
* <p>Static methods from this class may be invoked from within XML
|
||||
* configuration files using the Spring Expression Language (SpEL) and the
|
||||
* following syntax.
|
||||
*
|
||||
* <pre><code><bean id="bean1" ... p:port="#{T(org.springframework.util.SocketUtils).findAvailableTcpPort(12000)}" /></code></pre>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* <pre><code><bean id="socketUtils" class="org.springframework.util.SocketUtils" />
|
||||
*
|
||||
*<bean id="bean1" ... p:port="#{socketUtils.findAvailableTcpPort(12000)}" />
|
||||
*
|
||||
*<bean id="bean2" ... p:port="#{socketUtils.findAvailableTcpPort(30000)}" /></code></pre>
|
||||
*/
|
||||
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<Integer> 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<Integer> 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<Integer> 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<Integer> 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<Integer> 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<Integer> availablePorts = new TreeSet<Integer>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author 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<TypeVariable, Type>()));
|
||||
Method intMessageMethod = findMethod(MyTypeWithMethods.class, "readIntegerInputMessage", MyInterfaceType.class);
|
||||
MethodParameter intMessageMethodParam = new MethodParameter(intMessageMethod, 0);
|
||||
assertEquals(MyInterfaceType.class,
|
||||
resolveType(intMessageMethodParam.getGenericParameterType(), new HashMap<TypeVariable, Type>()));
|
||||
|
||||
Method intArrMessageMethod = findMethod(MyTypeWithMethods.class, "readIntegerArrayInputMessage", MyInterfaceType[].class);
|
||||
MethodParameter intArrMessageMethodParam = new MethodParameter(intArrMessageMethod, 0);
|
||||
assertEquals(MyInterfaceType[].class,
|
||||
resolveType(intArrMessageMethodParam.getGenericParameterType(), new HashMap<TypeVariable, Type>()));
|
||||
Method intArrMessageMethod = findMethod(MyTypeWithMethods.class, "readIntegerArrayInputMessage",
|
||||
MyInterfaceType[].class);
|
||||
MethodParameter intArrMessageMethodParam = new MethodParameter(intArrMessageMethod, 0);
|
||||
assertEquals(MyInterfaceType[].class,
|
||||
resolveType(intArrMessageMethodParam.getGenericParameterType(), new HashMap<TypeVariable, Type>()));
|
||||
|
||||
Method genericArrMessageMethod = findMethod(MySimpleTypeWithMethods.class, "readGenericArrayInputMessage", Object[].class);
|
||||
MethodParameter genericArrMessageMethodParam = new MethodParameter(genericArrMessageMethod, 0);
|
||||
Map<TypeVariable, Type> 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<TypeVariable, Type> varMap = getTypeVariableMap(MySimpleTypeWithMethods.class);
|
||||
assertEquals(Integer[].class, resolveType(genericArrMessageMethodParam.getGenericParameterType(), varMap));
|
||||
}
|
||||
|
||||
|
||||
@@ -171,26 +176,44 @@ public class GenericTypeResolverTests {
|
||||
}
|
||||
|
||||
public static class MyTypeWithMethods<T> {
|
||||
public MyInterfaceType<Integer> integer() { return null; }
|
||||
public MySimpleInterfaceType string() { return null; }
|
||||
public Object object() { return null; }
|
||||
|
||||
public MyInterfaceType<Integer> 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> 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> 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> T createNamedMock(String name, Class<T> 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> T extractValueFrom(MyInterfaceType<T> myInterfaceType) {
|
||||
return null;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Integer> 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<Integer> ports = SocketUtils.findAvailableTcpPorts(numRequested);
|
||||
assertAvailablePorts(ports, numRequested, PORT_RANGE_MIN, PORT_RANGE_MAX);
|
||||
}
|
||||
|
||||
private void findAvailableTcpPorts(int numRequested, int minPort, int maxPort) {
|
||||
SortedSet<Integer> 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<Integer> ports = SocketUtils.findAvailableUdpPorts(numRequested);
|
||||
assertAvailablePorts(ports, numRequested, PORT_RANGE_MIN, PORT_RANGE_MAX);
|
||||
}
|
||||
|
||||
private void findAvailableUdpPorts(int numRequested, int minPort, int maxPort) {
|
||||
SortedSet<Integer> ports = SocketUtils.findAvailableUdpPorts(numRequested, minPort, maxPort);
|
||||
assertAvailablePorts(ports, numRequested, minPort, maxPort);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author 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.
|
||||
* <p>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;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author 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
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
* <p>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<PropertyAccessor> 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();
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author 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
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
* <p>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.
|
||||
* <p>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;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author 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
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author 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<TypeDescriptor> argumentTypes) throws AccessException;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author 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
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author 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:
|
||||
* <pre class="code">
|
||||
* Some literal text
|
||||
* Hello #{name.firstName}!
|
||||
* #{3 + 4}
|
||||
* </pre>
|
||||
*
|
||||
* @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() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author 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;
|
||||
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author 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:
|
||||
*
|
||||
* <pre class="code">
|
||||
* "Hello ${getName()}"</pre>
|
||||
*
|
||||
* 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()}"
|
||||
* </pre>
|
||||
*
|
||||
* 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> T getValue(EvaluationContext context, Class<T> expectedResultType) throws EvaluationException {
|
||||
Object value = getValue(context);
|
||||
return ExpressionUtils.convert(context, value, expectedResultType);
|
||||
return ExpressionUtils.convertTypedValue(context, new TypedValue(value), expectedResultType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getValue(Class<T> 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> T getValue(Object rootObject, Class<T> desiredResultType) throws EvaluationException {
|
||||
Object value = getValue(rootObject);
|
||||
return ExpressionUtils.convert(null, value, desiredResultType);
|
||||
return ExpressionUtils.convertTypedValue(null, new TypedValue(value), desiredResultType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getValue(EvaluationContext context, Object rootObject, Class<T> desiredResultType)
|
||||
throws EvaluationException {
|
||||
Object value = getValue(context,rootObject);
|
||||
return ExpressionUtils.convert(context, value, desiredResultType);
|
||||
return ExpressionUtils.convertTypedValue(context, new TypedValue(value), desiredResultType);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author 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> T convert(EvaluationContext context, Object value, Class<T> 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> T convertTypedValue(EvaluationContext context, TypedValue typedValue, Class<T> targetType) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author 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> T getValue(EvaluationContext context, Class<T> expectedResultType) throws EvaluationException {
|
||||
Object value = getValue(context);
|
||||
return ExpressionUtils.convert(context, value, expectedResultType);
|
||||
return ExpressionUtils.convertTypedValue(context, new TypedValue(value), expectedResultType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getValue(Class<T> 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> T getValue(Object rootObject, Class<T> 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> T getValue(EvaluationContext context, Object rootObject, Class<T> 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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author 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<Expression> expressions = new LinkedList<Expression>();
|
||||
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<suffix.length() && pos<expressionString.length();i++) {
|
||||
if (expressionString.charAt(pos++)!=suffix.charAt(suffixPosition++)) {
|
||||
for (int i = 0; i < suffix.length() && pos < expressionString.length(); i++) {
|
||||
if (expressionString.charAt(pos++) != suffix.charAt(suffixPosition++)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (suffixPosition!=suffix.length()) {
|
||||
if (suffixPosition != suffix.length()) {
|
||||
// the expressionString ran out before the suffix could entirely be found
|
||||
return false;
|
||||
}
|
||||
@@ -167,58 +190,73 @@ public abstract class TemplateAwareExpressionParser implements ExpressionParser
|
||||
}
|
||||
|
||||
/**
|
||||
* Copes with nesting, for example '${...${...}}' where the correct end for the first ${ is the final }.
|
||||
*
|
||||
* Copes with nesting, for example '${...${...}}' where the correct end for the first
|
||||
* ${ is the final }.
|
||||
* @param prefix the prefix
|
||||
* @param suffix the suffix
|
||||
* @param expressionString the expression string
|
||||
* @param afterPrefixIndex the most recently found prefix location for which the matching end suffix is being sought
|
||||
* @param afterPrefixIndex the most recently found prefix location for which the
|
||||
* matching end suffix is being sought
|
||||
* @return the position of the correct matching nextSuffix or -1 if none can be found
|
||||
*/
|
||||
private int skipToCorrectEndSuffix(String prefix, String suffix, String expressionString, int afterPrefixIndex) throws ParseException {
|
||||
private int skipToCorrectEndSuffix(String prefix, String suffix,
|
||||
String expressionString, int afterPrefixIndex) throws ParseException {
|
||||
// Chew on the expression text - relying on the rules:
|
||||
// brackets must be in pairs: () [] {}
|
||||
// string literals are "..." or '...' and these may contain unmatched brackets
|
||||
int pos = afterPrefixIndex;
|
||||
int maxlen = expressionString.length();
|
||||
int nextSuffix = expressionString.indexOf(suffix,afterPrefixIndex);
|
||||
if (nextSuffix ==-1 ) {
|
||||
int nextSuffix = expressionString.indexOf(suffix, afterPrefixIndex);
|
||||
if (nextSuffix == -1) {
|
||||
return -1; // the suffix is missing
|
||||
}
|
||||
Stack<Bracket> stack = new Stack<Bracket>();
|
||||
while (pos<maxlen) {
|
||||
if (isSuffixHere(expressionString,pos,suffix) && stack.isEmpty()) {
|
||||
while (pos < maxlen) {
|
||||
if (isSuffixHere(expressionString, pos, suffix) && stack.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
char ch = expressionString.charAt(pos);
|
||||
switch (ch) {
|
||||
case '{': case '[': case '(':
|
||||
stack.push(new Bracket(ch,pos));
|
||||
break;
|
||||
case '}':case ']':case ')':
|
||||
if (stack.isEmpty()) {
|
||||
throw new ParseException(expressionString, pos, "Found closing '"+ch+"' at position "+pos+" without an opening '"+Bracket.theOpenBracketFor(ch)+"'");
|
||||
}
|
||||
Bracket p = stack.pop();
|
||||
if (!p.compatibleWithCloseBracket(ch)) {
|
||||
throw new ParseException(expressionString, pos, "Found closing '"+ch+"' at position "+pos+" but most recent opening is '"+p.bracket+"' at position "+p.pos);
|
||||
}
|
||||
break;
|
||||
case '\'':
|
||||
case '"':
|
||||
// jump to the end of the literal
|
||||
int endLiteral = expressionString.indexOf(ch,pos+1);
|
||||
if (endLiteral==-1) {
|
||||
throw new ParseException(expressionString, pos, "Found non terminating string literal starting at position "+pos);
|
||||
}
|
||||
pos=endLiteral;
|
||||
break;
|
||||
case '{':
|
||||
case '[':
|
||||
case '(':
|
||||
stack.push(new Bracket(ch, pos));
|
||||
break;
|
||||
case '}':
|
||||
case ']':
|
||||
case ')':
|
||||
if (stack.isEmpty()) {
|
||||
throw new ParseException(expressionString, pos, "Found closing '"
|
||||
+ ch + "' at position " + pos + " without an opening '"
|
||||
+ Bracket.theOpenBracketFor(ch) + "'");
|
||||
}
|
||||
Bracket p = stack.pop();
|
||||
if (!p.compatibleWithCloseBracket(ch)) {
|
||||
throw new ParseException(expressionString, pos, "Found closing '"
|
||||
+ ch + "' at position " + pos
|
||||
+ " but most recent opening is '" + p.bracket
|
||||
+ "' at position " + p.pos);
|
||||
}
|
||||
break;
|
||||
case '\'':
|
||||
case '"':
|
||||
// jump to the end of the literal
|
||||
int endLiteral = expressionString.indexOf(ch, pos + 1);
|
||||
if (endLiteral == -1) {
|
||||
throw new ParseException(expressionString, pos,
|
||||
"Found non terminating string literal starting at position "
|
||||
+ pos);
|
||||
}
|
||||
pos = endLiteral;
|
||||
break;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
if (!stack.isEmpty()) {
|
||||
Bracket p = stack.pop();
|
||||
throw new ParseException(expressionString, p.pos, "Missing closing '"+Bracket.theCloseBracketFor(p.bracket)+"' for '"+p.bracket+"' at position "+p.pos);
|
||||
throw new ParseException(expressionString, p.pos, "Missing closing '"
|
||||
+ Bracket.theCloseBracketFor(p.bracket) + "' for '" + p.bracket
|
||||
+ "' at position " + p.pos);
|
||||
}
|
||||
if (!isSuffixHere(expressionString, pos, suffix)) {
|
||||
return -1;
|
||||
@@ -226,43 +264,49 @@ public abstract class TemplateAwareExpressionParser implements ExpressionParser
|
||||
return pos;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This captures a type of bracket and the position in which it occurs in the expression. The positional
|
||||
* information is used if an error has to be reported because the related end bracket cannot be found.
|
||||
* Bracket is used to describe: square brackets [] round brackets () and curly brackets {}
|
||||
* This captures a type of bracket and the position in which it occurs in the
|
||||
* expression. The positional information is used if an error has to be reported
|
||||
* because the related end bracket cannot be found. Bracket is used to describe:
|
||||
* square brackets [] round brackets () and curly brackets {}
|
||||
*/
|
||||
private static class Bracket {
|
||||
|
||||
char bracket;
|
||||
|
||||
int pos;
|
||||
|
||||
Bracket(char bracket,int pos) {
|
||||
Bracket(char bracket, int pos) {
|
||||
this.bracket = bracket;
|
||||
this.pos = pos;
|
||||
}
|
||||
|
||||
boolean compatibleWithCloseBracket(char closeBracket) {
|
||||
if (bracket=='{') {
|
||||
return closeBracket=='}';
|
||||
} else if (bracket=='[') {
|
||||
return closeBracket==']';
|
||||
if (this.bracket == '{') {
|
||||
return closeBracket == '}';
|
||||
}
|
||||
return closeBracket==')';
|
||||
else if (this.bracket == '[') {
|
||||
return closeBracket == ']';
|
||||
}
|
||||
return closeBracket == ')';
|
||||
}
|
||||
|
||||
static char theOpenBracketFor(char closeBracket) {
|
||||
if (closeBracket=='}') {
|
||||
if (closeBracket == '}') {
|
||||
return '{';
|
||||
} else if (closeBracket==']') {
|
||||
}
|
||||
else if (closeBracket == ']') {
|
||||
return '[';
|
||||
}
|
||||
return '(';
|
||||
}
|
||||
|
||||
static char theCloseBracketFor(char openBracket) {
|
||||
if (openBracket=='{') {
|
||||
if (openBracket == '{') {
|
||||
return '}';
|
||||
} else if (openBracket=='[') {
|
||||
}
|
||||
else if (openBracket == '[') {
|
||||
return ']';
|
||||
}
|
||||
return ')';
|
||||
@@ -276,8 +320,7 @@ public abstract class TemplateAwareExpressionParser implements ExpressionParser
|
||||
* @return an evaluator for the parsed expression
|
||||
* @throws ParseException an exception occurred during parsing
|
||||
*/
|
||||
protected abstract Expression doParseExpression(String expressionString, ParserContext context)
|
||||
throws ParseException;
|
||||
protected abstract Expression doParseExpression(String expressionString,
|
||||
ParserContext context) throws ParseException;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author 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,8 +19,8 @@ package org.springframework.expression.common;
|
||||
import org.springframework.expression.ParserContext;
|
||||
|
||||
/**
|
||||
* Configurable {@link ParserContext} implementation for template parsing.
|
||||
* Expects the expression prefix and suffix as constructor arguments.
|
||||
* Configurable {@link ParserContext} implementation for template parsing. Expects the
|
||||
* expression prefix and suffix as constructor arguments.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -32,12 +32,15 @@ import org.springframework.expression.TypeConverter;
|
||||
import org.springframework.expression.TypedValue;
|
||||
|
||||
/**
|
||||
* An ExpressionState is for maintaining per-expression-evaluation state, any changes to it are not seen by other
|
||||
* expressions but it gives a place to hold local variables and for component expressions in a compound expression to
|
||||
* communicate state. This is in contrast to the EvaluationContext, which is shared amongst expression evaluations, and
|
||||
* any changes to it will be seen by other expressions or any code that chooses to ask questions of the context.
|
||||
* An ExpressionState is for maintaining per-expression-evaluation state, any changes to
|
||||
* it are not seen by other expressions but it gives a place to hold local variables and
|
||||
* for component expressions in a compound expression to communicate state. This is in
|
||||
* contrast to the EvaluationContext, which is shared amongst expression evaluations, and
|
||||
* any changes to it will be seen by other expressions or any code that chooses to ask
|
||||
* questions of the context.
|
||||
*
|
||||
* <p>It also acts as a place for to define common utility routines that the various Ast nodes might need.
|
||||
* <p>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<String, Object> 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);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>When a message is formatted, it will have this kind of form
|
||||
*
|
||||
* <pre class="code">
|
||||
* EL1004E: (pos 34): Type cannot be found 'String'
|
||||
* </pre>
|
||||
*
|
||||
* </code> 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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user