diff --git a/build.gradle b/build.gradle index 9ac71e790a..bf2977c699 100644 --- a/build.gradle +++ b/build.gradle @@ -817,6 +817,7 @@ configure(rootProject) { options.author = true options.header = rootProject.description options.overview = "src/api/overview.html" + options.stylesheetFile = file("src/api/stylesheet.css") options.splitIndex = true options.links(project.ext.javadocLinks) diff --git a/spring-aspects/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java b/spring-aspects/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java index 5e64cb7fcc..b810805179 100644 --- a/spring-aspects/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java +++ b/spring-aspects/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import org.springframework.cache.annotation.Caching; /** * @author Costin Leau + * @author Phillip Webb */ @Cacheable("default") public class AnnotatedClassCacheableService implements CacheableService { @@ -40,6 +41,10 @@ public class AnnotatedClassCacheableService implements CacheableService return null; } + public Object unless(int arg) { + return arg; + } + @CacheEvict("default") public void invalidate(Object arg1) { } diff --git a/spring-aspects/src/test/java/org/springframework/cache/config/CacheableService.java b/spring-aspects/src/test/java/org/springframework/cache/config/CacheableService.java index 09ad7df998..98c1da1e03 100644 --- a/spring-aspects/src/test/java/org/springframework/cache/config/CacheableService.java +++ b/spring-aspects/src/test/java/org/springframework/cache/config/CacheableService.java @@ -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,6 +20,7 @@ package org.springframework.cache.config; * Basic service interface. * * @author Costin Leau + * @author Phillip Webb */ public interface CacheableService { @@ -39,6 +40,8 @@ public interface CacheableService { T conditional(int field); + T unless(int arg); + T key(Object arg1, Object arg2); T name(Object arg1); diff --git a/spring-aspects/src/test/java/org/springframework/cache/config/DefaultCacheableService.java b/spring-aspects/src/test/java/org/springframework/cache/config/DefaultCacheableService.java index 6d83d91c02..d29d43894e 100644 --- a/spring-aspects/src/test/java/org/springframework/cache/config/DefaultCacheableService.java +++ b/spring-aspects/src/test/java/org/springframework/cache/config/DefaultCacheableService.java @@ -27,6 +27,7 @@ import org.springframework.cache.annotation.Caching; * Simple cacheable service * * @author Costin Leau + * @author Phillip Webb */ public class DefaultCacheableService implements CacheableService { @@ -78,6 +79,12 @@ public class DefaultCacheableService implements CacheableService { return counter.getAndIncrement(); } + @Override + @Cacheable(value = "default", unless = "#result > 10") + public Long unless(int arg) { + return (long) arg; + } + @Override @Cacheable(value = "default", key = "#p0") public Long key(Object arg1, Object arg2) { diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/CachePut.java b/spring-context/src/main/java/org/springframework/cache/annotation/CachePut.java index 0cc7f883f1..3ab2f52b0a 100644 --- a/spring-context/src/main/java/org/springframework/cache/annotation/CachePut.java +++ b/spring-context/src/main/java/org/springframework/cache/annotation/CachePut.java @@ -1,5 +1,5 @@ /* - * Copyright 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. @@ -32,6 +32,7 @@ import org.springframework.cache.Cache; * always causes the method to be invoked and its result to be placed into the cache. * * @author Costin Leau + * @author Phillip Webb * @since 3.1 */ @Target({ ElementType.METHOD, ElementType.TYPE }) @@ -58,4 +59,13 @@ public @interface CachePut { *

Default is "", meaning the method result is always cached. */ String condition() default ""; + + /** + * Spring Expression Language (SpEL) attribute used to veto the cache update. + *

Unlike {@link #condition()}, this expression is evaluated after the method + * has been called and can therefore refer to the {@code result}. Default is "", + * meaning that caching is never vetoed. + * @since 3.2 + */ + String unless() default ""; } diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/Cacheable.java b/spring-context/src/main/java/org/springframework/cache/annotation/Cacheable.java index 716b840ad0..1ab8df5762 100644 --- a/spring-context/src/main/java/org/springframework/cache/annotation/Cacheable.java +++ b/spring-context/src/main/java/org/springframework/cache/annotation/Cacheable.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,7 @@ import java.lang.annotation.Target; * returned instance is used as the cache value. * * @author Costin Leau + * @author Phillip Webb * @since 3.1 */ @Target({ElementType.METHOD, ElementType.TYPE}) @@ -56,4 +57,13 @@ public @interface Cacheable { *

Default is "", meaning the method is always cached. */ String condition() default ""; + + /** + * Spring Expression Language (SpEL) attribute used to veto method caching. + *

Unlike {@link #condition()}, this expression is evaluated after the method + * has been called and can therefore refer to the {@code result}. Default is "", + * meaning that caching is never vetoed. + * @since 3.2 + */ + String unless() default ""; } diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/SpringCacheAnnotationParser.java b/spring-context/src/main/java/org/springframework/cache/annotation/SpringCacheAnnotationParser.java index 9114dfc633..d8c7575ce7 100644 --- a/spring-context/src/main/java/org/springframework/cache/annotation/SpringCacheAnnotationParser.java +++ b/spring-context/src/main/java/org/springframework/cache/annotation/SpringCacheAnnotationParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,7 @@ import org.springframework.util.ObjectUtils; * @author Costin Leau * @author Juergen Hoeller * @author Chris Beams + * @author Phillip Webb * @since 3.1 */ @SuppressWarnings("serial") @@ -82,6 +83,7 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria CacheableOperation cuo = new CacheableOperation(); cuo.setCacheNames(caching.value()); cuo.setCondition(caching.condition()); + cuo.setUnless(caching.unless()); cuo.setKey(caching.key()); cuo.setName(ae.toString()); return cuo; @@ -102,6 +104,7 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria CachePutOperation cuo = new CachePutOperation(); cuo.setCacheNames(caching.value()); cuo.setCondition(caching.condition()); + cuo.setUnless(caching.unless()); cuo.setKey(caching.key()); cuo.setName(ae.toString()); return cuo; diff --git a/spring-context/src/main/java/org/springframework/cache/config/CacheAdviceParser.java b/spring-context/src/main/java/org/springframework/cache/config/CacheAdviceParser.java index a1206c576f..5092f474c7 100644 --- a/spring-context/src/main/java/org/springframework/cache/config/CacheAdviceParser.java +++ b/spring-context/src/main/java/org/springframework/cache/config/CacheAdviceParser.java @@ -1,5 +1,5 @@ /* - * Copyright 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. @@ -44,6 +44,7 @@ import org.w3c.dom.Element; * BeanDefinitionParser} for the {@code } tag. * * @author Costin Leau + * @author Phillip Webb */ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser { @@ -54,7 +55,9 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser { */ private static class Props { - private String key, condition, method; + private String key; + private String condition; + private String method; private String[] caches = null; Props(Element root) { @@ -70,13 +73,9 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser { T merge(Element element, ReaderContext readerCtx, T op) { String cache = element.getAttribute("cache"); - String k = element.getAttribute("key"); - String c = element.getAttribute("condition"); - - String[] localCaches = caches; - String localKey = key, localCondition = condition; // sanity check + String[] localCaches = caches; if (StringUtils.hasText(cache)) { localCaches = StringUtils.commaDelimitedListToStringArray(cache.trim()); } else { @@ -84,17 +83,10 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser { readerCtx.error("No cache specified specified for " + element.getNodeName(), element); } } - - if (StringUtils.hasText(k)) { - localKey = k.trim(); - } - - if (StringUtils.hasText(c)) { - localCondition = c.trim(); - } op.setCacheNames(localCaches); - op.setKey(localKey); - op.setCondition(localCondition); + + op.setKey(getAttributeValue(element, "key", this.key)); + op.setCondition(getAttributeValue(element, "condition", this.condition)); return op; } @@ -165,7 +157,8 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser { String name = prop.merge(opElement, parserContext.getReaderContext()); TypedStringValue nameHolder = new TypedStringValue(name); nameHolder.setSource(parserContext.extractSource(opElement)); - CacheOperation op = prop.merge(opElement, parserContext.getReaderContext(), new CacheableOperation()); + CacheableOperation op = prop.merge(opElement, parserContext.getReaderContext(), new CacheableOperation()); + op.setUnless(getAttributeValue(opElement, "unless", "")); Collection col = cacheOpMap.get(nameHolder); if (col == null) { @@ -207,7 +200,8 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser { String name = prop.merge(opElement, parserContext.getReaderContext()); TypedStringValue nameHolder = new TypedStringValue(name); nameHolder.setSource(parserContext.extractSource(opElement)); - CacheOperation op = prop.merge(opElement, parserContext.getReaderContext(), new CachePutOperation()); + CachePutOperation op = prop.merge(opElement, parserContext.getReaderContext(), new CachePutOperation()); + op.setUnless(getAttributeValue(opElement, "unless", "")); Collection col = cacheOpMap.get(nameHolder); if (col == null) { @@ -222,4 +216,14 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser { attributeSourceDefinition.getPropertyValues().add("nameMap", cacheOpMap); return attributeSourceDefinition; } + + + private static String getAttributeValue(Element element, String attributeName, String defaultValue) { + String attribute = element.getAttribute(attributeName); + if(StringUtils.hasText(attribute)) { + return attribute.trim(); + } + return defaultValue; + } + } diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java index 9e5162d6e3..8e0b36889d 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java @@ -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. @@ -56,6 +56,7 @@ import org.springframework.util.StringUtils; * @author Costin Leau * @author Juergen Hoeller * @author Chris Beams + * @author Phillip Webb * @since 3.1 */ public abstract class CacheAspectSupport implements InitializingBean { @@ -212,7 +213,7 @@ public abstract class CacheAspectSupport implements InitializingBean { retVal = invoker.invoke(); - inspectAfterCacheEvicts(ops.get(EVICT)); + inspectAfterCacheEvicts(ops.get(EVICT), retVal); if (!updates.isEmpty()) { update(updates, retVal); @@ -225,14 +226,16 @@ public abstract class CacheAspectSupport implements InitializingBean { } private void inspectBeforeCacheEvicts(Collection evictions) { - inspectCacheEvicts(evictions, true); + inspectCacheEvicts(evictions, true, ExpressionEvaluator.NO_RESULT); } - private void inspectAfterCacheEvicts(Collection evictions) { - inspectCacheEvicts(evictions, false); + private void inspectAfterCacheEvicts(Collection evictions, + Object result) { + inspectCacheEvicts(evictions, false, result); } - private void inspectCacheEvicts(Collection evictions, boolean beforeInvocation) { + private void inspectCacheEvicts(Collection evictions, + boolean beforeInvocation, Object result) { if (!evictions.isEmpty()) { @@ -242,7 +245,7 @@ public abstract class CacheAspectSupport implements InitializingBean { CacheEvictOperation evictOp = (CacheEvictOperation) context.operation; if (beforeInvocation == evictOp.isBeforeInvocation()) { - if (context.isConditionPassing()) { + if (context.isConditionPassing(result)) { // for each cache // lazy key initialization Object key = null; @@ -278,7 +281,7 @@ public abstract class CacheAspectSupport implements InitializingBean { private CacheStatus inspectCacheables(Collection cacheables) { Map cUpdates = new LinkedHashMap(cacheables.size()); - boolean updateRequire = false; + boolean updateRequired = false; Object retVal = null; if (!cacheables.isEmpty()) { @@ -305,7 +308,7 @@ public abstract class CacheAspectSupport implements InitializingBean { boolean localCacheHit = false; // check whether the cache needs to be inspected or not (the method will be invoked anyway) - if (!updateRequire) { + if (!updateRequired) { for (Cache cache : context.getCaches()) { Cache.ValueWrapper wrapper = cache.get(key); if (wrapper != null) { @@ -317,7 +320,7 @@ public abstract class CacheAspectSupport implements InitializingBean { } if (!localCacheHit) { - updateRequire = true; + updateRequired = true; } } else { @@ -329,7 +332,7 @@ public abstract class CacheAspectSupport implements InitializingBean { // return a status only if at least on cacheable matched if (atLeastOnePassed) { - return new CacheStatus(cUpdates, updateRequire, retVal); + return new CacheStatus(cUpdates, updateRequired, retVal); } } @@ -386,8 +389,11 @@ public abstract class CacheAspectSupport implements InitializingBean { private void update(Map updates, Object retVal) { for (Map.Entry entry : updates.entrySet()) { - for (Cache cache : entry.getKey().getCaches()) { - cache.put(entry.getValue(), retVal); + CacheOperationContext operationContext = entry.getKey(); + if(operationContext.canPutToCache(retVal)) { + for (Cache cache : operationContext.getCaches()) { + cache.put(entry.getValue(), retVal); + } } } } @@ -427,30 +433,49 @@ public abstract class CacheAspectSupport implements InitializingBean { private final CacheOperation operation; - private final Collection caches; - - private final Object target; - private final Method method; private final Object[] args; - // context passed around to avoid multiple creations - private final EvaluationContext evalContext; + private final Object target; + + private final Class targetClass; + + private final Collection caches; public CacheOperationContext(CacheOperation operation, Method method, Object[] args, Object target, Class targetClass) { this.operation = operation; - this.caches = CacheAspectSupport.this.getCaches(operation); - this.target = target; this.method = method; this.args = args; - - this.evalContext = evaluator.createEvaluationContext(caches, method, args, target, targetClass); + this.target = target; + this.targetClass = targetClass; + 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())) { - return evaluator.condition(this.operation.getCondition(), this.method, this.evalContext); + EvaluationContext evaluationContext = createEvaluationContext(result); + return evaluator.condition(this.operation.getCondition(), this.method, + evaluationContext); + } + return true; + } + + protected boolean canPutToCache(Object value) { + String unless = ""; + if (this.operation instanceof CacheableOperation) { + unless = ((CacheableOperation) this.operation).getUnless(); + } + else if (this.operation instanceof CachePutOperation) { + unless = ((CachePutOperation) this.operation).getUnless(); + } + if(StringUtils.hasText(unless)) { + EvaluationContext evaluationContext = createEvaluationContext(value); + return !evaluator.unless(unless, this.method, evaluationContext); } return true; } @@ -461,11 +486,17 @@ public abstract class CacheAspectSupport implements InitializingBean { */ protected Object generateKey() { if (StringUtils.hasText(this.operation.getKey())) { - return evaluator.key(this.operation.getKey(), this.method, this.evalContext); + EvaluationContext evaluationContext = createEvaluationContext(ExpressionEvaluator.NO_RESULT); + return evaluator.key(this.operation.getKey(), this.method, evaluationContext); } return keyGenerator.generate(this.target, this.method, this.args); } + private EvaluationContext createEvaluationContext(Object result) { + return evaluator.createEvaluationContext(this.caches, this.method, this.args, + this.target, this.targetClass, result); + } + protected Collection getCaches() { return this.caches; } diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheEvictOperation.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheEvictOperation.java index 62186d8966..f671c84f75 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheEvictOperation.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheEvictOperation.java @@ -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. @@ -27,6 +27,7 @@ public class CacheEvictOperation extends CacheOperation { private boolean cacheWide = false; private boolean beforeInvocation = false; + public void setCacheWide(boolean cacheWide) { this.cacheWide = cacheWide; } diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperation.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperation.java index cd8a40ceb2..46d84590e8 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperation.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperation.java @@ -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. @@ -119,10 +119,10 @@ public abstract class CacheOperation { result.append(this.name); result.append("] caches="); result.append(this.cacheNames); - result.append(" | condition='"); - result.append(this.condition); - result.append("' | key='"); + result.append(" | key='"); result.append(this.key); + result.append("' | condition='"); + result.append(this.condition); result.append("'"); return result; } diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CachePutOperation.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CachePutOperation.java index edb84ba156..e6a61b0adf 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/CachePutOperation.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CachePutOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,8 +20,28 @@ package org.springframework.cache.interceptor; * Class describing a cache 'put' operation. * * @author Costin Leau + * @author Phillip Webb * @since 3.1 */ public class CachePutOperation extends CacheOperation { + private String unless; + + + public String getUnless() { + return unless; + } + + public void setUnless(String unless) { + this.unless = unless; + } + + @Override + protected StringBuilder getOperationDescription() { + StringBuilder sb = super.getOperationDescription(); + sb.append(" | unless='"); + sb.append(this.unless); + sb.append("'"); + return sb; + } } diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheableOperation.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheableOperation.java index 23c0b20e2c..f9375a9a54 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheableOperation.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheableOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,8 +20,28 @@ package org.springframework.cache.interceptor; * Class describing a cache 'cacheable' operation. * * @author Costin Leau + * @author Phillip Webb * @since 3.1 */ public class CacheableOperation extends CacheOperation { + private String unless; + + + public String getUnless() { + return unless; + } + + public void setUnless(String unless) { + this.unless = unless; + } + + @Override + protected StringBuilder getOperationDescription() { + StringBuilder sb = super.getOperationDescription(); + sb.append(" | unless='"); + sb.append(this.unless); + sb.append("'"); + return sb; + } } diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/ExpressionEvaluator.java b/spring-context/src/main/java/org/springframework/cache/interceptor/ExpressionEvaluator.java index 9bcce589ed..ba109397a2 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/ExpressionEvaluator.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/ExpressionEvaluator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,49 +35,84 @@ import org.springframework.expression.spel.standard.SpelExpressionParser; *

Performs internal caching for performance reasons. * * @author Costin Leau + * @author Phillip Webb * @since 3.1 */ class ExpressionEvaluator { + public static final Object NO_RESULT = new Object(); + private final SpelExpressionParser parser = new SpelExpressionParser(); // shared param discoverer since it caches data internally private final ParameterNameDiscoverer paramNameDiscoverer = new LocalVariableTableParameterNameDiscoverer(); + private final Map keyCache = new ConcurrentHashMap(64); + private final Map conditionCache = new ConcurrentHashMap(64); - private final Map keyCache = new ConcurrentHashMap(64); + private final Map unlessCache = new ConcurrentHashMap(64); private final Map targetMethodCache = new ConcurrentHashMap(64); - public EvaluationContext createEvaluationContext( - Collection caches, Method method, Object[] args, Object target, Class targetClass) { - - CacheExpressionRootObject rootObject = - new CacheExpressionRootObject(caches, method, args, target, targetClass); - return new LazyParamAwareEvaluationContext(rootObject, - this.paramNameDiscoverer, method, args, targetClass, this.targetMethodCache); + /** + * Create an {@link EvaluationContext} without a return value. + * @see #createEvaluationContext(Collection, Method, Object[], Object, Class, Object) + */ + public EvaluationContext createEvaluationContext(Collection caches, + Method method, Object[] args, Object target, Class targetClass) { + return createEvaluationContext(caches, method, args, target, targetClass, + NO_RESULT); } - public boolean condition(String conditionExpression, Method method, EvaluationContext evalContext) { - String key = toString(method, conditionExpression); - Expression condExp = this.conditionCache.get(key); - if (condExp == null) { - condExp = this.parser.parseExpression(conditionExpression); - this.conditionCache.put(key, condExp); + /** + * Create an {@link EvaluationContext}. + * + * @param caches the current caches + * @param method the method + * @param args the method arguments + * @param target the target object + * @param targetClass the target class + * @param result the return value (can be {@code null}) or + * {@link #NO_RESULT} if there is no return at this time + * @return the evalulation context + */ + public EvaluationContext createEvaluationContext(Collection caches, + Method method, Object[] args, Object target, Class targetClass, + final Object result) { + CacheExpressionRootObject rootObject = new CacheExpressionRootObject(caches, + method, args, target, targetClass); + LazyParamAwareEvaluationContext evaluationContext = new LazyParamAwareEvaluationContext(rootObject, + this.paramNameDiscoverer, method, args, targetClass, this.targetMethodCache); + if(result != NO_RESULT) { + evaluationContext.setVariable("result", result); } - return condExp.getValue(evalContext, boolean.class); + return evaluationContext; } public Object key(String keyExpression, Method method, EvaluationContext evalContext) { - String key = toString(method, keyExpression); - Expression keyExp = this.keyCache.get(key); - if (keyExp == null) { - keyExp = this.parser.parseExpression(keyExpression); - this.keyCache.put(key, keyExp); + return getExpression(this.keyCache, keyExpression, method).getValue(evalContext); + } + + public boolean condition(String conditionExpression, Method method, EvaluationContext evalContext) { + return getExpression(this.conditionCache, conditionExpression, method).getValue( + evalContext, boolean.class); + } + + public boolean unless(String unlessExpression, Method method, EvaluationContext evalContext) { + return getExpression(this.unlessCache, unlessExpression, method).getValue( + evalContext, boolean.class); + } + + private Expression getExpression(Map cache, String expression, Method method) { + String key = toString(method, expression); + Expression rtn = cache.get(key); + if (rtn == null) { + rtn = this.parser.parseExpression(expression); + cache.put(key, rtn); } - return keyExp.getValue(evalContext); + return rtn; } private String toString(Method method, String expression) { diff --git a/spring-context/src/main/java/org/springframework/context/annotation/Configuration.java b/spring-context/src/main/java/org/springframework/context/annotation/Configuration.java index b09623c29c..ec3cf117ef 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/Configuration.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/Configuration.java @@ -68,8 +68,7 @@ import org.springframework.stereotype.Component; * * * - * - * } + * } * * In the example above, {@code } is required in order to * enable {@link ConfigurationClassPostProcessor} and other annotation-related diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java index e41aca1728..e1676c6eb2 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java @@ -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. @@ -256,7 +256,8 @@ class ConfigurationClassBeanDefinitionReader { if (proxyMode != ScopedProxyMode.NO) { BeanDefinitionHolder proxyDef = ScopedProxyCreator.createScopedProxy( new BeanDefinitionHolder(beanDef, beanName), this.registry, proxyMode == ScopedProxyMode.TARGET_CLASS); - beanDefToRegister = proxyDef.getBeanDefinition(); + beanDefToRegister = + new ConfigurationClassBeanDefinition((RootBeanDefinition) proxyDef.getBeanDefinition(), configClass); } if (logger.isDebugEnabled()) { @@ -307,12 +308,17 @@ class ConfigurationClassBeanDefinitionReader { @SuppressWarnings("serial") private static class ConfigurationClassBeanDefinition extends RootBeanDefinition implements AnnotatedBeanDefinition { - private AnnotationMetadata annotationMetadata; + private final AnnotationMetadata annotationMetadata; public ConfigurationClassBeanDefinition(ConfigurationClass configClass) { this.annotationMetadata = configClass.getMetadata(); } + public ConfigurationClassBeanDefinition(RootBeanDefinition original, ConfigurationClass configClass) { + super(original); + this.annotationMetadata = configClass.getMetadata(); + } + private ConfigurationClassBeanDefinition(ConfigurationClassBeanDefinition original) { super(original); this.annotationMetadata = original.annotationMetadata; diff --git a/spring-context/src/main/resources/org/springframework/cache/config/spring-cache-3.2.xsd b/spring-context/src/main/resources/org/springframework/cache/config/spring-cache-3.2.xsd index 9bfdf14010..baf1f802c0 100644 --- a/spring-context/src/main/resources/org/springframework/cache/config/spring-cache-3.2.xsd +++ b/spring-context/src/main/resources/org/springframework/cache/config/spring-cache-3.2.xsd @@ -36,7 +36,7 @@ @@ -150,7 +150,7 @@ @@ -160,7 +160,7 @@ - + @@ -194,16 +194,42 @@ example, 'get*', 'handle*', '*Order', 'on*Event', etc.]]> - + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -217,16 +243,16 @@ - + - + diff --git a/spring-context/src/test/java/org/springframework/cache/config/AbstractAnnotationTests.java b/spring-context/src/test/java/org/springframework/cache/config/AbstractAnnotationTests.java index 4bc0c888b3..41d717d692 100644 --- a/spring-context/src/test/java/org/springframework/cache/config/AbstractAnnotationTests.java +++ b/spring-context/src/test/java/org/springframework/cache/config/AbstractAnnotationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2011 the original author or authors. + * Copyright 2010-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package org.springframework.cache.config; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.*; import java.util.Collection; @@ -33,6 +35,7 @@ import org.springframework.context.ApplicationContext; * * @author Costin Leau * @author Chris Beams + * @author Phillip Webb */ public abstract class AbstractAnnotationTests { @@ -187,6 +190,15 @@ public abstract class AbstractAnnotationTests { assertSame(r3, r4); } + public void testUnlessExpression(CacheableService service) throws Exception { + Cache cache = cm.getCache("default"); + cache.clear(); + service.unless(10); + service.unless(11); + assertThat(cache.get(10).get(), equalTo((Object) 10L)); + assertThat(cache.get(11), nullValue()); + } + public void testKeyExpression(CacheableService service) throws Exception { Object r1 = service.key(5, 1); Object r2 = service.key(5, 2); @@ -441,6 +453,16 @@ public abstract class AbstractAnnotationTests { testConditionalExpression(cs); } + @Test + public void testUnlessExpression() throws Exception { + testUnlessExpression(cs); + } + + @Test + public void testClassCacheUnlessExpression() throws Exception { + testUnlessExpression(cs); + } + @Test public void testKeyExpression() throws Exception { testKeyExpression(cs); @@ -618,4 +640,4 @@ public abstract class AbstractAnnotationTests { public void testClassMultiConditionalCacheAndEvict() { testMultiConditionalCacheAndEvict(ccs); } -} \ No newline at end of file +} diff --git a/spring-context/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java b/spring-context/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java index 4c72d9d593..f168ab990a 100644 --- a/spring-context/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java +++ b/spring-context/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import org.springframework.cache.annotation.Caching; /** * @author Costin Leau + * @author Phillip Webb */ @Cacheable("default") public class AnnotatedClassCacheableService implements CacheableService { @@ -42,6 +43,12 @@ public class AnnotatedClassCacheableService implements CacheableService return null; } + @Override + @Cacheable(value = "default", unless = "#result > 10") + public Object unless(int arg) { + return arg; + } + @Override @CacheEvict("default") public void invalidate(Object arg1) { @@ -157,4 +164,4 @@ public class AnnotatedClassCacheableService implements CacheableService public Object multiUpdate(Object arg1) { return arg1; } -} \ No newline at end of file +} diff --git a/spring-context/src/test/java/org/springframework/cache/config/CacheableService.java b/spring-context/src/test/java/org/springframework/cache/config/CacheableService.java index 81dd758d19..98c1da1e03 100644 --- a/spring-context/src/test/java/org/springframework/cache/config/CacheableService.java +++ b/spring-context/src/test/java/org/springframework/cache/config/CacheableService.java @@ -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,6 +20,7 @@ package org.springframework.cache.config; * Basic service interface. * * @author Costin Leau + * @author Phillip Webb */ public interface CacheableService { @@ -39,6 +40,8 @@ public interface CacheableService { T conditional(int field); + T unless(int arg); + T key(Object arg1, Object arg2); T name(Object arg1); @@ -67,4 +70,4 @@ public interface CacheableService { T multiConditionalCacheAndEvict(Object arg1); T multiUpdate(Object arg1); -} \ No newline at end of file +} diff --git a/spring-context/src/test/java/org/springframework/cache/config/DefaultCacheableService.java b/spring-context/src/test/java/org/springframework/cache/config/DefaultCacheableService.java index f9f19b4759..f987d7bbe6 100644 --- a/spring-context/src/test/java/org/springframework/cache/config/DefaultCacheableService.java +++ b/spring-context/src/test/java/org/springframework/cache/config/DefaultCacheableService.java @@ -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,6 +27,7 @@ import org.springframework.cache.annotation.Caching; * Simple cacheable service * * @author Costin Leau + * @author Phillip Webb */ public class DefaultCacheableService implements CacheableService { @@ -78,6 +79,12 @@ public class DefaultCacheableService implements CacheableService { return counter.getAndIncrement(); } + @Override + @Cacheable(value = "default", unless = "#result > 10") + public Long unless(int arg) { + return (long) arg; + } + @Override @Cacheable(value = "default", key = "#p0") public Long key(Object arg1, Object arg2) { @@ -163,4 +170,4 @@ public class DefaultCacheableService implements CacheableService { public Long multiUpdate(Object arg1) { return Long.valueOf(arg1.toString()); } -} \ No newline at end of file +} diff --git a/spring-context/src/test/java/org/springframework/cache/interceptor/ExpressionEvalutatorTest.java b/spring-context/src/test/java/org/springframework/cache/interceptor/ExpressionEvalutatorTest.java index b52f364746..0a664afac0 100644 --- a/spring-context/src/test/java/org/springframework/cache/interceptor/ExpressionEvalutatorTest.java +++ b/spring-context/src/test/java/org/springframework/cache/interceptor/ExpressionEvalutatorTest.java @@ -1,5 +1,5 @@ /* - * Copyright 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,24 +16,39 @@ package org.springframework.cache.interceptor; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; import java.lang.reflect.Method; import java.util.Collection; import java.util.Iterator; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.springframework.cache.Cache; import org.springframework.cache.annotation.AnnotationCacheOperationSource; import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Caching; import org.springframework.cache.concurrent.ConcurrentMapCache; import org.springframework.expression.EvaluationContext; +import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.util.ReflectionUtils; import edu.emory.mathcs.backport.java.util.Collections; +/** + * @author Costin Leau + * @author Phillip Webb + */ public class ExpressionEvalutatorTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + private ExpressionEvaluator eval = new ExpressionEvaluator(); private AnnotationCacheOperationSource source = new AnnotationCacheOperationSource(); @@ -59,6 +74,7 @@ public class ExpressionEvalutatorTest { } @Test + @SuppressWarnings("unchecked") public void testMultipleCachingEval() throws Exception { AnnotatedClass target = new AnnotatedClass(); Method method = ReflectionUtils.findMethod(AnnotatedClass.class, "multipleCaching", Object.class, @@ -78,9 +94,41 @@ public class ExpressionEvalutatorTest { assertEquals(args[1], keyB); } + @Test + public void withReturnValue() throws Exception { + EvaluationContext context = createEvaluationContext("theResult"); + Object value = new SpelExpressionParser().parseExpression("#result").getValue(context); + assertThat(value, equalTo((Object) "theResult")); + } + + @Test + public void withNullReturn() throws Exception { + EvaluationContext context = createEvaluationContext(null); + Object value = new SpelExpressionParser().parseExpression("#result").getValue(context); + assertThat(value, nullValue()); + } + + @Test + public void withoutReturnValue() throws Exception { + EvaluationContext context = createEvaluationContext(ExpressionEvaluator.NO_RESULT); + Object value = new SpelExpressionParser().parseExpression("#result").getValue(context); + assertThat(value, nullValue()); + } + + private EvaluationContext createEvaluationContext(Object result) { + AnnotatedClass target = new AnnotatedClass(); + Method method = ReflectionUtils.findMethod(AnnotatedClass.class, "multipleCaching", Object.class, + Object.class); + Object[] args = new Object[] { new Object(), new Object() }; + @SuppressWarnings("unchecked") + Collection map = Collections.singleton(new ConcurrentMapCache("test")); + EvaluationContext context = eval.createEvaluationContext(map, method, args, target, target.getClass(), result); + return context; + } + private static class AnnotatedClass { @Caching(cacheable = { @Cacheable(value = "test", key = "#a"), @Cacheable(value = "test", key = "#b") }) public void multipleCaching(Object a, Object b) { } } -} \ No newline at end of file +} diff --git a/spring-context/src/test/resources/org/springframework/cache/config/cache-advice.xml b/spring-context/src/test/resources/org/springframework/cache/config/cache-advice.xml index d9eef4816b..5ebf54ddcb 100644 --- a/spring-context/src/test/resources/org/springframework/cache/config/cache-advice.xml +++ b/spring-context/src/test/resources/org/springframework/cache/config/cache-advice.xml @@ -3,7 +3,7 @@ xmlns:aop="http://www.springframework.org/schema/aop" xmlns:cache="http://www.springframework.org/schema/cache" xmlns:p="http://www.springframework.org/schema/p" - xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd"> @@ -11,6 +11,7 @@ + @@ -54,6 +55,7 @@ + @@ -80,9 +82,9 @@ - + - + @@ -98,12 +100,12 @@ - + - + - + diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java b/spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java index c9e7bad2f1..ccadb93061 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java @@ -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. @@ -20,6 +20,7 @@ package org.springframework.expression.spel; * Configuration object for the SpEL expression parser. * * @author Juergen Hoeller + * @author Phillip Webb * @since 3.0 * @see org.springframework.expression.spel.standard.SpelExpressionParser#SpelExpressionParser(SpelParserConfiguration) */ @@ -29,19 +30,52 @@ public class SpelParserConfiguration { private final boolean autoGrowCollections; + private int maximumAutoGrowSize; + + /** + * Create a new {@link SpelParserConfiguration} instance. + * @param autoGrowNullReferences if null references should automatically grow + * @param autoGrowCollections if collections should automatically grow + * @see #SpelParserConfiguration(boolean, boolean, int) + */ public SpelParserConfiguration(boolean autoGrowNullReferences, boolean autoGrowCollections) { + this(autoGrowNullReferences, autoGrowCollections, Integer.MAX_VALUE); + } + + /** + * Create a new {@link SpelParserConfiguration} instance. + * @param autoGrowNullReferences if null references should automatically grow + * @param autoGrowCollections if collections should automatically grow + * @param maximumAutoGrowSize the maximum size that the collection can auto grow + */ + public SpelParserConfiguration(boolean autoGrowNullReferences, + boolean autoGrowCollections, int maximumAutoGrowSize) { this.autoGrowNullReferences = autoGrowNullReferences; this.autoGrowCollections = autoGrowCollections; + this.maximumAutoGrowSize = maximumAutoGrowSize; } + /** + * @return {@code true} if {@code null} references should be automatically grown + */ public boolean isAutoGrowNullReferences() { return this.autoGrowNullReferences; } + /** + * @return {@code true} if collections should be automatically grown + */ public boolean isAutoGrowCollections() { return this.autoGrowCollections; } + /** + * @return the maximum size that a collection can auto grow + */ + public int getMaximumAutoGrowSize() { + return this.maximumAutoGrowSize; + } + } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java index f061326251..2e19cc81e2 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java @@ -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,6 +38,7 @@ import org.springframework.expression.spel.support.ReflectivePropertyAccessor; * (lists/sets)/arrays * * @author Andy Clement + * @author Phillip Webb * @since 3.0 */ // TODO support multidimensional arrays @@ -257,25 +258,20 @@ public class Indexer extends SpelNodeImpl { private final boolean growCollection; + private int maximumSize; + CollectionIndexingValueRef(Collection collection, int index, TypeDescriptor collectionEntryTypeDescriptor, - TypeConverter typeConverter, boolean growCollection) { + TypeConverter typeConverter, boolean growCollection, int maximumSize) { this.collection = collection; this.index = index; this.collectionEntryTypeDescriptor = collectionEntryTypeDescriptor; this.typeConverter = typeConverter; this.growCollection = growCollection; + this.maximumSize = maximumSize; } public TypedValue getValue() { - if (this.index >= this.collection.size()) { - if (this.growCollection) { - growCollection(this.collectionEntryTypeDescriptor, this.index, this.collection); - } - else { - throw new SpelEvaluationException(getStartPosition(), SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS, - this.collection.size(), this.index); - } - } + growCollectionIfNecessary(); if (this.collection instanceof List) { Object o = ((List) this.collection).get(this.index); return new TypedValue(o, this.collectionEntryTypeDescriptor.elementTypeDescriptor(o)); @@ -291,15 +287,7 @@ public class Indexer extends SpelNodeImpl { } public void setValue(Object newValue) { - if (this.index >= this.collection.size()) { - if (this.growCollection) { - growCollection(this.collectionEntryTypeDescriptor, this.index, this.collection); - } - else { - throw new SpelEvaluationException(getStartPosition(), SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS, - this.collection.size(), this.index); - } - } + growCollectionIfNecessary(); if (this.collection instanceof List) { List list = (List) this.collection; if (this.collectionEntryTypeDescriptor.getElementTypeDescriptor() != null) { @@ -314,6 +302,36 @@ public class Indexer extends SpelNodeImpl { } } + private void growCollectionIfNecessary() { + if (this.index >= this.collection.size()) { + + if (!this.growCollection) { + throw new SpelEvaluationException(getStartPosition(), SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS, + this.collection.size(), this.index); + } + + if(this.index >= this.maximumSize) { + throw new SpelEvaluationException(getStartPosition(), SpelMessage.UNABLE_TO_GROW_COLLECTION); + } + + if (this.collectionEntryTypeDescriptor.getElementTypeDescriptor() == null) { + throw new SpelEvaluationException(getStartPosition(), SpelMessage.UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE); + } + + TypeDescriptor elementType = this.collectionEntryTypeDescriptor.getElementTypeDescriptor(); + try { + int newElements = this.index - this.collection.size(); + while (newElements >= 0) { + (this.collection).add(elementType.getType().newInstance()); + newElements--; + } + } + catch (Exception ex) { + throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.UNABLE_TO_GROW_COLLECTION); + } + } + } + public boolean isWritable() { return true; } @@ -403,7 +421,8 @@ public class Indexer extends SpelNodeImpl { } else if (targetObject instanceof Collection) { return new CollectionIndexingValueRef((Collection) targetObject, idx, targetObjectTypeDescriptor, - state.getTypeConverter(), state.getConfiguration().isAutoGrowCollections()); + state.getTypeConverter(), state.getConfiguration().isAutoGrowCollections(), + state.getConfiguration().getMaximumAutoGrowSize()); } else if (targetObject instanceof String) { return new StringIndexingLValue((String) targetObject, idx, targetObjectTypeDescriptor); @@ -421,32 +440,6 @@ public class Indexer extends SpelNodeImpl { targetObjectTypeDescriptor.toString()); } - /** - * Attempt to grow the specified collection so that the specified index is valid. - * @param targetType the type of the elements in the collection - * @param index the index into the collection that needs to be valid - * @param collection the collection to grow with elements - */ - private void growCollection(TypeDescriptor targetType, int index, Collection collection) { - if (targetType.getElementTypeDescriptor() == null) { - throw new SpelEvaluationException(getStartPosition(), SpelMessage.UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE); - } - TypeDescriptor elementType = targetType.getElementTypeDescriptor(); - Object newCollectionElement; - try { - int newElements = index - collection.size(); - while (newElements > 0) { - collection.add(elementType.getType().newInstance()); - newElements--; - } - newCollectionElement = elementType.getType().newInstance(); - } - catch (Exception ex) { - throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.UNABLE_TO_GROW_COLLECTION); - } - collection.add(newCollectionElement); - } - @Override public String toStringAST() { StringBuilder sb = new StringBuilder(); diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java index aeb991ab55..25fcba0f4b 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,14 @@ package org.springframework.expression.spel; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.lang.reflect.Method; import java.util.ArrayList; @@ -24,7 +31,6 @@ import java.util.List; import java.util.Map; import org.junit.Test; - import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.AccessException; import org.springframework.expression.BeanResolver; @@ -48,6 +54,7 @@ import org.springframework.expression.spel.testresources.TestPerson; * @author Andy Clement * @author Mark Fisher * @author Sam Brannen + * @author Phillip Webb * @since 3.0 */ public class EvaluationTests extends ExpressionTestCase { @@ -708,6 +715,23 @@ public class EvaluationTests extends ExpressionTestCase { } } + @Test + public void limitCollectionGrowing() throws Exception { + TestClass instance = new TestClass(); + StandardEvaluationContext ctx = new StandardEvaluationContext(instance); + SpelExpressionParser parser = new SpelExpressionParser( new SpelParserConfiguration(true, true, 3)); + Expression expression = parser.parseExpression("foo[2]"); + expression.setValue(ctx, "2"); + assertThat(instance.getFoo().size(), equalTo(3)); + expression = parser.parseExpression("foo[3]"); + try { + expression.setValue(ctx, "3"); + } catch(SpelEvaluationException see) { + assertEquals(SpelMessage.UNABLE_TO_GROW_COLLECTION, see.getMessageCode()); + assertThat(instance.getFoo().size(), equalTo(3)); + } + } + // For now I am making #this not assignable @Test public void increment01root() { diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodeSQLExceptionTranslator.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodeSQLExceptionTranslator.java index a3290b8825..3bf52d3f71 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodeSQLExceptionTranslator.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodeSQLExceptionTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,6 @@ import java.lang.reflect.Constructor; import java.sql.BatchUpdateException; import java.sql.SQLException; import java.util.Arrays; - import javax.sql.DataSource; import org.springframework.core.JdkVersion; @@ -30,9 +29,9 @@ import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessResourceFailureException; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.DeadlockLoserDataAccessException; +import org.springframework.dao.DuplicateKeyException; import org.springframework.dao.PermissionDeniedDataAccessException; import org.springframework.dao.TransientDataAccessResourceException; -import org.springframework.dao.DuplicateKeyException; import org.springframework.jdbc.BadSqlGrammarException; import org.springframework.jdbc.InvalidResultSetAccessException; @@ -201,20 +200,25 @@ public class SQLErrorCodeSQLExceptionTranslator extends AbstractFallbackSQLExcep // Check SQLErrorCodes with corresponding error code, if available. if (this.sqlErrorCodes != null) { - String errorCode = null; + String errorCode; if (this.sqlErrorCodes.isUseSqlStateForTranslation()) { errorCode = sqlEx.getSQLState(); } else { - errorCode = Integer.toString(sqlEx.getErrorCode()); + // Try to find SQLException with actual error code, looping through the causes. + // E.g. applicable to java.sql.DataTruncation as of JDK 1.6. + SQLException current = sqlEx; + while (current.getErrorCode() == 0 && current.getCause() instanceof SQLException) { + current = (SQLException) current.getCause(); + } + errorCode = Integer.toString(current.getErrorCode()); } if (errorCode != null) { // Look for defined custom translations first. CustomSQLErrorCodesTranslation[] customTranslations = this.sqlErrorCodes.getCustomTranslations(); if (customTranslations != null) { - for (int i = 0; i < customTranslations.length; i++) { - CustomSQLErrorCodesTranslation customTranslation = customTranslations[i]; + for (CustomSQLErrorCodesTranslation customTranslation : customTranslations) { if (Arrays.binarySearch(customTranslation.getErrorCodes(), errorCode) >= 0) { if (customTranslation.getExceptionClass() != null) { DataAccessException customException = createCustomException( @@ -273,7 +277,7 @@ public class SQLErrorCodeSQLExceptionTranslator extends AbstractFallbackSQLExcep // We couldn't identify it more precisely - let's hand it over to the SQLState fallback translator. if (logger.isDebugEnabled()) { - String codes = null; + String codes; if (this.sqlErrorCodes != null && this.sqlErrorCodes.isUseSqlStateForTranslation()) { codes = "SQL state '" + sqlEx.getSQLState() + "', error code '" + sqlEx.getErrorCode(); } @@ -321,8 +325,8 @@ public class SQLErrorCodeSQLExceptionTranslator extends AbstractFallbackSQLExcep try { int constructorType = 0; Constructor[] constructors = exceptionClass.getConstructors(); - for (int i = 0; i < constructors.length; i++) { - Class[] parameterTypes = constructors[i].getParameterTypes(); + for (Constructor constructor : constructors) { + Class[] parameterTypes = constructor.getParameterTypes(); if (parameterTypes.length == 1 && parameterTypes[0].equals(String.class)) { if (constructorType < MESSAGE_ONLY_CONSTRUCTOR) constructorType = MESSAGE_ONLY_CONSTRUCTOR; @@ -350,7 +354,7 @@ public class SQLErrorCodeSQLExceptionTranslator extends AbstractFallbackSQLExcep } // invoke constructor - Constructor exceptionConstructor = null; + Constructor exceptionConstructor; switch (constructorType) { case MESSAGE_SQL_SQLEX_CONSTRUCTOR: Class[] messageAndSqlAndSqlExArgsClass = new Class[] {String.class, String.class, SQLException.class}; diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLErrorCodeSQLExceptionTranslatorTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLErrorCodeSQLExceptionTranslatorTests.java index c2e07ec765..f7a23fa2e0 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLErrorCodeSQLExceptionTranslatorTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLErrorCodeSQLExceptionTranslatorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,9 @@ package org.springframework.jdbc.support; -import java.sql.SQLException; import java.sql.BatchUpdateException; +import java.sql.DataTruncation; +import java.sql.SQLException; import junit.framework.TestCase; @@ -33,6 +34,7 @@ import org.springframework.jdbc.InvalidResultSetAccessException; /** * @author Rod Johnson + * @author Juergen Hoeller */ public class SQLErrorCodeSQLExceptionTranslatorTests extends TestCase { @@ -92,13 +94,21 @@ public class SQLErrorCodeSQLExceptionTranslatorTests extends TestCase { SQLExceptionTranslator sext = new SQLErrorCodeSQLExceptionTranslator(ERROR_CODES); SQLException badSqlEx = new SQLException("", "", 1); - BatchUpdateException batchUdateEx = new BatchUpdateException(); - batchUdateEx.setNextException(badSqlEx); - BadSqlGrammarException bsgex = (BadSqlGrammarException) sext.translate("task", "SQL", batchUdateEx); + BatchUpdateException batchUpdateEx = new BatchUpdateException(); + batchUpdateEx.setNextException(badSqlEx); + BadSqlGrammarException bsgex = (BadSqlGrammarException) sext.translate("task", "SQL", batchUpdateEx); assertEquals("SQL", bsgex.getSql()); assertEquals(badSqlEx, bsgex.getSQLException()); } + public void testDataTruncationTranslation() { + SQLExceptionTranslator sext = new SQLErrorCodeSQLExceptionTranslator(ERROR_CODES); + + SQLException dataAccessEx = new SQLException("", "", 5); + DataTruncation dataTruncation = new DataTruncation(1, true, true, 1, 1, dataAccessEx); + DataAccessResourceFailureException daex = (DataAccessResourceFailureException) sext.translate("task", "SQL", dataTruncation); + assertEquals(dataTruncation, daex.getCause()); + } @SuppressWarnings("serial") public void testCustomTranslateMethodTranslation() { diff --git a/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManager.java b/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManager.java index 961ffdcc72..0024eef27d 100644 --- a/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManager.java +++ b/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ package org.springframework.web.accept; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; @@ -55,6 +56,7 @@ public class ContentNegotiationManager implements ContentNegotiationStrategy, Me private final Set fileExtensionResolvers = new LinkedHashSet(); + /** * Create an instance with the given ContentNegotiationStrategy instances. *

Each instance is checked to see if it is also an implementation of @@ -72,12 +74,29 @@ public class ContentNegotiationManager implements ContentNegotiationStrategy, Me } /** - * Create an instance with a {@link HeaderContentNegotiationStrategy}. + * Create an instance with the given ContentNegotiationStrategy instances. + *

Each instance is checked to see if it is also an implementation of + * MediaTypeFileExtensionResolver, and if so it is registered as such. + * @param strategies one more more ContentNegotiationStrategy instances + */ + public ContentNegotiationManager(Collection strategies) { + Assert.notEmpty(strategies, "At least one ContentNegotiationStrategy is expected"); + this.contentNegotiationStrategies.addAll(strategies); + for (ContentNegotiationStrategy strategy : this.contentNegotiationStrategies) { + if (strategy instanceof MediaTypeFileExtensionResolver) { + this.fileExtensionResolvers.add((MediaTypeFileExtensionResolver) strategy); + } + } + } + + /** + * Create a default instance with a {@link HeaderContentNegotiationStrategy}. */ public ContentNegotiationManager() { this(new HeaderContentNegotiationStrategy()); } + /** * Add MediaTypeFileExtensionResolver instances. *

Note that some {@link ContentNegotiationStrategy} implementations also diff --git a/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManagerFactoryBean.java b/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManagerFactoryBean.java index 8cf79f927d..a5d0357d91 100644 --- a/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManagerFactoryBean.java +++ b/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManagerFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.web.accept; import java.util.ArrayList; @@ -22,7 +23,6 @@ import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; - import javax.servlet.ServletContext; import org.springframework.beans.factory.FactoryBean; @@ -39,13 +39,13 @@ import org.springframework.web.context.ServletContextAware; *

By default strategies for checking the extension of the request path and * the {@code Accept} header are registered. The path extension check will perform * lookups through the {@link ServletContext} and the Java Activation Framework - * (if present) unless {@linkplain #setMediaTypes(Properties) media types} are configured. + * (if present) unless {@linkplain #setMediaTypes media types} are configured. * * @author Rossen Stoyanchev * @since 3.2 */ public class ContentNegotiationManagerFactoryBean - implements FactoryBean, InitializingBean, ServletContextAware { + implements FactoryBean, ServletContextAware, InitializingBean { private boolean favorPathExtension = true; @@ -65,6 +65,7 @@ public class ContentNegotiationManagerFactoryBean private ServletContext servletContext; + /** * Indicate whether the extension of the request path should be used to determine * the requested media type with the highest priority. @@ -81,7 +82,6 @@ public class ContentNegotiationManagerFactoryBean *

When this mapping is not set or when an extension is not found, the Java * Action Framework, if available, may be used if enabled via * {@link #setFavorPathExtension(boolean)}. - * * @see #addMediaType(String, MediaType) * @see #addMediaTypes(Map) */ @@ -121,9 +121,8 @@ public class ContentNegotiationManagerFactoryBean * to map from file extensions to media types. This is used only when * {@link #setFavorPathExtension(boolean)} is set to {@code true}. *

The default value is {@code true}. - * - * @see #parameterName - * @see #setMediaTypes(Properties) + * @see #setParameterName + * @see #setMediaTypes */ public void setUseJaf(boolean useJaf) { this.useJaf = useJaf; @@ -138,8 +137,7 @@ public class ContentNegotiationManagerFactoryBean * {@code "application/pdf"} regardless of the {@code Accept} header. *

To use this option effectively you must also configure the MediaType * type mappings via {@link #setMediaTypes(Properties)}. - * - * @see #setParameterName(String) + * @see #setParameterName */ public void setFavorParameter(boolean favorParameter) { this.favorParameter = favorParameter; @@ -180,7 +178,8 @@ public class ContentNegotiationManagerFactoryBean this.servletContext = servletContext; } - public void afterPropertiesSet() throws Exception { + + public void afterPropertiesSet() { List strategies = new ArrayList(); if (this.favorPathExtension) { @@ -210,8 +209,12 @@ public class ContentNegotiationManagerFactoryBean strategies.add(new FixedContentNegotiationStrategy(this.defaultContentType)); } - ContentNegotiationStrategy[] array = strategies.toArray(new ContentNegotiationStrategy[strategies.size()]); - this.contentNegotiationManager = new ContentNegotiationManager(array); + this.contentNegotiationManager = new ContentNegotiationManager(strategies); + } + + + public ContentNegotiationManager getObject() { + return this.contentNegotiationManager; } public Class getObjectType() { @@ -222,8 +225,4 @@ public class ContentNegotiationManagerFactoryBean return true; } - public ContentNegotiationManager getObject() throws Exception { - return this.contentNegotiationManager; - } - } diff --git a/spring-web/src/main/resources/META-INF/web-fragment.xml b/spring-web/src/main/resources/META-INF/web-fragment.xml index d7c28cbccd..e687c8b58e 100644 --- a/spring-web/src/main/resources/META-INF/web-fragment.xml +++ b/spring-web/src/main/resources/META-INF/web-fragment.xml @@ -5,5 +5,6 @@ version="3.0" metadata-complete="true"> spring_web + - \ No newline at end of file + diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/ContentNegotiatingViewResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/ContentNegotiatingViewResolver.java index 2d543941af..f7b59c3f6c 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/ContentNegotiatingViewResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/ContentNegotiatingViewResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,6 @@ import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Set; - import javax.activation.FileTypeMap; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; @@ -33,6 +32,7 @@ import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.OrderComparator; @@ -95,7 +95,7 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport private ContentNegotiationManager contentNegotiationManager; - private ContentNegotiationManagerFactoryBean cnManagerFactoryBean = new ContentNegotiationManagerFactoryBean(); + private final ContentNegotiationManagerFactoryBean cnManagerFactoryBean = new ContentNegotiationManagerFactoryBean(); private boolean useNotAcceptableStatusCode = false; @@ -104,10 +104,6 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport private List viewResolvers; - public ContentNegotiatingViewResolver() { - super(); - } - public void setOrder(int order) { this.order = order; } @@ -118,7 +114,9 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport /** * Set the {@link ContentNegotiationManager} to use to determine requested media types. - * If not set, the default constructor is used. + *

If not set, ContentNegotiationManager's default constructor will be used, + * applying a {@link org.springframework.web.accept.HeaderContentNegotiationStrategy}. + * @see ContentNegotiationManager#ContentNegotiationManager() */ public void setContentNegotiationManager(ContentNegotiationManager contentNegotiationManager) { this.contentNegotiationManager = contentNegotiationManager; @@ -130,18 +128,16 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport *

For instance, when this flag is {@code true} (the default), a request for {@code /hotels.pdf} * will result in an {@code AbstractPdfView} being resolved, while the {@code Accept} header can be the * browser-defined {@code text/html,application/xhtml+xml}. - * * @deprecated use {@link #setContentNegotiationManager(ContentNegotiationManager)} */ @Deprecated public void setFavorPathExtension(boolean favorPathExtension) { - this.cnManagerFactoryBean.setFavorParameter(favorPathExtension); + this.cnManagerFactoryBean.setFavorPathExtension(favorPathExtension); } /** * Indicate whether to use the Java Activation Framework to map from file extensions to media types. *

Default is {@code true}, i.e. the Java Activation Framework is used (if available). - * * @deprecated use {@link #setContentNegotiationManager(ContentNegotiationManager)} */ @Deprecated @@ -155,7 +151,6 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport *

For instance, when this flag is {@code true}, a request for {@code /hotels?format=pdf} will result * in an {@code AbstractPdfView} being resolved, while the {@code Accept} header can be the browser-defined * {@code text/html,application/xhtml+xml}. - * * @deprecated use {@link #setContentNegotiationManager(ContentNegotiationManager)} */ @Deprecated @@ -166,7 +161,6 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport /** * Set the parameter name that can be used to determine the requested media type if the {@link * #setFavorParameter} property is {@code true}. The default parameter name is {@code format}. - * * @deprecated use {@link #setContentNegotiationManager(ContentNegotiationManager)} */ @Deprecated @@ -179,7 +173,6 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport *

If set to {@code true}, this view resolver will only refer to the file extension and/or * parameter, as indicated by the {@link #setFavorPathExtension favorPathExtension} and * {@link #setFavorParameter favorParameter} properties. - * * @deprecated use {@link #setContentNegotiationManager(ContentNegotiationManager)} */ @Deprecated @@ -191,7 +184,6 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport * Set the mapping from file extensions to media types. *

When this mapping is not set or when an extension is not present, this view resolver * will fall back to using a {@link FileTypeMap} when the Java Action Framework is available. - * * @deprecated use {@link #setContentNegotiationManager(ContentNegotiationManager)} */ @Deprecated @@ -207,7 +199,6 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport * Set the default content type. *

This content type will be used when file extension, parameter, nor {@code Accept} * header define a content-type, either through being disabled or empty. - * * @deprecated use {@link #setContentNegotiationManager(ContentNegotiationManager)} */ @Deprecated @@ -275,7 +266,7 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport this.cnManagerFactoryBean.setServletContext(servletContext); } - public void afterPropertiesSet() throws Exception { + public void afterPropertiesSet() { if (this.contentNegotiationManager == null) { this.cnManagerFactoryBean.afterPropertiesSet(); this.contentNegotiationManager = this.cnManagerFactoryBean.getObject(); diff --git a/src/api/stylesheet.css b/src/api/stylesheet.css new file mode 100644 index 0000000000..4b105f4ca4 --- /dev/null +++ b/src/api/stylesheet.css @@ -0,0 +1,541 @@ +/* Javadoc style sheet */ + +/* +Overall document style +*/ +body { + background-color:#ffffff; + color:#353833; + font-family:Arial, Helvetica, sans-serif; + font-size:76%; + margin:0; +} +a:link, a:visited { + text-decoration:none; + color:#4c6b87; +} +a:hover, a:focus { + text-decoration:none; + color:#bb7a2a; +} +a:active { + text-decoration:none; + color:#4c6b87; +} +a[name] { + color:#353833; +} +a[name]:hover { + text-decoration:none; + color:#353833; +} +pre { + font-size:1.3em; +} +h1 { + font-size:1.8em; +} +h2 { + font-size:1.5em; +} +h3 { + font-size:1.4em; +} +h4 { + font-size:1.3em; +} +h5 { + font-size:1.2em; +} +h6 { + font-size:1.1em; +} +ul { + list-style-type:disc; +} +code, tt { + font-size:1.2em; +} +dt code { + font-size:1.2em; +} +table tr td dt code { + font-size:1.2em; + vertical-align:top; +} +sup { + font-size:.6em; +} +/* +Document title and Copyright styles +*/ +.clear { + clear:both; + height:0px; + overflow:hidden; +} +.aboutLanguage { + float:right; + padding:0px 21px; + font-size:.8em; + z-index:200; + margin-top:-7px; +} +.legalCopy { + margin-left:.5em; +} +.bar a, .bar a:link, .bar a:visited, .bar a:active { + color:#FFFFFF; + text-decoration:none; +} +.bar a:hover, .bar a:focus { + color:#bb7a2a; +} +.tab { + background-color:#0066FF; + background-image:url(resources/titlebar.gif); + background-position:left top; + background-repeat:no-repeat; + color:#ffffff; + padding:8px; + width:5em; + font-weight:bold; +} +/* +Navigation bar styles +*/ +.bar { + background-image:url(resources/background.gif); + background-repeat:repeat-x; + color:#FFFFFF; + padding:.8em .5em .4em .8em; + height:auto;/*height:1.8em;*/ + font-size:1em; + margin:0; +} +.topNav { + background-image:url(resources/background.gif); + background-repeat:repeat-x; + color:#FFFFFF; + float:left; + padding:0; + width:100%; + clear:right; + height:2.8em; + padding-top:10px; + overflow:hidden; +} +.bottomNav { + margin-top:10px; + background-image:url(resources/background.gif); + background-repeat:repeat-x; + color:#FFFFFF; + float:left; + padding:0; + width:100%; + clear:right; + height:2.8em; + padding-top:10px; + overflow:hidden; +} +.subNav { + background-color:#dee3e9; + border-bottom:1px solid #9eadc0; + float:left; + width:100%; + overflow:hidden; +} +.subNav div { + clear:left; + float:left; + padding:0 0 5px 6px; +} +ul.navList, ul.subNavList { + float:left; + margin:0 25px 0 0; + padding:0; +} +ul.navList li{ + list-style:none; + float:left; + padding:3px 6px; +} +ul.subNavList li{ + list-style:none; + float:left; + font-size:90%; +} +.topNav a:link, .topNav a:active, .topNav a:visited, .bottomNav a:link, .bottomNav a:active, .bottomNav a:visited { + color:#FFFFFF; + text-decoration:none; +} +.topNav a:hover, .bottomNav a:hover { + text-decoration:none; + color:#bb7a2a; +} +.navBarCell1Rev { + background-image:url(resources/tab.gif); + background-color:#a88834; + color:#FFFFFF; + margin: auto 5px; + border:1px solid #c9aa44; +} +/* +Page header and footer styles +*/ +.header, .footer { + clear:both; + margin:0 20px; + padding:5px 0 0 0; +} +.indexHeader { + margin:10px; + position:relative; +} +.indexHeader h1 { + font-size:1.3em; +} +.title { + color:#2c4557; + margin:10px 0; +} +.subTitle { + margin:5px 0 0 0; +} +.header ul { + margin:0 0 25px 0; + padding:0; +} +.footer ul { + margin:20px 0 5px 0; +} +.header ul li, .footer ul li { + list-style:none; + font-size:1.2em; +} +/* +Heading styles +*/ +div.details ul.blockList ul.blockList ul.blockList li.blockList h4, div.details ul.blockList ul.blockList ul.blockListLast li.blockList h4 { + background-color:#dee3e9; + border-top:1px solid #9eadc0; + border-bottom:1px solid #9eadc0; + margin:0 0 6px -8px; + padding:2px 5px; +} +ul.blockList ul.blockList ul.blockList li.blockList h3 { + background-color:#dee3e9; + border-top:1px solid #9eadc0; + border-bottom:1px solid #9eadc0; + margin:0 0 6px -8px; + padding:2px 5px; +} +ul.blockList ul.blockList li.blockList h3 { + padding:0; + margin:15px 0; +} +ul.blockList li.blockList h2 { + padding:0px 0 20px 0; +} +/* +Page layout container styles +*/ +.contentContainer, .sourceContainer, .classUseContainer, .serializedFormContainer, .constantValuesContainer { + clear:both; + padding:10px 20px; + position:relative; +} +.indexContainer { + margin:10px; + position:relative; + font-size:1.0em; +} +.indexContainer h2 { + font-size:1.1em; + padding:0 0 3px 0; +} +.indexContainer ul { + margin:0; + padding:0; +} +.indexContainer ul li { + list-style:none; +} +.contentContainer .description dl dt, .contentContainer .details dl dt, .serializedFormContainer dl dt { + font-size:1.1em; + font-weight:bold; + margin:10px 0 0 0; + color:#4E4E4E; +} +.contentContainer .description dl dd, .contentContainer .details dl dd, .serializedFormContainer dl dd { + margin:10px 0 10px 20px; +} +.serializedFormContainer dl.nameValue dt { + margin-left:1px; + font-size:1.1em; + display:inline; + font-weight:bold; +} +.serializedFormContainer dl.nameValue dd { + margin:0 0 0 1px; + font-size:1.1em; + display:inline; +} +/* +List styles +*/ +ul.horizontal li { + display:inline; + font-size:0.9em; +} +ul.inheritance { + margin:0; + padding:0; +} +ul.inheritance li { + display:inline; + list-style:none; +} +ul.inheritance li ul.inheritance { + margin-left:15px; + padding-left:15px; + padding-top:1px; +} +ul.blockList, ul.blockListLast { + margin:10px 0 10px 0; + padding:0; +} +ul.blockList li.blockList, ul.blockListLast li.blockList { + list-style:none; + margin-bottom:25px; +} +ul.blockList ul.blockList li.blockList, ul.blockList ul.blockListLast li.blockList { + padding:0px 20px 5px 10px; + border:1px solid #9eadc0; + background-color:#f9f9f9; +} +ul.blockList ul.blockList ul.blockList li.blockList, ul.blockList ul.blockList ul.blockListLast li.blockList { + padding:0 0 5px 8px; + background-color:#ffffff; + border:1px solid #9eadc0; + border-top:none; +} +ul.blockList ul.blockList ul.blockList ul.blockList li.blockList { + margin-left:0; + padding-left:0; + padding-bottom:15px; + border:none; + border-bottom:1px solid #9eadc0; +} +ul.blockList ul.blockList ul.blockList ul.blockList li.blockListLast { + list-style:none; + border-bottom:none; + padding-bottom:0; +} +table tr td dl, table tr td dl dt, table tr td dl dd { + margin-top:0; + margin-bottom:1px; +} +/* +Table styles +*/ +.contentContainer table, .classUseContainer table, .constantValuesContainer table { + border-bottom:1px solid #9eadc0; + width:100%; +} +.contentContainer ul li table, .classUseContainer ul li table, .constantValuesContainer ul li table { + width:100%; +} +.contentContainer .description table, .contentContainer .details table { + border-bottom:none; +} +.contentContainer ul li table th.colOne, .contentContainer ul li table th.colFirst, .contentContainer ul li table th.colLast, .classUseContainer ul li table th, .constantValuesContainer ul li table th, .contentContainer ul li table td.colOne, .contentContainer ul li table td.colFirst, .contentContainer ul li table td.colLast, .classUseContainer ul li table td, .constantValuesContainer ul li table td{ + vertical-align:top; + padding-right:20px; +} +.contentContainer ul li table th.colLast, .classUseContainer ul li table th.colLast,.constantValuesContainer ul li table th.colLast, +.contentContainer ul li table td.colLast, .classUseContainer ul li table td.colLast,.constantValuesContainer ul li table td.colLast, +.contentContainer ul li table th.colOne, .classUseContainer ul li table th.colOne, +.contentContainer ul li table td.colOne, .classUseContainer ul li table td.colOne { + padding-right:3px; +} +.overviewSummary caption, .packageSummary caption, .contentContainer ul.blockList li.blockList caption, .summary caption, .classUseContainer caption, .constantValuesContainer caption { + position:relative; + text-align:left; + background-repeat:no-repeat; + color:#FFFFFF; + font-weight:bold; + clear:none; + overflow:hidden; + padding:0px; + margin:0px; +} +caption a:link, caption a:hover, caption a:active, caption a:visited { + color:#FFFFFF; +} +.overviewSummary caption span, .packageSummary caption span, .contentContainer ul.blockList li.blockList caption span, .summary caption span, .classUseContainer caption span, .constantValuesContainer caption span { + white-space:nowrap; + padding-top:8px; + padding-left:8px; + display:block; + float:left; + background-image:url(resources/titlebar.gif); + height:18px; +} +.contentContainer ul.blockList li.blockList caption span.activeTableTab span { + white-space:nowrap; + padding-top:8px; + padding-left:8px; + display:block; + float:left; + background-image:url(resources/activetitlebar.gif); + height:18px; +} +.contentContainer ul.blockList li.blockList caption span.tableTab span { + white-space:nowrap; + padding-top:8px; + padding-left:8px; + display:block; + float:left; + background-image:url(resources/titlebar.gif); + height:18px; +} +.contentContainer ul.blockList li.blockList caption span.tableTab, .contentContainer ul.blockList li.blockList caption span.activeTableTab { + padding-top:0px; + padding-left:0px; + background-image:none; + float:none; + display:inline; +} +.overviewSummary .tabEnd, .packageSummary .tabEnd, .contentContainer ul.blockList li.blockList .tabEnd, .summary .tabEnd, .classUseContainer .tabEnd, .constantValuesContainer .tabEnd { + width:10px; + background-image:url(resources/titlebar_end.gif); + background-repeat:no-repeat; + background-position:top right; + position:relative; + float:left; +} +.contentContainer ul.blockList li.blockList .activeTableTab .tabEnd { + width:10px; + margin-right:5px; + background-image:url(resources/activetitlebar_end.gif); + background-repeat:no-repeat; + background-position:top right; + position:relative; + float:left; +} +.contentContainer ul.blockList li.blockList .tableTab .tabEnd { + width:10px; + margin-right:5px; + background-image:url(resources/titlebar_end.gif); + background-repeat:no-repeat; + background-position:top right; + position:relative; + float:left; +} +ul.blockList ul.blockList li.blockList table { + margin:0 0 12px 0px; + width:100%; +} +.tableSubHeadingColor { + background-color: #EEEEFF; +} +.altColor { + background-color:#eeeeef; +} +.rowColor { + background-color:#ffffff; +} +.overviewSummary td, .packageSummary td, .contentContainer ul.blockList li.blockList td, .summary td, .classUseContainer td, .constantValuesContainer td { + text-align:left; + padding:3px 3px 3px 7px; +} +th.colFirst, th.colLast, th.colOne, .constantValuesContainer th { + background:#dee3e9; + border-top:1px solid #9eadc0; + border-bottom:1px solid #9eadc0; + text-align:left; + padding:3px 3px 3px 7px; +} +td.colOne a:link, td.colOne a:active, td.colOne a:visited, td.colOne a:hover, td.colFirst a:link, td.colFirst a:active, td.colFirst a:visited, td.colFirst a:hover, td.colLast a:link, td.colLast a:active, td.colLast a:visited, td.colLast a:hover, .constantValuesContainer td a:link, .constantValuesContainer td a:active, .constantValuesContainer td a:visited, .constantValuesContainer td a:hover { + font-weight:bold; +} +td.colFirst, th.colFirst { + border-left:1px solid #9eadc0; + white-space:nowrap; +} +td.colLast, th.colLast { + border-right:1px solid #9eadc0; +} +td.colOne, th.colOne { + border-right:1px solid #9eadc0; + border-left:1px solid #9eadc0; +} +table.overviewSummary { + padding:0px; + margin-left:0px; +} +table.overviewSummary td.colFirst, table.overviewSummary th.colFirst, +table.overviewSummary td.colOne, table.overviewSummary th.colOne { + width:25%; + vertical-align:middle; +} +table.packageSummary td.colFirst, table.overviewSummary th.colFirst { + width:25%; + vertical-align:middle; +} +/* +Content styles +*/ +.description pre { + margin-top:0; +} +.deprecatedContent { + margin:0; + padding:10px 0; +} +.docSummary { + padding:0; +} +/* +Formatting effect styles +*/ +.sourceLineNo { + color:green; + padding:0 30px 0 0; +} +h1.hidden { + visibility:hidden; + overflow:hidden; + font-size:.9em; +} +.block { + display:block; + margin:3px 0 0 0; +} +.strong { + font-weight:bold; +} + + +/* +Spring +*/ + +pre.code { + background-color: #F8F8F8; + border: 1px solid #CCCCCC; + border-radius: 3px 3px 3px 3px; + overflow: auto; + padding: 10px; + margin: 4px 20px 2px 0px; +} + +pre.code code, pre.code code * { + font-size: 1em; +} + +pre.code code, pre.code code * { + padding: 0 !important; + margin: 0 !important; +} diff --git a/src/dist/changelog.txt b/src/dist/changelog.txt index 70defd90dd..d033dfbc53 100644 --- a/src/dist/changelog.txt +++ b/src/dist/changelog.txt @@ -6,11 +6,17 @@ http://www.springsource.org Changes in version 3.2.2 (2013-03-07) -------------------------------------- +* official support for Hibernate 4.2 * ConfigurationClassPostProcessor consistently uses ClassLoader, not loading core JDK annotations via ASM (SPR-10249) +* ConfigurationClassPostProcessor allows for overriding of scoped-proxy bean definitions (SPR-10265) +* added "maximumAutoGrowSize" property to SpelParserConfiguration (SPR-10229) +* SQLErrorCodeSQLExceptionTranslator tries to find SQLException with actual error code among causes (SPR-10260) * DefaultMessageListenerContainer invokes specified ExceptionListener for recovery exceptions as well (SPR-10230) * DefaultMessageListenerContainer logs recovery failures at error level and exposes "isRecovering()" method (SPR-10230) * MediaType throws dedicated InvalidMediaTypeException instead of generic IllegalArgumentException (SPR-10226) +* marked spring-web module as 'distributable' in order for session replication to work on Tomcat (SPR-10219) * consistent use of LinkedHashMaps and independent getAttributeNames Enumeration in Servlet/Portlet mocks (SPR-10224) +* support 'unless' expression for cache veto (SPR-8871) Changes in version 3.2.1 (2013-01-24) @@ -34,7 +40,7 @@ Changes in version 3.2.1 (2013-01-24) * MessageSourceResourceBundle overrides JDK 1.6 containsKey method, avoiding NPE in getKeys (SPR-10136) * SpringValidationAdapter properly detects invalid value for JSR-303 field-level bean constraints (SPR-9332) * SpringBeanAutowiringInterceptor eagerly releases BeanFactory if post-construction fails (SPR-10013) -* added "exposeAccessContext" flag JndiRmiClientInterceptor/ProxyFactoryBean (for WebLogic; SPR-9428) +* added "exposeAccessContext" flag to JndiRmiClientInterceptor/ProxyFactoryBean (for WebLogic; SPR-9428) * MBeanExporter does not log warnings for manually unregistered MBeans (SPR-9451) * MBeanInfoAssembler impls expose actual method parameter names if possible (SPR-9985) * AbstractCacheManager accepts no caches defined, allowing for EHCache default cache setup (SPR-7955) diff --git a/src/reference/docbook/cache.xml b/src/reference/docbook/cache.xml index 08d2f34c0e..2ed4ab2b3f 100644 --- a/src/reference/docbook/cache.xml +++ b/src/reference/docbook/cache.xml @@ -152,6 +152,13 @@ public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) + + In addition the conditional parameter, the unless parameter can be used to veto the adding of a value to the cache. Unlike + conditional, unless SpEL expressions are evalulated after the method has been called. Expanding + on the previous example - perhaps we only want to cache paperback books: + +

@@ -218,6 +225,13 @@ public Book findBook(String name)]]> stands for the argument index (starting from 0). iban or a0 (one can also use p0 or ]]> notation as an alias). + + result + evaluation context + The result of the method call (the value to be cached). Only available in 'unless' expressions and 'cache evict' + expression (when beforeInvocation is false). + #result + diff --git a/src/reference/docbook/scheduling.xml b/src/reference/docbook/scheduling.xml index 6c5b7fbe83..a30a0f7d99 100644 --- a/src/reference/docbook/scheduling.xml +++ b/src/reference/docbook/scheduling.xml @@ -840,9 +840,9 @@ public class ExampleJob extends QuartzJobBean { object. Of course, we still need to schedule the jobs themselves. This is done using triggers and a SchedulerFactoryBean. Several triggers are - available within Quartz. Spring offers two subclassed triggers with - convenient defaults: CronTriggerBean and - SimpleTriggerBean. + available within Quartz and Spring offers two Quartz FactoryBean + implementations with convenient defaults: CronTriggerFactoryBean and + SimpleTriggerFactoryBean. Triggers need to be scheduled. Spring offers a SchedulerFactoryBean that exposes triggers to be @@ -851,7 +851,7 @@ public class ExampleJob extends QuartzJobBean { Find below a couple of examples: - <bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean"> + <bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean"> <!-- see the example of method invoking job above --> <property name="jobDetail" ref="jobDetail" /> <!-- 10 seconds --> @@ -860,7 +860,7 @@ public class ExampleJob extends QuartzJobBean { <property name="repeatInterval" value="50000" /> </bean> -<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean"> +<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"> <property name="jobDetail" ref="exampleJob" /> <!-- run every morning at 6 AM --> <property name="cronExpression" value="0 0 6 * * ?" />