Merge branch 'master' into websocket-stomp

This commit is contained in:
Rossen Stoyanchev
2013-06-27 15:58:12 -04:00
307 changed files with 8375 additions and 4414 deletions

View File

@@ -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
View File

@@ -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
View File

@@ -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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author 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,

View File

@@ -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;
}
}

View File

@@ -70,4 +70,6 @@ public interface CacheableService<T> {
T multiConditionalCacheAndEvict(Object arg1);
T multiUpdate(Object arg1);
TestEntity putRefersToResult(TestEntity arg1);
}

View File

@@ -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;
}
}

View 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;
}
}

View File

@@ -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));
}

View File

@@ -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);

View File

@@ -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>

View File

@@ -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> {
}
}

View File

@@ -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!");
}
});
}
}
}

View File

@@ -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

View File

@@ -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.
*

View File

@@ -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;
}
}
}

View File

@@ -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) {

View File

@@ -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);
}
}
}
}
}

View File

@@ -25,6 +25,7 @@ package org.springframework.cache.interceptor;
public class CacheEvictOperation extends CacheOperation {
private boolean cacheWide = false;
private boolean beforeInvocation = false;

View File

@@ -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;
}
}
}

View File

@@ -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 = "";

View File

@@ -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);
}
/**

View File

@@ -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

View File

@@ -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) {

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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) + "]";
}
}

View 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);
}
}

View File

@@ -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.
*/

View File

@@ -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);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author 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);
}
/**

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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
*/

View File

@@ -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();
}

View File

@@ -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;
}
}
}

View File

@@ -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>

View File

@@ -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());
}
}
}

View File

@@ -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 &&

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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}.
*/

View File

@@ -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;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author 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());
}

View File

@@ -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
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author 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}

View File

@@ -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>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author 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 + "]");
}

View File

@@ -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;
}
}
/**

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -70,4 +70,6 @@ public interface CacheableService<T> {
T multiConditionalCacheAndEvict(Object arg1);
T multiUpdate(Object arg1);
TestEntity putRefersToResult(TestEntity arg1);
}

View File

@@ -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;
}
}

View 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;
}
}

View 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)));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author 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 {
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author 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

View File

@@ -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 {
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author 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) {
}

View File

@@ -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

View File

@@ -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";
}
}

View File

@@ -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());
}
// /**

View File

@@ -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 {
}
}

View File

@@ -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));
}
}

View File

@@ -1,3 +1,4 @@
targetName=wrappedAssemblerOne
logicName=logicTwo
realLogicName=realLogic
jedi=true

View File

@@ -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>

View File

@@ -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();
}
}

View File

@@ -42,6 +42,7 @@ import java.net.URL;
* @see UrlResource
* @see ByteArrayResource
* @see InputStreamResource
* @see PathResource
*/
public interface Resource extends InputStreamSource {

View File

@@ -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 &mdash; 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>&lt;bean id="bean1" ... p:port="#{T(org.springframework.util.SocketUtils).findAvailableTcpPort(12000)}" /&gt;</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>&lt;bean id="socketUtils" class="org.springframework.util.SocketUtils" /&gt;
*
*&lt;bean id="bean1" ... p:port="#{socketUtils.findAvailableTcpPort(12000)}" /&gt;
*
*&lt;bean id="bean2" ... p:port="#{socketUtils.findAvailableTcpPort(30000)}" /&gt;</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;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author 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;

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author 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;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author 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

View File

@@ -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();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author 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;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author 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

View File

@@ -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;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author 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
*/

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author 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;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author 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
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author 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() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author 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;

View File

@@ -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&lt;Integer&gt; 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&lt;Integer&gt; 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
*/

View File

@@ -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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author 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">
* &quot;Hello ${getName()}&quot;</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.
*
* &quot;Hello ${getName()}&quot;
* </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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author 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) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author 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");
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author 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;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author 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);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author 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;
}
}

View File

@@ -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();
}

View File

@@ -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);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author 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