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 a7063ed2..c7188d5d 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
@@ -1,342 +1,342 @@
-/*
- * Copyright 2004-2008 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.springframework.webflow.engine;
-
-import java.io.IOException;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-import org.springframework.core.style.ToStringCreator;
-import org.springframework.util.Assert;
-import org.springframework.webflow.context.ExternalContext;
-import org.springframework.webflow.definition.TransitionDefinition;
-import org.springframework.webflow.execution.Event;
-import org.springframework.webflow.execution.FlowExecutionException;
-import org.springframework.webflow.execution.RequestContext;
-import org.springframework.webflow.execution.View;
-import org.springframework.webflow.execution.ViewFactory;
-
-/**
- * A view state is a state that issues a response to the user, for example, for soliciting form input. To accomplish
- * this, a ViewState delegates to a {@link ViewFactory}.
- *
- * @see ViewFactory
- *
- * @author Keith Donald
- * @author Erwin Vervaet
- */
-public class ViewState extends TransitionableState {
-
- /**
- * The list of actions to be executed before the view is rendered.
- */
- private ActionList renderActionList = new ActionList();
-
- /**
- * A factory for creating and restoring the view rendered by this view state.
- */
- private ViewFactory viewFactory;
-
- /**
- * The set of view variables created by this view state.
- */
- private Map variables = new LinkedHashMap();
-
- /**
- * Whether or not a redirect should occur before the view is rendered.
- */
- private Boolean redirect;
-
- /**
- * Whether or not the view should render as a popup.
- */
- private boolean popup;
-
- /**
- * Create a new view state.
- * @param flow the owning flow
- * @param id the state identifier (must be unique to the flow)
- * @param viewFactory the view factory
- * @throws IllegalArgumentException when this state cannot be added to given flow, e.g. because the id is not unique
- */
- public ViewState(Flow flow, String id, ViewFactory viewFactory) throws IllegalArgumentException {
- super(flow, id);
- Assert.notNull(viewFactory, "The view factory is required");
- this.viewFactory = viewFactory;
- }
-
- // implementing StateDefinition
-
- public boolean isViewState() {
- return true;
- }
-
- /**
- * Adds a view variable.
- * @param variable the variable
- */
- public void addVariable(ViewVariable variable) {
- variables.put(variable.getName(), variable);
- }
-
- /**
- * Adds a set of view variables.
- * @param variables the variables
- */
- public void addVariables(ViewVariable[] variables) {
- for (int i = 0; i < variables.length; i++) {
- addVariable(variables[i]);
- }
- }
-
- /**
- * Returns the view variable with the given name.
- * @param name the name of the variable
- */
- public ViewVariable getVariable(String name) {
- return (ViewVariable) variables.get(name);
- }
-
- /**
- * Returns the configured view variables.
- */
- public ViewVariable[] getVariables() {
- return (ViewVariable[]) variables.values().toArray(new ViewVariable[variables.size()]);
- }
-
- /**
- * Returns whether this view state should request a flow execution redirect when entered.
- */
- public boolean getRedirect() {
- return redirect.booleanValue();
- }
-
- /**
- * Sets whether this view state should requests a flow execution redirect when entered.
- * @param redirect the redirect flag
- */
- public void setRedirect(Boolean redirect) {
- this.redirect = redirect;
- }
-
- /**
- * Returns whether this view state should render as a popup.
- */
- public boolean getPopup() {
- return popup;
- }
-
- /**
- * Sets whether this view state should render as a popup.
- * @param popup the popup flag
- */
- public void setPopup(boolean popup) {
- this.popup = popup;
- }
-
- /**
- * Returns the view factory.
- */
- public ViewFactory getViewFactory() {
- return viewFactory;
- }
-
- /**
- * Returns the list of actions executable by this view state on entry and on refresh. The returned list is mutable.
- * @return the state action list
- */
- public ActionList getRenderActionList() {
- return renderActionList;
- }
-
- protected void doPreEntryActions(RequestControlContext context) throws FlowExecutionException {
- createVariables(context);
- }
-
- protected void doEnter(RequestControlContext context) throws FlowExecutionException {
- context.assignFlowExecutionKey();
- ExternalContext externalContext = context.getExternalContext();
- if (externalContext.isResponseComplete()) {
- if (!externalContext.isResponseCompleteFlowExecutionRedirect()) {
- clearFlash(context);
- }
- } else {
- if (shouldRedirect(context)) {
- context.getExternalContext().requestFlowExecutionRedirect();
- if (popup) {
- context.getExternalContext().requestRedirectInPopup();
- }
- } else {
- View view = viewFactory.getView(context);
- context.setCurrentView(view);
- render(context, view);
- }
- }
- }
-
- public void resume(RequestControlContext context) {
- restoreVariables(context);
- View view = viewFactory.getView(context);
- context.setCurrentView(view);
- if (view.userEventQueued()) {
- boolean stateExited = handleEvent(view, context);
- if (!stateExited) {
- ExternalContext externalContext = context.getExternalContext();
- if (externalContext.isResponseComplete()) {
- if (externalContext.isResponseCompleteFlowExecutionRedirect()) {
- context.getFlashScope().put(View.USER_EVENT_STATE_ATTRIBUTE, view.getUserEventState());
- } else {
- clearFlash(context);
- }
- } else {
- if (externalContext.isAjaxRequest()) {
- render(context, view);
- } else {
- if (shouldRedirect(context)) {
- context.getFlashScope().put(View.USER_EVENT_STATE_ATTRIBUTE, view.getUserEventState());
- externalContext.requestFlowExecutionRedirect();
- } else {
- render(context, view);
- }
- }
- }
- }
- } else {
- refresh(view, context);
- }
- }
-
- private boolean handleEvent(View view, RequestControlContext context) {
- view.processUserEvent();
- if (view.hasFlowEvent()) {
- Event event = view.getFlowEvent();
- if (logger.isDebugEnabled()) {
- logger.debug("Event '" + event.getId() + "' returned from view " + view);
- }
- return context.handleEvent(event);
- } else {
- return false;
- }
- }
-
- private void refresh(View view, RequestControlContext context) {
- ExternalContext externalContext = context.getExternalContext();
- if (externalContext.isResponseComplete()) {
- clearFlash(context);
- } else {
- render(context, view);
- }
- }
-
- public void exit(RequestControlContext context) {
- super.exit(context);
- updateHistory(context);
- destroyVariables(context);
- context.setCurrentView(null);
- }
-
- // internal helpers
-
- private void createVariables(RequestContext context) {
- Iterator it = variables.values().iterator();
- while (it.hasNext()) {
- ViewVariable variable = (ViewVariable) it.next();
- if (logger.isDebugEnabled()) {
- logger.debug("Creating " + variable);
- }
- variable.create(context);
- }
- }
-
- private boolean shouldRedirect(RequestControlContext context) {
- if (redirect != null) {
- return redirect.booleanValue();
- } else {
- return context.getRedirectOnPause();
- }
- }
-
- private void render(RequestControlContext context, View view) throws ViewRenderingException {
- if (logger.isDebugEnabled()) {
- logger.debug("Rendering + " + view);
- logger.debug(" Flash scope = " + context.getFlashScope());
- logger.debug(" Messages = " + context.getMessageContext());
- }
- context.viewRendering(view);
- renderActionList.execute(context);
- try {
- view.render();
- } catch (IOException e) {
- throw new ViewRenderingException(getOwner().getId(), getId(), view, e);
- }
- clearFlash(context);
- context.getExternalContext().recordResponseComplete();
- context.viewRendered(view);
- }
-
- private void clearFlash(RequestContext context) {
- context.getFlashScope().clear();
- context.getMessageContext().clearMessages();
- }
-
- private void restoreVariables(RequestContext context) {
- Iterator it = variables.values().iterator();
- while (it.hasNext()) {
- ViewVariable variable = (ViewVariable) it.next();
- if (logger.isDebugEnabled()) {
- logger.debug("Restoring " + variable);
-
- }
- variable.restore(context);
- }
- }
-
- private void updateHistory(RequestControlContext context) {
- TransitionDefinition transition = context.getCurrentTransition();
- History history = (History) transition.getAttributes().get("history");
- if (history == null || history == History.PRESERVE) {
- View currentView = context.getCurrentView();
- if (currentView != null) {
- currentView.saveState();
- }
- context.updateCurrentFlowExecutionSnapshot();
- } else if (history == History.DISCARD) {
- context.removeCurrentFlowExecutionSnapshot();
- } else if (history == History.INVALIDATE) {
- context.removeAllFlowExecutionSnapshots();
- }
- }
-
- private void destroyVariables(RequestContext context) {
- Iterator it = variables.values().iterator();
- while (it.hasNext()) {
- ViewVariable variable = (ViewVariable) it.next();
- if (logger.isDebugEnabled()) {
- logger.debug("Destroying " + variable);
-
- }
- variable.destroy(context);
- }
- }
-
- protected void appendToString(ToStringCreator creator) {
- super.appendToString(creator);
- creator.append("viewFactory", viewFactory).append("variables", variables).append("redirect", redirect).append(
- "popup", popup);
- }
-
+/*
+ * Copyright 2004-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.webflow.engine;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.springframework.core.style.ToStringCreator;
+import org.springframework.util.Assert;
+import org.springframework.webflow.context.ExternalContext;
+import org.springframework.webflow.definition.TransitionDefinition;
+import org.springframework.webflow.execution.Event;
+import org.springframework.webflow.execution.FlowExecutionException;
+import org.springframework.webflow.execution.RequestContext;
+import org.springframework.webflow.execution.View;
+import org.springframework.webflow.execution.ViewFactory;
+
+/**
+ * A view state is a state that issues a response to the user, for example, for soliciting form input. To accomplish
+ * this, a ViewState delegates to a {@link ViewFactory}.
+ *
+ * @see ViewFactory
+ *
+ * @author Keith Donald
+ * @author Erwin Vervaet
+ */
+public class ViewState extends TransitionableState {
+
+ /**
+ * The list of actions to be executed before the view is rendered.
+ */
+ private ActionList renderActionList = new ActionList();
+
+ /**
+ * A factory for creating and restoring the view rendered by this view state.
+ */
+ private ViewFactory viewFactory;
+
+ /**
+ * The set of view variables created by this view state.
+ */
+ private Map variables = new LinkedHashMap();
+
+ /**
+ * Whether or not a redirect should occur before the view is rendered.
+ */
+ private Boolean redirect;
+
+ /**
+ * Whether or not the view should render as a popup.
+ */
+ private boolean popup;
+
+ /**
+ * Create a new view state.
+ * @param flow the owning flow
+ * @param id the state identifier (must be unique to the flow)
+ * @param viewFactory the view factory
+ * @throws IllegalArgumentException when this state cannot be added to given flow, e.g. because the id is not unique
+ */
+ public ViewState(Flow flow, String id, ViewFactory viewFactory) throws IllegalArgumentException {
+ super(flow, id);
+ Assert.notNull(viewFactory, "The view factory is required");
+ this.viewFactory = viewFactory;
+ }
+
+ // implementing StateDefinition
+
+ public boolean isViewState() {
+ return true;
+ }
+
+ /**
+ * Adds a view variable.
+ * @param variable the variable
+ */
+ public void addVariable(ViewVariable variable) {
+ variables.put(variable.getName(), variable);
+ }
+
+ /**
+ * Adds a set of view variables.
+ * @param variables the variables
+ */
+ public void addVariables(ViewVariable[] variables) {
+ for (int i = 0; i < variables.length; i++) {
+ addVariable(variables[i]);
+ }
+ }
+
+ /**
+ * Returns the view variable with the given name.
+ * @param name the name of the variable
+ */
+ public ViewVariable getVariable(String name) {
+ return (ViewVariable) variables.get(name);
+ }
+
+ /**
+ * Returns the configured view variables.
+ */
+ public ViewVariable[] getVariables() {
+ return (ViewVariable[]) variables.values().toArray(new ViewVariable[variables.size()]);
+ }
+
+ /**
+ * Returns whether this view state should request a flow execution redirect when entered.
+ */
+ public boolean getRedirect() {
+ return redirect.booleanValue();
+ }
+
+ /**
+ * Sets whether this view state should requests a flow execution redirect when entered.
+ * @param redirect the redirect flag
+ */
+ public void setRedirect(Boolean redirect) {
+ this.redirect = redirect;
+ }
+
+ /**
+ * Returns whether this view state should render as a popup.
+ */
+ public boolean getPopup() {
+ return popup;
+ }
+
+ /**
+ * Sets whether this view state should render as a popup.
+ * @param popup the popup flag
+ */
+ public void setPopup(boolean popup) {
+ this.popup = popup;
+ }
+
+ /**
+ * Returns the view factory.
+ */
+ public ViewFactory getViewFactory() {
+ return viewFactory;
+ }
+
+ /**
+ * Returns the list of actions executable by this view state on entry and on refresh. The returned list is mutable.
+ * @return the state action list
+ */
+ public ActionList getRenderActionList() {
+ return renderActionList;
+ }
+
+ protected void doPreEntryActions(RequestControlContext context) throws FlowExecutionException {
+ createVariables(context);
+ }
+
+ protected void doEnter(RequestControlContext context) throws FlowExecutionException {
+ context.assignFlowExecutionKey();
+ ExternalContext externalContext = context.getExternalContext();
+ if (externalContext.isResponseComplete()) {
+ if (!externalContext.isResponseCompleteFlowExecutionRedirect()) {
+ clearFlash(context);
+ }
+ } else {
+ if (shouldRedirect(context)) {
+ context.getExternalContext().requestFlowExecutionRedirect();
+ if (popup) {
+ context.getExternalContext().requestRedirectInPopup();
+ }
+ } else {
+ View view = viewFactory.getView(context);
+ context.setCurrentView(view);
+ render(context, view);
+ }
+ }
+ }
+
+ public void resume(RequestControlContext context) {
+ restoreVariables(context);
+ View view = viewFactory.getView(context);
+ context.setCurrentView(view);
+ if (view.userEventQueued()) {
+ boolean stateExited = handleEvent(view, context);
+ if (!stateExited) {
+ ExternalContext externalContext = context.getExternalContext();
+ if (externalContext.isResponseComplete()) {
+ if (externalContext.isResponseCompleteFlowExecutionRedirect()) {
+ context.getFlashScope().put(View.USER_EVENT_STATE_ATTRIBUTE, view.getUserEventState());
+ } else {
+ clearFlash(context);
+ }
+ } else {
+ if (externalContext.isAjaxRequest()) {
+ render(context, view);
+ } else {
+ if (shouldRedirect(context)) {
+ context.getFlashScope().put(View.USER_EVENT_STATE_ATTRIBUTE, view.getUserEventState());
+ externalContext.requestFlowExecutionRedirect();
+ } else {
+ render(context, view);
+ }
+ }
+ }
+ }
+ } else {
+ refresh(view, context);
+ }
+ }
+
+ private boolean handleEvent(View view, RequestControlContext context) {
+ view.processUserEvent();
+ if (view.hasFlowEvent()) {
+ Event event = view.getFlowEvent();
+ if (logger.isDebugEnabled()) {
+ logger.debug("Event '" + event.getId() + "' returned from view " + view);
+ }
+ return context.handleEvent(event);
+ } else {
+ return false;
+ }
+ }
+
+ private void refresh(View view, RequestControlContext context) {
+ ExternalContext externalContext = context.getExternalContext();
+ if (externalContext.isResponseComplete()) {
+ clearFlash(context);
+ } else {
+ render(context, view);
+ }
+ }
+
+ public void exit(RequestControlContext context) {
+ super.exit(context);
+ updateHistory(context);
+ destroyVariables(context);
+ context.setCurrentView(null);
+ }
+
+ // internal helpers
+
+ private void createVariables(RequestContext context) {
+ Iterator it = variables.values().iterator();
+ while (it.hasNext()) {
+ ViewVariable variable = (ViewVariable) it.next();
+ if (logger.isDebugEnabled()) {
+ logger.debug("Creating " + variable);
+ }
+ variable.create(context);
+ }
+ }
+
+ private boolean shouldRedirect(RequestControlContext context) {
+ if (redirect != null) {
+ return redirect.booleanValue();
+ } else {
+ return context.getRedirectOnPause();
+ }
+ }
+
+ private void render(RequestControlContext context, View view) throws ViewRenderingException {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Rendering + " + view);
+ logger.debug(" Flash scope = " + context.getFlashScope());
+ logger.debug(" Messages = " + context.getMessageContext());
+ }
+ context.viewRendering(view);
+ renderActionList.execute(context);
+ try {
+ view.render();
+ } catch (IOException e) {
+ throw new ViewRenderingException(getOwner().getId(), getId(), view, e);
+ }
+ clearFlash(context);
+ context.getExternalContext().recordResponseComplete();
+ context.viewRendered(view);
+ }
+
+ private void clearFlash(RequestContext context) {
+ context.getFlashScope().clear();
+ context.getMessageContext().clearMessages();
+ }
+
+ private void restoreVariables(RequestContext context) {
+ Iterator it = variables.values().iterator();
+ while (it.hasNext()) {
+ ViewVariable variable = (ViewVariable) it.next();
+ if (logger.isDebugEnabled()) {
+ logger.debug("Restoring " + variable);
+
+ }
+ variable.restore(context);
+ }
+ }
+
+ private void updateHistory(RequestControlContext context) {
+ TransitionDefinition transition = context.getCurrentTransition();
+ History history = (History) transition.getAttributes().get("history");
+ if (history == null || history == History.PRESERVE) {
+ View currentView = context.getCurrentView();
+ if (currentView != null && shouldRedirect(context)) {
+ currentView.saveState();
+ }
+ context.updateCurrentFlowExecutionSnapshot();
+ } else if (history == History.DISCARD) {
+ context.removeCurrentFlowExecutionSnapshot();
+ } else if (history == History.INVALIDATE) {
+ context.removeAllFlowExecutionSnapshots();
+ }
+ }
+
+ private void destroyVariables(RequestContext context) {
+ Iterator it = variables.values().iterator();
+ while (it.hasNext()) {
+ ViewVariable variable = (ViewVariable) it.next();
+ if (logger.isDebugEnabled()) {
+ logger.debug("Destroying " + variable);
+
+ }
+ variable.destroy(context);
+ }
+ }
+
+ protected void appendToString(ToStringCreator creator) {
+ super.appendToString(creator);
+ creator.append("viewFactory", viewFactory).append("variables", variables).append("redirect", redirect).append(
+ "popup", popup);
+ }
+
}
\ No newline at end of file
diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/StubViewFactory.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/StubViewFactory.java
index 4f2f63be..f2295769 100644
--- a/spring-webflow/src/test/java/org/springframework/webflow/engine/StubViewFactory.java
+++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/StubViewFactory.java
@@ -63,7 +63,7 @@ public class StubViewFactory implements ViewFactory {
}
public void saveState() {
-
+ context.getFlowScope().put("saveStateCalled", Boolean.TRUE);
}
}
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 9bb9c723..70157a5f 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
@@ -215,7 +215,7 @@ public class ViewStateTests extends TestCase {
assertEquals("Restored", ((TestBean) context.getViewScope().get("foo")).datum1);
}
- public void testResumeViewStateForEventWithTransitionStateExited() {
+ public void testResumeViewStateForEventWithTransitionFlowEnded() {
Flow flow = new Flow("myFlow");
StubViewFactory viewFactory = new StubViewFactory();
ViewState state = new ViewState(flow, "viewState", viewFactory);
@@ -233,6 +233,42 @@ public class ViewStateTests extends TestCase {
assertTrue(testAction.isExecuted());
}
+ public void testResumeViewStateForEventWithTransitionStateExited() {
+ Flow flow = new Flow("myFlow");
+ StubViewFactory viewFactory = new StubViewFactory();
+ ViewState state = new ViewState(flow, "viewState", viewFactory);
+ state.getTransitionSet().add(new Transition(on("submit"), to("next")));
+ ViewState next = new ViewState(flow, "next", viewFactory);
+ MockRequestControlContext context = new MockRequestControlContext(flow);
+ context.setAlwaysRedirectOnPause(true);
+ state.enter(context);
+ context = new MockRequestControlContext(context.getFlowExecutionContext());
+ context.putRequestParameter("_eventId", "submit");
+ state.resume(context);
+ assertTrue(context.getExternalContext().isResponseComplete());
+ assertTrue(context.getFlowExecutionContext().isActive());
+ assertSame(next, context.getCurrentState());
+ assertTrue(context.getFlowScope().contains("saveStateCalled"));
+ }
+
+ public void testResumeViewStateForEventWithTransitionStateExitedNoRedirect() {
+ Flow flow = new Flow("myFlow");
+ StubViewFactory viewFactory = new StubViewFactory();
+ ViewState state = new ViewState(flow, "viewState", viewFactory);
+ state.getTransitionSet().add(new Transition(on("submit"), to("next")));
+ ViewState next = new ViewState(flow, "next", viewFactory);
+ MockRequestControlContext context = new MockRequestControlContext(flow);
+ context.setAlwaysRedirectOnPause(false);
+ state.enter(context);
+ context = new MockRequestControlContext(context.getFlowExecutionContext());
+ context.putRequestParameter("_eventId", "submit");
+ state.resume(context);
+ assertTrue(context.getExternalContext().isResponseComplete());
+ assertTrue(context.getFlowExecutionContext().isActive());
+ assertSame(next, context.getCurrentState());
+ assertFalse(context.getFlowScope().contains("saveStateCalled"));
+ }
+
public void testResumeViewStateForEventStateNotExitedNonAjax() {
Flow flow = new Flow("myFlow");
StubViewFactory viewFactory = new StubViewFactory();