From 7a98c145768ea3f0027c6c38dd70e0343fb26a14 Mon Sep 17 00:00:00 2001 From: Jeremy Grelle Date: Wed, 29 Jul 2009 16:06:03 +0000 Subject: [PATCH] Optimizations for state saving with no redirect --- .../webflow/engine/ViewState.java | 682 +++++++++--------- .../webflow/engine/StubViewFactory.java | 2 +- .../webflow/engine/ViewStateTests.java | 38 +- 3 files changed, 379 insertions(+), 343 deletions(-) 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();