-
+
Name
- #{hotel.name}
+ #{h.name}
Address
- #{hotel.address}
+ #{h.address}
City, State
- #{hotel.city}, #{hotel.state}, #{hotel.country}
+ #{h.city}, #{h.state}, #{h.country}
Zip
- #{hotel.zip}
+ #{h.zip}
Action
diff --git a/spring-webflow/src/main/java/org/springframework/webflow/definition/StateDefinition.java b/spring-webflow/src/main/java/org/springframework/webflow/definition/StateDefinition.java
index 286d600f..8627c46b 100644
--- a/spring-webflow/src/main/java/org/springframework/webflow/definition/StateDefinition.java
+++ b/spring-webflow/src/main/java/org/springframework/webflow/definition/StateDefinition.java
@@ -37,4 +37,10 @@ public interface StateDefinition extends Annotated {
* @return the state identifier
*/
public String getId();
+
+ /**
+ * Returns true if this state is a view state.
+ * @return true if a view state, false otherwise
+ */
+ public boolean isViewState();
}
\ No newline at end of file
diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/RequestControlContext.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/RequestControlContext.java
index 62a26df4..bea026ad 100644
--- a/spring-webflow/src/main/java/org/springframework/webflow/engine/RequestControlContext.java
+++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/RequestControlContext.java
@@ -117,4 +117,14 @@ public interface RequestControlContext extends RequestContext {
*/
public boolean getAlwaysRedirectOnPause();
+ /**
+ * Initialize view scope. Called by view states when they enter.
+ */
+ public void initViewScope();
+
+ /**
+ * Destroy view-scope. Called by view-states when they exit.
+ */
+ public void destroyViewScope();
+
}
\ No newline at end of file
diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/State.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/State.java
index b9d7f305..f6b52011 100644
--- a/spring-webflow/src/main/java/org/springframework/webflow/engine/State.java
+++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/State.java
@@ -98,6 +98,10 @@ public abstract class State extends AnnotatedObject implements StateDefinition {
return id;
}
+ public boolean isViewState() {
+ return false;
+ }
+
// implementation specific
/**
diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/ViewState.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/ViewState.java
index c38654b4..0f170de1 100644
--- a/spring-webflow/src/main/java/org/springframework/webflow/engine/ViewState.java
+++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/ViewState.java
@@ -77,6 +77,12 @@ public class ViewState extends TransitionableState {
this.viewFactory = viewFactory;
}
+ // implementing StateDefinition
+
+ public boolean isViewState() {
+ return true;
+ }
+
/**
* Adds a view variable.
* @param variable the variable
@@ -156,6 +162,7 @@ public class ViewState extends TransitionableState {
}
protected void doPreEntryActions(RequestControlContext context) throws FlowExecutionException {
+ context.initViewScope();
createVariables(context);
}
@@ -196,6 +203,7 @@ public class ViewState extends TransitionableState {
public void exit(RequestControlContext context) {
destroyVariables(context);
+ context.destroyViewScope();
super.exit(context);
}
diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/ViewVariable.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/ViewVariable.java
index 55a325d3..b8278a76 100644
--- a/spring-webflow/src/main/java/org/springframework/webflow/engine/ViewVariable.java
+++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/ViewVariable.java
@@ -58,7 +58,7 @@ public class ViewVariable extends AnnotatedObject {
*/
public void create(RequestContext context) {
Object value = valueFactory.createInitialValue(context);
- context.getFlowScope().put(name, value);
+ context.getViewScope().put(name, value);
}
/**
@@ -67,7 +67,7 @@ public class ViewVariable extends AnnotatedObject {
* @param context the executing flow
*/
public void restore(RequestContext context) {
- Object value = context.getFlowScope().get(name);
+ Object value = context.getViewScope().get(name);
valueFactory.restoreReferences(value, context);
}
@@ -76,6 +76,6 @@ public class ViewVariable extends AnnotatedObject {
* @param context the executing flow
*/
public Object destroy(RequestContext context) {
- return context.getFlowScope().remove(name);
+ return context.getViewScope().remove(name);
}
}
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 b120920f..a5cd8275 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
@@ -48,6 +48,8 @@ import org.springframework.webflow.execution.FlowSession;
*/
class RequestControlContextImpl implements RequestControlContext {
+ private static final String FLOW_VIEW_MAP_ATTRIBUTE = "flowViewMap";
+
/**
* The owning flow execution carrying out this request.
*/
@@ -116,6 +118,21 @@ class RequestControlContextImpl implements RequestControlContext {
return flowExecution.getFlashScope();
}
+ public boolean inViewState() {
+ return flowExecution.isActive() && getCurrentState() != null && getCurrentState().isViewState();
+ }
+
+ public MutableAttributeMap getViewScope() throws IllegalStateException {
+ if (!flowExecution.isActive()) {
+ throw new IllegalStateException("This flow is not active");
+ }
+ if (!getCurrentState().isViewState()) {
+ throw new IllegalStateException("The current state '" + getCurrentState().getId() + "' of this flow '"
+ + getActiveFlow().getId() + "' is not a view state - view scope not accessible");
+ }
+ return (MutableAttributeMap) getFlowScope().get(FLOW_VIEW_MAP_ATTRIBUTE);
+ }
+
public MutableAttributeMap getFlowScope() {
return flowExecution.getActiveSession().getScope();
}
@@ -196,6 +213,14 @@ class RequestControlContextImpl implements RequestControlContext {
flowExecution.start(flow, input, this);
}
+ public void initViewScope() {
+ getFlowScope().put(FLOW_VIEW_MAP_ATTRIBUTE, new LocalAttributeMap());
+ }
+
+ public void destroyViewScope() {
+ getFlowScope().remove(FLOW_VIEW_MAP_ATTRIBUTE);
+ }
+
public boolean handleEvent(Event event) throws FlowExecutionException {
this.lastEvent = event;
return flowExecution.handleEvent(event, this);
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 99d5b163..ea992b93 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
@@ -89,6 +89,22 @@ public interface RequestContext {
*/
public MutableAttributeMap getFlashScope();
+ /**
+ * Returns true if the flow is currently active and in a view state. When in a view state {@link #getViewScope()},
+ * can be safely called.
+ * @see #getViewScope()
+ * @return true if in a view state, false if not
+ */
+ public boolean inViewState();
+
+ /**
+ * Returns a mutable map for accessing and/or setting attributes in view scope. View scoped attributes exist for
+ * the life of the current view state.
+ * @return the view scope
+ * @throws IllegalStateException this flow is not in a view-state
+ */
+ public MutableAttributeMap getViewScope() throws IllegalStateException;
+
/**
* Returns a mutable map for accessing and/or setting attributes in flow scope. Flow scoped attributes exist for
* the life of the active flow session.
@@ -177,4 +193,5 @@ public interface RequestContext {
* @return the flow execution URL
*/
public String getFlowExecutionUrl() throws IllegalStateException;
+
}
\ No newline at end of file
diff --git a/spring-webflow/src/main/java/org/springframework/webflow/expression/el/ImplicitFlowVariableELResolver.java b/spring-webflow/src/main/java/org/springframework/webflow/expression/el/ImplicitFlowVariableELResolver.java
index d7d2dfe9..bab6d123 100644
--- a/spring-webflow/src/main/java/org/springframework/webflow/expression/el/ImplicitFlowVariableELResolver.java
+++ b/spring-webflow/src/main/java/org/springframework/webflow/expression/el/ImplicitFlowVariableELResolver.java
@@ -151,6 +151,7 @@ public class ImplicitFlowVariableELResolver extends ELResolver {
vars.put("requestParameters", requestContextResolver);
vars.put("requestScope", requestContextResolver);
vars.put("flashScope", requestContextResolver);
+ vars.put("viewScope", requestContextResolver);
vars.put("flowScope", requestContextResolver);
vars.put("conversationScope", requestContextResolver);
vars.put("messageContext", requestContextResolver);
diff --git a/spring-webflow/src/main/java/org/springframework/webflow/expression/el/ScopeSearchingELResolver.java b/spring-webflow/src/main/java/org/springframework/webflow/expression/el/ScopeSearchingELResolver.java
index a696e102..832bac45 100644
--- a/spring-webflow/src/main/java/org/springframework/webflow/expression/el/ScopeSearchingELResolver.java
+++ b/spring-webflow/src/main/java/org/springframework/webflow/expression/el/ScopeSearchingELResolver.java
@@ -67,6 +67,9 @@ public class ScopeSearchingELResolver extends ELResolver {
} else if (requestContext.getFlashScope().contains(attributeName)) {
elContext.setPropertyResolved(true);
return requestContext.getFlashScope().get(attributeName).getClass();
+ } else if (requestContext.inViewState() && requestContext.getViewScope().contains(attributeName)) {
+ elContext.setPropertyResolved(true);
+ return requestContext.getViewScope().get(attributeName).getClass();
} else if (requestContext.getFlowScope().contains(attributeName)) {
elContext.setPropertyResolved(true);
return requestContext.getFlowScope().get(attributeName).getClass();
@@ -96,6 +99,12 @@ public class ScopeSearchingELResolver extends ELResolver {
}
elContext.setPropertyResolved(true);
return requestContext.getFlashScope().get(attributeName);
+ } else if (requestContext.inViewState() && requestContext.getViewScope().contains(attributeName)) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Successfully resolved view scoped variable '" + property + "'");
+ }
+ elContext.setPropertyResolved(true);
+ return requestContext.getViewScope().get(attributeName);
} else if (requestContext.getFlowScope().contains(attributeName)) {
if (logger.isDebugEnabled()) {
logger.debug("Successfully resolved flow scoped variable '" + property + "'");
@@ -125,6 +134,9 @@ public class ScopeSearchingELResolver extends ELResolver {
} else if (requestContext.getFlashScope().contains(attributeName)) {
elContext.setPropertyResolved(true);
return false;
+ } else if (requestContext.inViewState() && requestContext.getViewScope().contains(attributeName)) {
+ elContext.setPropertyResolved(true);
+ return false;
} else if (requestContext.getFlowScope().contains(attributeName)) {
elContext.setPropertyResolved(true);
return false;
@@ -154,6 +166,12 @@ public class ScopeSearchingELResolver extends ELResolver {
}
elContext.setPropertyResolved(true);
requestContext.getFlashScope().put(attributeName, value);
+ } else if (requestContext.inViewState() && requestContext.getViewScope().contains(attributeName)) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Successfully resolved view scoped variable '" + property + "'");
+ }
+ elContext.setPropertyResolved(true);
+ requestContext.getViewScope().put(attributeName, value);
} else if (requestContext.getFlowScope().contains(attributeName)) {
if (logger.isDebugEnabled()) {
logger.debug("Successfully resolved flow scoped variable '" + property + "'");
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 13f5c0fe..3aadc3d8 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
@@ -45,6 +45,8 @@ import org.springframework.webflow.execution.RequestContext;
*/
public class MockRequestContext implements RequestContext {
+ protected static final String FLOW_VIEW_MAP_ATTRIBUTE = "flowViewMap";
+
private FlowExecutionContext flowExecutionContext;
private ExternalContext externalContext;
@@ -126,6 +128,21 @@ public class MockRequestContext implements RequestContext {
return getMockFlowExecutionContext().getFlashScope();
}
+ public boolean inViewState() {
+ return getFlowExecutionContext().isActive() && getCurrentState() != null && getCurrentState().isViewState();
+ }
+
+ public MutableAttributeMap getViewScope() throws IllegalStateException {
+ if (!getFlowExecutionContext().isActive()) {
+ throw new IllegalStateException("This flow is not active");
+ }
+ if (!getCurrentState().isViewState()) {
+ throw new IllegalStateException("The current state '" + getCurrentState().getId() + "' of this flow '"
+ + getActiveFlow().getId() + "' is not a view state - view scope not accessible");
+ }
+ return (MutableAttributeMap) getFlowScope().get(FLOW_VIEW_MAP_ATTRIBUTE);
+ }
+
public MutableAttributeMap getFlowScope() {
return getFlowExecutionContext().getActiveSession().getScope();
}
diff --git a/spring-webflow/src/main/java/org/springframework/webflow/test/MockRequestControlContext.java b/spring-webflow/src/main/java/org/springframework/webflow/test/MockRequestControlContext.java
index c45bd958..da7d7f55 100644
--- a/spring-webflow/src/main/java/org/springframework/webflow/test/MockRequestControlContext.java
+++ b/spring-webflow/src/main/java/org/springframework/webflow/test/MockRequestControlContext.java
@@ -15,6 +15,7 @@
*/
package org.springframework.webflow.test;
+import org.springframework.webflow.core.collection.LocalAttributeMap;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.engine.Flow;
import org.springframework.webflow.engine.RequestControlContext;
@@ -73,6 +74,14 @@ public class MockRequestControlContext extends MockRequestContext implements Req
flow.start(this, input);
}
+ public void initViewScope() {
+ getFlowScope().put(FLOW_VIEW_MAP_ATTRIBUTE, new LocalAttributeMap());
+ }
+
+ public void destroyViewScope() {
+ getFlowScope().remove(FLOW_VIEW_MAP_ATTRIBUTE);
+ }
+
public boolean handleEvent(Event event) {
setLastEvent(event);
return ((Flow) getActiveFlow()).handleEvent(this);
diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/ViewStateTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/ViewStateTests.java
index 957b4af3..d4ab66e0 100644
--- a/spring-webflow/src/test/java/org/springframework/webflow/engine/ViewStateTests.java
+++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/ViewStateTests.java
@@ -55,7 +55,7 @@ public class ViewStateTests extends TestCase {
}));
MockRequestControlContext context = new MockRequestControlContext(flow);
state.enter(context);
- assertEquals("bar", context.getFlowScope().getString("foo"));
+ assertEquals("bar", context.getViewScope().getString("foo"));
assertTrue("Render not called", context.getFlowScope().contains("renderCalled"));
assertFalse(context.getFlowExecutionRedirectSent());
}
@@ -151,7 +151,7 @@ public class ViewStateTests extends TestCase {
state.resume(context);
assertTrue("Render not called", context.getFlowScope().contains("renderCalled"));
assertFalse(context.getFlowExecutionRedirectSent());
- assertEquals("Restored", ((TestBean) context.getFlowScope().get("foo")).datum1);
+ assertEquals("Restored", ((TestBean) context.getViewScope().get("foo")).datum1);
}
public void testResumeViewStateForEvent() {
@@ -202,7 +202,7 @@ public class ViewStateTests extends TestCase {
new ViewState(flow, "next", viewFactory);
MockRequestControlContext context = new MockRequestControlContext(flow);
state.enter(context);
- assertTrue(context.getFlowScope().contains("foo"));
+ assertTrue(context.getViewScope().contains("foo"));
context = new MockRequestControlContext(context.getFlowExecutionContext());
context.putRequestParameter("_eventId", "submit");
state.resume(context);
diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/ViewVariableTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/ViewVariableTests.java
index b40e235c..a6461fd6 100644
--- a/spring-webflow/src/test/java/org/springframework/webflow/engine/ViewVariableTests.java
+++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/ViewVariableTests.java
@@ -3,7 +3,7 @@ package org.springframework.webflow.engine;
import junit.framework.TestCase;
import org.springframework.webflow.execution.RequestContext;
-import org.springframework.webflow.test.MockRequestContext;
+import org.springframework.webflow.test.MockRequestControlContext;
public class ViewVariableTests extends TestCase {
@@ -18,9 +18,12 @@ public class ViewVariableTests extends TestCase {
public void restoreReferences(Object value, RequestContext context) {
}
});
- MockRequestContext context = new MockRequestContext();
+ Flow flow = new Flow("flow");
+ ViewState view = new ViewState(flow, "view", new StubViewFactory());
+ MockRequestControlContext context = new MockRequestControlContext(flow);
+ view.enter(context);
var.create(context);
- assertEquals("bar", context.getFlowScope().get("foo"));
+ assertEquals("bar", context.getViewScope().get("foo"));
}
public void testDestroyVariable() {
@@ -32,11 +35,14 @@ public class ViewVariableTests extends TestCase {
public void restoreReferences(Object value, RequestContext context) {
}
});
- MockRequestContext context = new MockRequestContext();
+ Flow flow = new Flow("flow");
+ ViewState view = new ViewState(flow, "view", new StubViewFactory());
+ MockRequestControlContext context = new MockRequestControlContext(flow);
+ view.enter(context);
var.create(context);
- assertEquals("bar", context.getFlowScope().get("foo"));
+ assertEquals("bar", context.getViewScope().get("foo"));
var.destroy(context);
- assertFalse(context.getFlowScope().contains("foo"));
+ assertFalse(context.getViewScope().contains("foo"));
}
public void testRestoreVariable() {
@@ -50,10 +56,13 @@ public class ViewVariableTests extends TestCase {
assertEquals("bar", value);
}
});
- MockRequestContext context = new MockRequestContext();
+ Flow flow = new Flow("flow");
+ ViewState view = new ViewState(flow, "view", new StubViewFactory());
+ MockRequestControlContext context = new MockRequestControlContext(flow);
+ view.enter(context);
var.create(context);
var.restore(context);
- assertEquals("bar", context.getFlowScope().get("foo"));
+ assertEquals("bar", context.getViewScope().get("foo"));
assertTrue(restoreCalled);
}