diff --git a/spring-webflow/src/main/java/org/springframework/webflow/core/collection/LocalAttributeMap.java b/spring-webflow/src/main/java/org/springframework/webflow/core/collection/LocalAttributeMap.java index 71f4eeb9..72ed3a09 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/core/collection/LocalAttributeMap.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/core/collection/LocalAttributeMap.java @@ -21,6 +21,7 @@ import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.Collection; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; import org.springframework.binding.collection.MapAccessor; @@ -232,6 +233,18 @@ public class LocalAttributeMap implements MutableAttributeMap, Serializable { return this; } + public MutableAttributeMap removeAll(MutableAttributeMap attributes) { + if (attributes == null) { + return this; + } + Iterator it = attributes.asMap().keySet().iterator(); + Map internal = getMapInternal(); + while (it.hasNext()) { + internal.remove(it.next()); + } + return this; + } + public Object remove(String attributeName) { return getMapInternal().remove(attributeName); } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/core/collection/MutableAttributeMap.java b/spring-webflow/src/main/java/org/springframework/webflow/core/collection/MutableAttributeMap.java index 3c0208cf..a2658020 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/core/collection/MutableAttributeMap.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/core/collection/MutableAttributeMap.java @@ -45,6 +45,13 @@ public interface MutableAttributeMap extends AttributeMap { */ public MutableAttributeMap putAll(AttributeMap attributes); + /** + * Remove all attributes in the map provided from this map. + * @param attributes the attributes to remove from this map + * @return this, to support call chaining + */ + public MutableAttributeMap removeAll(MutableAttributeMap attributes); + /** * Remove an attribute from this map. * @param attributeName the name of the attribute to remove diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/AnnotatedAction.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/AnnotatedAction.java index 8ab56ca9..e2053ca3 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/AnnotatedAction.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/AnnotatedAction.java @@ -18,7 +18,7 @@ package org.springframework.webflow.engine; import org.springframework.core.style.ToStringCreator; import org.springframework.util.Assert; import org.springframework.util.StringUtils; -import org.springframework.webflow.core.collection.AttributeMap; +import org.springframework.webflow.action.MultiAction; import org.springframework.webflow.execution.Action; import org.springframework.webflow.execution.Event; import org.springframework.webflow.execution.RequestContext; @@ -43,7 +43,6 @@ public class AnnotatedAction extends AnnotatedObject implements Action { *

* The name attribute is often used as a qualifier for an action's result event, and is typically used to allow the * flow to respond to a specific action's outcome within a larger action execution chain. - * @see ActionState */ public static final String NAME_ATTRIBUTE = "name"; @@ -51,8 +50,7 @@ public class AnnotatedAction extends AnnotatedObject implements Action { * The action execution method attribute ("method"). *

* The method property is a hint about what method should be invoked; for example, the name of a specific target - * method on a {@link org.springframework.webflow.action.MultiAction multi action}. - * @see ActionState + * method on a {@link MultiAction multi-action}. */ public static final String METHOD_ATTRIBUTE = "method"; @@ -139,14 +137,12 @@ public class AnnotatedAction extends AnnotatedObject implements Action { } public Event execute(RequestContext context) throws Exception { - AttributeMap originalAttributes = getAttributes(); try { - context.setAttributes(getAttributes()); + context.getAttributes().putAll(getAttributes()); Event result = getTargetAction().execute(context); return postProcessResult(result); } finally { - // restore original attributes - context.setAttributes(originalAttributes); + context.getAttributes().removeAll(getAttributes()); } } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/RequestControlContextImpl.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/RequestControlContextImpl.java index 74db4265..b2aaf1e8 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/RequestControlContextImpl.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/RequestControlContextImpl.java @@ -18,8 +18,6 @@ package org.springframework.webflow.engine.impl; import org.springframework.binding.message.MessageContext; import org.springframework.core.style.ToStringCreator; import org.springframework.webflow.context.ExternalContext; -import org.springframework.webflow.core.collection.AttributeMap; -import org.springframework.webflow.core.collection.CollectionUtils; import org.springframework.webflow.core.collection.LocalAttributeMap; import org.springframework.webflow.core.collection.MutableAttributeMap; import org.springframework.webflow.core.collection.ParameterMap; @@ -70,10 +68,9 @@ class RequestControlContextImpl implements RequestControlContext { private LocalAttributeMap requestScope = new LocalAttributeMap(); /** - * Holder for contextual properties describing the currently executing request; never null, initially empty and - * immutable. + * Holder for contextual properties describing the currently executing request; never null, initially empty. */ - private AttributeMap attributes = CollectionUtils.EMPTY_ATTRIBUTE_MAP; + private LocalAttributeMap attributes = new LocalAttributeMap(); /** * The current event being processed by this flow; initially null. @@ -157,18 +154,10 @@ class RequestControlContextImpl implements RequestControlContext { return currentTransition; } - public AttributeMap getAttributes() { + public MutableAttributeMap getAttributes() { return attributes; } - public void setAttributes(AttributeMap attributes) { - if (attributes == null) { - this.attributes = CollectionUtils.EMPTY_ATTRIBUTE_MAP; - } else { - this.attributes = attributes; - } - } - // implementing RequestControlContext public String getFlowExecutionUrl() { diff --git a/spring-webflow/src/main/java/org/springframework/webflow/execution/RequestContext.java b/spring-webflow/src/main/java/org/springframework/webflow/execution/RequestContext.java index 63504c61..f298d09b 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/execution/RequestContext.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/execution/RequestContext.java @@ -17,7 +17,6 @@ package org.springframework.webflow.execution; import org.springframework.binding.message.MessageContext; import org.springframework.webflow.context.ExternalContext; -import org.springframework.webflow.core.collection.AttributeMap; import org.springframework.webflow.core.collection.MutableAttributeMap; import org.springframework.webflow.core.collection.ParameterMap; import org.springframework.webflow.definition.FlowDefinition; @@ -175,17 +174,11 @@ public interface RequestContext { public TransitionDefinition getCurrentTransition(); /** - * Returns a context map for accessing arbitrary attributes about the state of the current request. These attributes - * may be used to influence flow execution behavior. - * @return the current attributes of this request, or empty if not set + * Returns a context map for accessing attributes about the state of the current request. These attributes may be + * used to influence flow execution behavior. + * @return the current attributes of this request, or empty if none are set */ - public AttributeMap getAttributes(); - - /** - * Set the contextual attributes describing the state of this request. Overwrites any pre-existing collection. - * @param attributes the attributes - */ - public void setAttributes(AttributeMap attributes); + public MutableAttributeMap getAttributes(); /** * Returns the context-relative URL of this flow execution. Needed by response writers that write out the URL of diff --git a/spring-webflow/src/main/java/org/springframework/webflow/test/MockRequestContext.java b/spring-webflow/src/main/java/org/springframework/webflow/test/MockRequestContext.java index cf7d9c7f..822bee78 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/test/MockRequestContext.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/test/MockRequestContext.java @@ -166,14 +166,10 @@ public class MockRequestContext implements RequestContext { return currentTransition; } - public AttributeMap getAttributes() { + public MutableAttributeMap getAttributes() { return attributes; } - public void setAttributes(AttributeMap attributes) { - this.attributes.replaceWith(attributes); - } - public String getFlowExecutionUrl() { if (flowExecutionContext.getKey() == null) { throw new IllegalStateException( diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/AnnotedActionTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/AnnotedActionTests.java index f24529db..6693d1a7 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/engine/AnnotedActionTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/AnnotedActionTests.java @@ -18,6 +18,7 @@ package org.springframework.webflow.engine; import junit.framework.TestCase; import org.springframework.webflow.action.AbstractAction; +import org.springframework.webflow.engine.AnnotatedAction; import org.springframework.webflow.execution.Event; import org.springframework.webflow.execution.RequestContext; import org.springframework.webflow.execution.TestAction; @@ -47,6 +48,22 @@ public class AnnotedActionTests extends TestCase { } }); assertEquals("success", action.execute(context).getId()); + assertEquals(0, context.getAttributes().size()); + } + + public void testExecuteWithChainOfCustomAttributes() throws Exception { + AnnotatedAction action2 = new AnnotatedAction(action); + action2.getAttributes().put("attr2", "value"); + action.getAttributes().put("attr", "value"); + action.setTargetAction(new AbstractAction() { + protected Event doExecute(RequestContext context) throws Exception { + assertEquals("value", context.getAttributes().getString("attr")); + assertEquals("value", context.getAttributes().getString("attr2")); + return success(); + } + }); + assertEquals("success", action2.execute(context).getId()); + assertEquals(0, context.getAttributes().size()); } public void testExecuteWithName() throws Exception {