From 6a56e9678aa953185e599a79ac016d93405728d9 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Tue, 3 Aug 2010 11:15:20 +0000 Subject: [PATCH] SWF-1365 Add support for partial state saving in JSF 2, ensure one FacesContext instance per request, avoid redirect by default when remaining in same view state (originally introduced in commit point 2365. --- spring-faces/.classpath | 4 +- spring-faces/ivy.xml | 4 +- spring-faces/pom.xml | 14 +- .../faces/webflow/FlowFacesContext.java | 5 +- .../FlowFacesContextLifecycleListener.java | 48 +++++ .../faces/webflow/FlowRenderKit.java | 55 ++++++ .../webflow/FlowViewResponseStateManager.java | 181 ++++++++++++++++++ .../faces/webflow/FlowViewStateManager.java | 79 +++++--- .../faces/webflow/Jsf2FlowFacesContext.java | 21 +- .../faces/webflow/JsfView.java | 36 ++-- .../faces/webflow/JsfViewFactory.java | 97 +++++----- .../main/resources/META-INF/faces-config.xml | 1 + .../ProgressiveCommandLinkRendererTests.java | 15 +- .../faces/webflow/JSFMockHelper.java | 14 ++ .../faces/webflow/JsfAjaxHandlerTests.java | 12 +- .../faces/webflow/JsfViewTests.java | 3 - .../webflow/MockBaseFacesContextFactory.java | 21 +- .../booking-faces/.classpath | 1 + spring-webflow-samples/booking-faces/ivy.xml | 16 +- spring-webflow-samples/booking-faces/pom.xml | 6 - .../webflow/samples/booking/Booking.java | 8 - .../webapp/WEB-INF/config/webflow-config.xml | 10 +- .../main/webapp/WEB-INF/faces-config-12.xml | 17 -- .../webapp/WEB-INF/layouts/standard.xhtml | 16 +- .../src/main/webapp/WEB-INF/web.xml | 23 +-- .../config/hotelbooking-portlet-config.xml | 4 + .../webflow/engine/ViewState.java | 34 +++- .../engine/builder/FlowArtifactFactory.java | 5 +- 28 files changed, 530 insertions(+), 220 deletions(-) create mode 100644 spring-faces/src/main/java/org/springframework/faces/webflow/FlowFacesContextLifecycleListener.java create mode 100644 spring-faces/src/main/java/org/springframework/faces/webflow/FlowRenderKit.java create mode 100644 spring-faces/src/main/java/org/springframework/faces/webflow/FlowViewResponseStateManager.java delete mode 100644 spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/faces-config-12.xml diff --git a/spring-faces/.classpath b/spring-faces/.classpath index 271552c4..b59bd22d 100644 --- a/spring-faces/.classpath +++ b/spring-faces/.classpath @@ -8,8 +8,8 @@ - - + + diff --git a/spring-faces/ivy.xml b/spring-faces/ivy.xml index 28f3082f..89b9d96f 100644 --- a/spring-faces/ivy.xml +++ b/spring-faces/ivy.xml @@ -42,8 +42,8 @@ - - + + diff --git a/spring-faces/pom.xml b/spring-faces/pom.xml index 3e3b75c9..caa8d05a 100644 --- a/spring-faces/pom.xml +++ b/spring-faces/pom.xml @@ -57,12 +57,6 @@ spring-webflow ${project.version} - - com.sun.facelets - jsf-facelets - 1.1.14 - true - org.richfaces.framework richfaces-api @@ -82,15 +76,15 @@ provided - javax.faces + com.sun.faces jsf-api - 2.0 + 2.0.3 provided - javax.faces + com.sun.faces jsf-impl - 2.0.2 + 2.0.3 provided diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/FlowFacesContext.java b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowFacesContext.java index 96df2c61..1c8bbd74 100644 --- a/spring-faces/src/main/java/org/springframework/faces/webflow/FlowFacesContext.java +++ b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowFacesContext.java @@ -58,6 +58,8 @@ public class FlowFacesContext extends FacesContext { private FlowFacesContextMessageDelegate messageDelegate; + private ExternalContext externalContext; + /** * The base FacesContext delegate */ @@ -77,6 +79,7 @@ public class FlowFacesContext extends FacesContext { this.context = context; this.delegate = delegate; this.messageDelegate = new FlowFacesContextMessageDelegate(context); + this.externalContext = new FlowExternalContext(delegate.getExternalContext()); setCurrentInstance(this); } @@ -160,7 +163,7 @@ public class FlowFacesContext extends FacesContext { } public ExternalContext getExternalContext() { - return new FlowExternalContext(delegate.getExternalContext()); + return externalContext; } public RenderKit getRenderKit() { diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/FlowFacesContextLifecycleListener.java b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowFacesContextLifecycleListener.java new file mode 100644 index 00000000..8932832f --- /dev/null +++ b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowFacesContextLifecycleListener.java @@ -0,0 +1,48 @@ +/* + * Copyright 2004-2010 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.faces.webflow; + +import javax.faces.context.FacesContext; + +import org.springframework.webflow.execution.FlowExecutionListener; +import org.springframework.webflow.execution.FlowExecutionListenerAdapter; +import org.springframework.webflow.execution.RequestContext; + +/** + * A {@link FlowExecutionListener} that creates a {@link FlowFacesContext} instance when a flow request is submitted and + * releases it when the request has been processed. + * + * @author Rossen Stoyanchev + */ +public class FlowFacesContextLifecycleListener extends FlowExecutionListenerAdapter { + + /** + * Creates a new instance of {@link FlowFacesContext} that is then available for the duration of the request. + * @param context the current flow request context + */ + public void requestSubmitted(RequestContext context) { + FlowFacesContext.newInstance(context, FlowLifecycle.newInstance()); + } + + /** + * Releases the current {@link FlowFacesContext} instance. + * @param context the source of the event + */ + public void requestProcessed(RequestContext context) { + FacesContext.getCurrentInstance().release(); + } + +} diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/FlowRenderKit.java b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowRenderKit.java new file mode 100644 index 00000000..bcf3420b --- /dev/null +++ b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowRenderKit.java @@ -0,0 +1,55 @@ +/* + * Copyright 2004-2010 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.faces.webflow; + +/** + * A render kit implementation that ensures use of Web Flow's FlowViewResponseStateManager, which takes over reading and + * writing JSF state and manages that in Web Flow's view scope. The FlowViewResponseStateManager is plugged in only in a + * JSF 2 environment. + * + * @author Rossen Stoyanchev + * @since 2.2.0 + */ +import javax.faces.render.RenderKit; +import javax.faces.render.RenderKitWrapper; +import javax.faces.render.ResponseStateManager; + +public class FlowRenderKit extends RenderKitWrapper { + + private RenderKit delegate; + + private FlowViewResponseStateManager responseStateManager; + + public FlowRenderKit(RenderKit delegate) { + this.delegate = delegate; + if (JsfRuntimeInformation.isAtLeastJsf20()) { + this.responseStateManager = new FlowViewResponseStateManager(delegate.getResponseStateManager()); + } + } + + public RenderKit getWrapped() { + return delegate; + } + + /** + * Returns an instance of {@link FlowViewResponseStateManager} in a JSF 2 environment or returns the delegates's + * ResponseStateManager instance otherwise. + */ + public ResponseStateManager getResponseStateManager() { + return (JsfRuntimeInformation.isAtLeastJsf20()) ? responseStateManager : delegate.getResponseStateManager(); + } + +} diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/FlowViewResponseStateManager.java b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowViewResponseStateManager.java new file mode 100644 index 00000000..25deb785 --- /dev/null +++ b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowViewResponseStateManager.java @@ -0,0 +1,181 @@ +/* + * Copyright 2004-2010 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.faces.webflow; + +import java.io.IOException; +import java.io.Writer; + +import javax.faces.context.FacesContext; +import javax.faces.context.ResponseWriter; +import javax.faces.render.RenderKitFactory; +import javax.faces.render.ResponseStateManager; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.webflow.execution.RequestContext; +import org.springframework.webflow.execution.RequestContextHolder; + +/** + *

+ * A custom ResponseStateManager that writes JSF state to a Web Flow managed view-scoped variable. This class is plugged + * in via {@link FlowRenderKit} in JSF 2 runtime environments only. + *

+ * + *

+ * In JSF 2 where a partial state saving algorithm is used, Web Flow delegates to the JSF 2 runtime to handle state + * saving. However, an instance of this class plugged in via {@link FlowRenderKit} will ensure that state is saved in a + * Web Flow managed view-scoped variable. + *

+ * + * @author Rossen Stoyanchev + * @since 2.2.0 + */ +public class FlowViewResponseStateManager extends ResponseStateManager { + + private static final Log logger = LogFactory.getLog(FlowViewResponseStateManager.class); + + private ResponseStateManager delegate; + + private char[] stateFieldStart = ("".toCharArray(); + + public FlowViewResponseStateManager(ResponseStateManager delegate) { + this.delegate = delegate; + } + + /** + *

+ * Wraps state in an instance of {@link FlowSerializedView} and stores it in view scope. + *

+ * + *

+ * Also complies with the contract for {@link ResponseStateManager#writeState(FacesContext, Object)} by writing the + * "javax.faces.ViewState" and optionally the "javax.faces.RenderKitId" hidden input fields to the response. + *

+ */ + @Override + public void writeState(FacesContext facesContext, Object state) throws IOException { + if (!JsfUtils.isFlowRequest()) { + delegate.writeState(facesContext, state); + } + FlowSerializedView view = null; + if (state instanceof FlowSerializedView) { + view = (FlowSerializedView) state; + } else { + Object[] serializedState = (Object[]) state; + view = new FlowSerializedView(facesContext.getViewRoot().getViewId(), serializedState[0], serializedState[1]); + } + RequestContext requestContext = RequestContextHolder.getRequestContext(); + requestContext.getViewScope().put(FlowViewStateManager.SERIALIZED_VIEW_STATE, view); + + ResponseWriter writer = facesContext.getResponseWriter(); + writeViewStateField(facesContext, writer); + writeRenderKitIdField(facesContext, writer); + } + + /** + *

+ * Retrieves the state from view scope as an instance of {@link FlowSerializedView} and turns it to an array before + * returning. + *

+ */ + @Override + public Object getState(FacesContext facesContext, String viewId) { + if (!JsfUtils.isFlowRequest()) { + return delegate.getState(facesContext, viewId); + } + RequestContext requestContext = RequestContextHolder.getRequestContext(); + FlowSerializedView view = (FlowSerializedView) requestContext.getViewScope().get( + FlowViewStateManager.SERIALIZED_VIEW_STATE); + Object[] state = null; + if (view == null) { + logger.debug("No matching view in view scope"); + } else { + state = new Object[] { view.getTreeStructure(), view.getComponentState() }; + } + return state; + } + + /** + * This method returns the flow execution key to be used as the value for the "javax.faces.ViewState" hidden input + * field. The value of this key is not important because JSF state is stored in a Web Flow managed view scoped + * variable. However the presence of the view state parameter alone is important for triggering actions. Hence we + * return the most logical value, which is the flow execution key. + */ + @Override + public String getViewState(FacesContext facesContext, Object state) { + if (!JsfUtils.isFlowRequest()) { + return delegate.getViewState(facesContext, state); + } + return getFlowExecutionKey(); + } + + // ------------------- Delegation methods ------------------// + + @SuppressWarnings( { "deprecation" }) + @Override + public Object getTreeStructureToRestore(FacesContext context, String viewId) { + return delegate.getTreeStructureToRestore(context, viewId); + } + + @SuppressWarnings( { "deprecation" }) + @Override + public Object getComponentStateToRestore(FacesContext context) { + return delegate.getComponentStateToRestore(context); + } + + // ------------------- Private helper methods ------------------// + + private String getFlowExecutionKey() { + RequestContext requestContext = RequestContextHolder.getRequestContext(); + return requestContext.getFlowExecutionContext().getKey().toString(); + } + + /** + * See comments on {@link ResponseStateManager#VIEW_STATE_PARAM}. + * + * @param context the FacesContext for the current request + * @param writer the ResponseWriter to write to + * @throws IOException if an error occurs writing to the client + */ + private void writeViewStateField(FacesContext context, Writer writer) throws IOException { + writer.write(stateFieldStart); + writer.write(getFlowExecutionKey()); + writer.write(stateFieldEnd); + } + + /** + * See comments on ResponseStateManager.RENDER_KIT_ID_PARAM in + * {@link ResponseStateManager#writeState(FacesContext, Object)}. + * + * @param context the FacesContext for the current request + * @param writer the ResponseWriter to write to + * @throws IOException if an error occurs writing to the client + */ + private void writeRenderKitIdField(FacesContext context, ResponseWriter writer) throws IOException { + String result = context.getApplication().getDefaultRenderKitId(); + if (result != null && !RenderKitFactory.HTML_BASIC_RENDER_KIT.equals(result)) { + writer.startElement("input", context.getViewRoot()); + writer.writeAttribute("type", "hidden", "type"); + writer.writeAttribute("name", ResponseStateManager.RENDER_KIT_ID_PARAM, "name"); + writer.writeAttribute("value", result, "value"); + writer.endElement("input"); + } + } + +} diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/FlowViewStateManager.java b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowViewStateManager.java index 9c81325b..fcbacb15 100644 --- a/spring-faces/src/main/java/org/springframework/faces/webflow/FlowViewStateManager.java +++ b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowViewStateManager.java @@ -15,6 +15,8 @@ */ package org.springframework.faces.webflow; +import static org.springframework.faces.webflow.JsfRuntimeInformation.isAtLeastJsf20; + import java.io.IOException; import javax.faces.application.StateManager; @@ -35,7 +37,7 @@ public class FlowViewStateManager extends StateManager { private static final Log logger = LogFactory.getLog(FlowViewStateManager.class); - private static final String SERIALIZED_VIEW_STATE = "flowSerializedViewState"; + protected static final String SERIALIZED_VIEW_STATE = "flowSerializedViewState"; private StateManager delegate; @@ -136,49 +138,78 @@ public class FlowViewStateManager extends StateManager { if (!JsfUtils.isFlowRequest()) { return delegate.saveSerializedView(context); } - FlowSerializedView view = (FlowSerializedView) saveView(context); - return new javax.faces.application.StateManager.SerializedView(view.getTreeStructure(), view - .getComponentState()); + Object state = saveView(context); + if (state instanceof FlowSerializedView) { + FlowSerializedView serializedState = (FlowSerializedView) state; + return new javax.faces.application.StateManager.SerializedView(serializedState.getTreeStructure(), + serializedState.getComponentState()); + } else { + Object[] serializedState = (Object[]) state; + return new javax.faces.application.StateManager.SerializedView(serializedState[0], serializedState[1]); + } } /** - * JSF 1.2 version of state saving + *

+ * JSF 1.2 (or higher) version of state saving. + *

+ * + *

+ * In JSF 2 where a partial state saving algorithm is used, this method merely delegates to the next + * ViewStateManager. Thus partial state saving is handled by the JSF 2 runtime. However, a + * {@link FlowViewResponseStateManager} plugged in via {@link FlowRenderKit} will ensure the state is saved in a Web + * Flow view-scoped variable. + *

*/ public Object saveView(FacesContext context) { if (context.getViewRoot().isTransient()) { return null; } - if (!JsfUtils.isFlowRequest()) { + if ((!JsfUtils.isFlowRequest()) || isAtLeastJsf20()) { return delegate.saveView(context); + } else { + RequestContext requestContext = RequestContextHolder.getRequestContext(); + if (logger.isDebugEnabled()) { + logger.debug("Saving view root '" + context.getViewRoot().getViewId() + "' in view scope"); + } + FlowSerializedView view = new FlowSerializedView(context.getViewRoot().getViewId(), + getTreeStructureToSave(context), getComponentStateToSave(context)); + requestContext.getViewScope().put(SERIALIZED_VIEW_STATE, view); + return view; } - RequestContext requestContext = RequestContextHolder.getRequestContext(); - if (logger.isDebugEnabled()) { - logger.debug("Saving view root '" + context.getViewRoot().getViewId() + "' in view scope"); - } - FlowSerializedView view = new FlowSerializedView(context.getViewRoot().getViewId(), - getTreeStructureToSave(context), getComponentStateToSave(context)); - requestContext.getViewScope().put(SERIALIZED_VIEW_STATE, view); - return view; } + /** + *

+ * In JSF 2 where a partial state saving algorithm is used, this method merely delegates to the next + * ViewStateManager. Thus partial state saving is handled by the JSF 2 runtime. However, a + * {@link FlowViewResponseStateManager} plugged in via {@link FlowRenderKit} will ensure the state is saved in a Web + * Flow view-scoped variable. + *

+ */ public UIViewRoot restoreView(FacesContext context, String viewId, String renderKitId) { - if (!JsfUtils.isFlowRequest()) { + if ((!JsfUtils.isFlowRequest()) || isAtLeastJsf20()) { return delegate.restoreView(context, viewId, renderKitId); + } else { + UIViewRoot viewRoot = restoreTreeStructure(context, viewId, renderKitId); + if (viewRoot != null) { + context.setViewRoot(viewRoot); + restoreComponentState(context, viewRoot, renderKitId); + } + return viewRoot; } - UIViewRoot viewRoot = restoreTreeStructure(context, viewId, renderKitId); - if (viewRoot != null) { - context.setViewRoot(viewRoot); - restoreComponentState(context, viewRoot, renderKitId); - } - return viewRoot; } @Override public String getViewState(FacesContext context) { + if (!JsfUtils.isFlowRequest()) { + return delegate.getViewState(context); + } /* - * Mojarra 2: PartialRequestContextImpl.renderState() invokes this method during Ajax request rendering. It is - * overridden to convert FlowSerializedView to an array containing tree structure and component state. The - * ResponseStateManager.getViewState() calls the ServerSideStateHelper, which expects the array. + * Mojarra 2: PartialRequestContextImpl.renderState() invokes this method during Ajax request rendering. We + * overridde it to convert FlowSerializedView state to an array before calling the + * ResponseStateManager.getViewState(), which in turn calls the ServerSideStateHelper and expects state to be an + * array. */ Object state = saveView(context); if (state != null) { diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/Jsf2FlowFacesContext.java b/spring-faces/src/main/java/org/springframework/faces/webflow/Jsf2FlowFacesContext.java index 742403de..fc4e5bcc 100644 --- a/spring-faces/src/main/java/org/springframework/faces/webflow/Jsf2FlowFacesContext.java +++ b/spring-faces/src/main/java/org/springframework/faces/webflow/Jsf2FlowFacesContext.java @@ -32,6 +32,8 @@ import javax.faces.context.PartialViewContext; import javax.faces.context.PartialViewContextFactory; import javax.faces.event.PhaseId; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.webflow.execution.RequestContext; /** @@ -41,6 +43,8 @@ import org.springframework.webflow.execution.RequestContext; */ public class Jsf2FlowFacesContext extends FlowFacesContext { + private ExternalContext externalContext; + /* * This partialViewContext duplicates the one FacesContextImpl because the constructor of FacesContextImpl calls * getPartialViewContext(), which causes it to be instantiated with an instance of FacesContextImpl. This leads to @@ -52,13 +56,15 @@ public class Jsf2FlowFacesContext extends FlowFacesContext { public Jsf2FlowFacesContext(RequestContext context, FacesContext delegate) { super(context, delegate); + this.externalContext = new Jsf2FlowExternalContext(getDelegate().getExternalContext()); + PartialViewContextFactory f = (PartialViewContextFactory) FactoryFinder .getFactory(FactoryFinder.PARTIAL_VIEW_CONTEXT_FACTORY); - partialViewContext = f.getPartialViewContext(this); + this.partialViewContext = f.getPartialViewContext(this); } public ExternalContext getExternalContext() { - return new Jsf2FlowExternalContext(getDelegate().getExternalContext()); + return externalContext; } // --------------- JSF 2.0 Pass-through delegate methods ------------------// @@ -128,10 +134,17 @@ public class Jsf2FlowFacesContext extends FlowFacesContext { protected class Jsf2FlowExternalContext extends FlowExternalContext { + Log logger = LogFactory.getLog(FlowExternalContext.class); + public Jsf2FlowExternalContext(ExternalContext delegate) { super(delegate); } + public void responseSendError(int statusCode, String message) throws IOException { + logger.debug("Sending error HTTP status code " + statusCode + " with message '" + message + "'"); + delegate.responseSendError(statusCode, message); + } + // --------------- JSF 2.0 Pass-through delegate methods ------------------// public String getContextName() { @@ -210,10 +223,6 @@ public class Jsf2FlowFacesContext extends FlowFacesContext { delegate.responseReset(); } - public void responseSendError(int statusCode, String message) throws IOException { - delegate.responseSendError(statusCode, message); - } - public void setResponseStatus(int statusCode) { delegate.setResponseStatus(statusCode); } diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/JsfView.java b/spring-faces/src/main/java/org/springframework/faces/webflow/JsfView.java index d9643d43..ed7cfb63 100644 --- a/spring-faces/src/main/java/org/springframework/faces/webflow/JsfView.java +++ b/spring-faces/src/main/java/org/springframework/faces/webflow/JsfView.java @@ -16,6 +16,7 @@ package org.springframework.faces.webflow; import static org.springframework.faces.webflow.JsfRuntimeInformation.isAtLeastJsf12; +import static org.springframework.faces.webflow.JsfRuntimeInformation.isLessThanJsf20; import java.io.IOException; import java.io.Serializable; @@ -79,7 +80,7 @@ public class JsfView implements View { * Performs the standard duties of the JSF RENDER_RESPONSE phase. */ public void render() throws IOException { - FacesContext facesContext = FlowFacesContext.newInstance(requestContext, facesLifecycle); + FacesContext facesContext = FlowFacesContext.getCurrentInstance(); if (facesContext.getResponseComplete()) { return; } @@ -90,7 +91,6 @@ public class JsfView implements View { } finally { logger.debug("View rendering complete"); facesContext.responseComplete(); - facesContext.release(); } } @@ -107,15 +107,11 @@ public class JsfView implements View { * INVOKE_APPLICATION. */ public void processUserEvent() { - FacesContext facesContext = FlowFacesContext.newInstance(requestContext, facesLifecycle); + FacesContext facesContext = FlowFacesContext.getCurrentInstance(); facesContext.setViewRoot(viewRoot); - try { - // Must respect these flags in case user set them during RESTORE_VIEW phase - if (!facesContext.getRenderResponse() && !facesContext.getResponseComplete()) { - facesLifecycle.execute(facesContext); - } - } finally { - facesContext.release(); + // Must respect these flags in case user set them during RESTORE_VIEW phase + if (!facesContext.getRenderResponse() && !facesContext.getResponseComplete()) { + facesLifecycle.execute(facesContext); } } @@ -124,22 +120,26 @@ public class JsfView implements View { * snapshot */ public void saveState() { - FacesContext facesContext = FlowFacesContext.newInstance(requestContext, facesLifecycle); + FacesContext facesContext = FlowFacesContext.getCurrentInstance(); if (viewRoot instanceof AjaxViewRoot) { facesContext.setViewRoot(((AjaxViewRoot) viewRoot).getOriginalViewRoot()); } else { facesContext.setViewRoot(viewRoot); } - try { - facesContext.getApplication().getStateManager().saveSerializedView(facesContext); - } finally { - facesContext.release(); - } + facesContext.getApplication().getStateManager().saveSerializedView(facesContext); } public Serializable getUserEventState() { - // Set the temporary UIViewRoot state so that it will be available across the redirect - return new ViewRootHolder(getViewRoot()); + if (isLessThanJsf20()) { + // Set the temporary UIViewRoot state so that it will be available across the redirect + return new ViewRootHolder(getViewRoot()); + } else { + // In JSF 2 the partial state saving algorithm attaches a system event listener to the UIViewRoot with + // a reference to the FacesContext instance. The FacesContext instance is released at end of each request. + // Hence, keeping the UIViewRoot across the redirect is not feasible. + logger.debug("User event state requested but not saved."); + return null; + } } public boolean hasFlowEvent() { diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/JsfViewFactory.java b/spring-faces/src/main/java/org/springframework/faces/webflow/JsfViewFactory.java index ae4e6594..1d9a4f45 100644 --- a/spring-faces/src/main/java/org/springframework/faces/webflow/JsfViewFactory.java +++ b/spring-faces/src/main/java/org/springframework/faces/webflow/JsfViewFactory.java @@ -17,7 +17,6 @@ package org.springframework.faces.webflow; import static org.springframework.faces.webflow.JsfRuntimeInformation.isAtLeastJsf12; import static org.springframework.faces.webflow.JsfRuntimeInformation.isAtLeastJsf20; -import static org.springframework.faces.webflow.JsfRuntimeInformation.isLessThanJsf20; import static org.springframework.faces.webflow.JsfRuntimeInformation.isPortletRequest; import java.util.Iterator; @@ -72,65 +71,61 @@ public class JsfViewFactory implements ViewFactory { * be rendered in the case of an executing transition. */ public View getView(RequestContext context) { - FacesContext facesContext = FlowFacesContext.newInstance(context, lifecycle); - try { - if (isAtLeastJsf20()) { - facesContext.setCurrentPhaseId(PhaseId.RESTORE_VIEW); + FacesContext facesContext = FlowFacesContext.getCurrentInstance(); + if (isAtLeastJsf20()) { + facesContext.setCurrentPhaseId(PhaseId.RESTORE_VIEW); + } + if (!facesContext.getRenderResponse()) { + // only publish a RESTORE_VIEW event if this is the first phase of the lifecycle + // this won't be true when this method is called after a transition from one view-state to another + JsfUtils.notifyBeforeListeners(PhaseId.RESTORE_VIEW, lifecycle, facesContext); + } + ViewHandler viewHandler = facesContext.getApplication().getViewHandler(); + if (isAtLeastJsf12() && (!isPortletRequest(facesContext))) { + viewHandler.initView(facesContext); + } + JsfView view; + String viewName = (String) viewIdExpression.getValue(context); + if (viewAlreadySet(facesContext, viewName)) { + if (logger.isDebugEnabled()) { + logger.debug("Existing view root found with id '" + facesContext.getViewRoot().getId() + "'"); } - if (!facesContext.getRenderResponse()) { - // only publish a RESTORE_VIEW event if this is the first phase of the lifecycle - // this won't be true when this method is called after a transition from one view-state to another - JsfUtils.notifyBeforeListeners(PhaseId.RESTORE_VIEW, lifecycle, facesContext); - } - ViewHandler viewHandler = facesContext.getApplication().getViewHandler(); - if (isAtLeastJsf12() && (!isPortletRequest(facesContext))) { - viewHandler.initView(facesContext); - } - JsfView view; - String viewName = (String) viewIdExpression.getValue(context); - if (viewAlreadySet(facesContext, viewName)) { - if (logger.isDebugEnabled()) { - logger.debug("Existing view root found with id '" + facesContext.getViewRoot().getId() + "'"); - } - UIViewRoot viewRoot = facesContext.getViewRoot(); - viewRoot.setLocale(context.getExternalContext().getLocale()); - processTree(facesContext, viewRoot); - view = createJsfView(facesContext.getViewRoot(), lifecycle, context); - } else { - if (context.inViewState()) { - UIViewRoot viewRoot = viewHandler.restoreView(facesContext, viewName); - if (viewRoot != null) { - if (logger.isDebugEnabled()) { - logger.debug("UIViewRoot restored for '" + viewName + "'"); - } - facesContext.setViewRoot(viewRoot); - processTree(facesContext, viewRoot); - view = createJsfView(viewRoot, lifecycle, context); - } else { - if (logger.isDebugEnabled()) { - logger.debug("Creating UIViewRoot from '" + viewName + "'"); - } - viewRoot = viewHandler.createView(facesContext, viewName); - facesContext.setViewRoot(viewRoot); - view = createJsfView(viewRoot, lifecycle, context); + UIViewRoot viewRoot = facesContext.getViewRoot(); + viewRoot.setLocale(context.getExternalContext().getLocale()); + processTree(facesContext, viewRoot); + view = createJsfView(facesContext.getViewRoot(), lifecycle, context); + } else { + if (context.inViewState()) { + UIViewRoot viewRoot = viewHandler.restoreView(facesContext, viewName); + if (viewRoot != null) { + if (logger.isDebugEnabled()) { + logger.debug("UIViewRoot restored for '" + viewName + "'"); } + facesContext.setViewRoot(viewRoot); + processTree(facesContext, viewRoot); + view = createJsfView(viewRoot, lifecycle, context); } else { if (logger.isDebugEnabled()) { - logger.debug("Creating transient UIViewRoot from '" + viewName + "'"); + logger.debug("Creating UIViewRoot from '" + viewName + "'"); } - UIViewRoot viewRoot = viewHandler.createView(facesContext, viewName); - viewRoot.setTransient(true); + viewRoot = viewHandler.createView(facesContext, viewName); facesContext.setViewRoot(viewRoot); view = createJsfView(viewRoot, lifecycle, context); } + } else { + if (logger.isDebugEnabled()) { + logger.debug("Creating transient UIViewRoot from '" + viewName + "'"); + } + UIViewRoot viewRoot = viewHandler.createView(facesContext, viewName); + viewRoot.setTransient(true); + facesContext.setViewRoot(viewRoot); + view = createJsfView(viewRoot, lifecycle, context); } - if (!facesContext.getRenderResponse()) { - JsfUtils.notifyAfterListeners(PhaseId.RESTORE_VIEW, lifecycle, facesContext); - } - return view; - } finally { - facesContext.release(); } + if (!facesContext.getRenderResponse()) { + JsfUtils.notifyAfterListeners(PhaseId.RESTORE_VIEW, lifecycle, facesContext); + } + return view; } private boolean viewAlreadySet(FacesContext facesContext, String viewName) { @@ -143,7 +138,7 @@ public class JsfViewFactory implements ViewFactory { } private JsfView createJsfView(UIViewRoot root, Lifecycle lifecycle, RequestContext context) { - if (isLessThanJsf20() && isSpringJavascriptAjaxRequest(context.getExternalContext())) { + if (isSpringJavascriptAjaxRequest(context.getExternalContext())) { root = new AjaxViewRoot(root); } return new JsfView(root, lifecycle, context); diff --git a/spring-faces/src/main/resources/META-INF/faces-config.xml b/spring-faces/src/main/resources/META-INF/faces-config.xml index 0ef11f11..c5a1cb66 100644 --- a/spring-faces/src/main/resources/META-INF/faces-config.xml +++ b/spring-faces/src/main/resources/META-INF/faces-config.xml @@ -88,6 +88,7 @@ HTML_BASIC + org.springframework.faces.webflow.FlowRenderKit javax.faces.Command diff --git a/spring-faces/src/test/java/org/springframework/faces/ui/ProgressiveCommandLinkRendererTests.java b/spring-faces/src/test/java/org/springframework/faces/ui/ProgressiveCommandLinkRendererTests.java index 93e1ec4c..cedb8036 100644 --- a/spring-faces/src/test/java/org/springframework/faces/ui/ProgressiveCommandLinkRendererTests.java +++ b/spring-faces/src/test/java/org/springframework/faces/ui/ProgressiveCommandLinkRendererTests.java @@ -1,13 +1,10 @@ package org.springframework.faces.ui; -import java.io.StringWriter; - import javax.faces.component.UIForm; import javax.faces.component.UIParameter; import junit.framework.TestCase; -import org.apache.myfaces.test.mock.MockResponseWriter; import org.springframework.faces.webflow.JSFMockHelper; public class ProgressiveCommandLinkRendererTests extends TestCase { @@ -16,15 +13,11 @@ public class ProgressiveCommandLinkRendererTests extends TestCase { ProgressiveCommandLinkRenderer renderer = new ProgressiveCommandLinkRenderer(); - StringWriter output = new StringWriter(); - public void setUp() throws Exception { jsf.setUp(); - jsf.facesContext().setResponseWriter(new MockResponseWriter(output, null, null)); } public void tearDown() throws Exception { - System.out.println(output); jsf.tearDown(); } @@ -47,7 +40,7 @@ public class ProgressiveCommandLinkRendererTests extends TestCase { jsf.facesContext().getResponseWriter().endElement("a"); - assertEquals(expected, output.toString()); + assertEquals(expected, jsf.contentAsString()); } public void testRenderOnClick_AjaxEnabled_WithParams() throws Exception { @@ -78,7 +71,7 @@ public class ProgressiveCommandLinkRendererTests extends TestCase { jsf.facesContext().getResponseWriter().endElement("a"); - assertEquals(expected, output.toString()); + assertEquals(expected, jsf.contentAsString()); } public void testRenderOnClick_AjaxDisabled_NoParams() throws Exception { @@ -100,7 +93,7 @@ public class ProgressiveCommandLinkRendererTests extends TestCase { jsf.facesContext().getResponseWriter().endElement("a"); - assertEquals(expected, output.toString()); + assertEquals(expected, jsf.contentAsString()); } public void testRenderOnClick_AjaxDisabled_WithParams() throws Exception { @@ -132,6 +125,6 @@ public class ProgressiveCommandLinkRendererTests extends TestCase { jsf.facesContext().getResponseWriter().endElement("a"); - assertEquals(expected, output.toString()); + assertEquals(expected, jsf.contentAsString()); } } diff --git a/spring-faces/src/test/java/org/springframework/faces/webflow/JSFMockHelper.java b/spring-faces/src/test/java/org/springframework/faces/webflow/JSFMockHelper.java index 74fdf966..c73b6d0b 100644 --- a/spring-faces/src/test/java/org/springframework/faces/webflow/JSFMockHelper.java +++ b/spring-faces/src/test/java/org/springframework/faces/webflow/JSFMockHelper.java @@ -1,5 +1,6 @@ package org.springframework.faces.webflow; +import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; @@ -19,8 +20,10 @@ import org.apache.myfaces.test.mock.MockHttpServletRequest; import org.apache.myfaces.test.mock.MockHttpServletResponse; import org.apache.myfaces.test.mock.MockHttpSession; import org.apache.myfaces.test.mock.MockPartialViewContextFactory; +import org.apache.myfaces.test.mock.MockPrintWriter; import org.apache.myfaces.test.mock.MockRenderKit; import org.apache.myfaces.test.mock.MockRenderKitFactory; +import org.apache.myfaces.test.mock.MockResponseWriter; import org.apache.myfaces.test.mock.MockServletConfig; import org.apache.myfaces.test.mock.MockServletContext; import org.apache.myfaces.test.mock.lifecycle.MockLifecycle; @@ -45,6 +48,10 @@ public class JSFMockHelper { return mock.config(); } + public String contentAsString() throws IOException { + return mock.contentAsString(); + } + public MockExternalContext externalContext() { return mock.externalContext(); } @@ -132,6 +139,8 @@ public class JSFMockHelper { facesContextFactory = (FacesContextFactory) FactoryFinder.getFactory(FactoryFinder.FACES_CONTEXT_FACTORY); facesContext = facesContextFactory.getFacesContext(servletContext, request, response, lifecycle); externalContext = (MockExternalContext) facesContext.getExternalContext(); + facesContext.setResponseWriter(new MockResponseWriter(response.getWriter())); + UIViewRoot root = new UIViewRoot(); root.setViewId("/viewId"); root.setRenderKitId(RenderKitFactory.HTML_BASIC_RENDER_KIT); @@ -174,6 +183,11 @@ public class JSFMockHelper { return config; } + public String contentAsString() throws IOException { + MockPrintWriter writer = (MockPrintWriter) response.getWriter(); + return new String(writer.content()); + } + public MockExternalContext externalContext() { return externalContext; } diff --git a/spring-faces/src/test/java/org/springframework/faces/webflow/JsfAjaxHandlerTests.java b/spring-faces/src/test/java/org/springframework/faces/webflow/JsfAjaxHandlerTests.java index 051a3378..544fb019 100644 --- a/spring-faces/src/test/java/org/springframework/faces/webflow/JsfAjaxHandlerTests.java +++ b/spring-faces/src/test/java/org/springframework/faces/webflow/JsfAjaxHandlerTests.java @@ -1,10 +1,7 @@ package org.springframework.faces.webflow; -import java.io.IOException; - import junit.framework.TestCase; -import org.apache.myfaces.test.mock.MockPrintWriter; import org.springframework.web.context.support.StaticWebApplicationContext; public class JsfAjaxHandlerTests extends TestCase { @@ -27,13 +24,8 @@ public class JsfAjaxHandlerTests extends TestCase { public void testSendAjaxRedirect() throws Exception { ajaxHandler.sendAjaxRedirectInternal("/target", jsfMock.request(), jsfMock.response(), false); assertEquals( - "\n", - extractResponseContent()); - } - - private String extractResponseContent() throws IOException { - MockPrintWriter writer = (MockPrintWriter) jsfMock.response().getWriter(); - return new String(writer.content()); + "\n", + jsfMock.contentAsString()); } } diff --git a/spring-faces/src/test/java/org/springframework/faces/webflow/JsfViewTests.java b/spring-faces/src/test/java/org/springframework/faces/webflow/JsfViewTests.java index 1516b078..f4df7b0d 100644 --- a/spring-faces/src/test/java/org/springframework/faces/webflow/JsfViewTests.java +++ b/spring-faces/src/test/java/org/springframework/faces/webflow/JsfViewTests.java @@ -115,8 +115,6 @@ public class JsfViewTests extends TestCase { EasyMock.replay(new Object[] { context, flowExecutionContext, flowMap, flashScope }); view.render(); - - assertNull("The FacesContext was not released", FacesContext.getCurrentInstance()); } public final void testRenderException() throws IOException { @@ -131,7 +129,6 @@ public class JsfViewTests extends TestCase { try { view.render(); } catch (Exception ex) { - assertNull("The FacesContext was not released", FacesContext.getCurrentInstance()); } } diff --git a/spring-faces/src/test/java/org/springframework/faces/webflow/MockBaseFacesContextFactory.java b/spring-faces/src/test/java/org/springframework/faces/webflow/MockBaseFacesContextFactory.java index 888564c0..2da2e12c 100644 --- a/spring-faces/src/test/java/org/springframework/faces/webflow/MockBaseFacesContextFactory.java +++ b/spring-faces/src/test/java/org/springframework/faces/webflow/MockBaseFacesContextFactory.java @@ -10,6 +10,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.myfaces.test.mock.MockExternalContext20; +import org.apache.myfaces.test.mock.MockHttpServletResponse; public class MockBaseFacesContextFactory extends FacesContextFactory { @@ -22,7 +23,25 @@ public class MockBaseFacesContextFactory extends FacesContextFactory { } else { ExternalContext ext = new MockExternalContext20((ServletContext) context, (HttpServletRequest) request, - (HttpServletResponse) response); + (HttpServletResponse) response) { + + public void setResponseBufferSize(int size) { + ((MockHttpServletResponse) getResponse()).setBufferSize(size); + } + + public void setResponseContentLength(int length) { + ((MockHttpServletResponse) getResponse()).setContentLength(length); + } + + public void setResponseContentType(String contentType) { + ((MockHttpServletResponse) getResponse()).setContentType(contentType); + } + + public void setResponseStatus(int statusCode) { + ((MockHttpServletResponse) getResponse()).setStatus(statusCode); + } + + }; return new MockBaseFacesContext(ext, lifecycle); } diff --git a/spring-webflow-samples/booking-faces/.classpath b/spring-webflow-samples/booking-faces/.classpath index f85cdb95..c84dc702 100755 --- a/spring-webflow-samples/booking-faces/.classpath +++ b/spring-webflow-samples/booking-faces/.classpath @@ -8,6 +8,7 @@ + diff --git a/spring-webflow-samples/booking-faces/ivy.xml b/spring-webflow-samples/booking-faces/ivy.xml index b1d136bb..2dc658ee 100755 --- a/spring-webflow-samples/booking-faces/ivy.xml +++ b/spring-webflow-samples/booking-faces/ivy.xml @@ -21,7 +21,6 @@ - @@ -49,21 +48,8 @@ - - - - - + diff --git a/spring-webflow-samples/booking-faces/pom.xml b/spring-webflow-samples/booking-faces/pom.xml index 5d83895e..3abdb720 100644 --- a/spring-webflow-samples/booking-faces/pom.xml +++ b/spring-webflow-samples/booking-faces/pom.xml @@ -153,12 +153,6 @@ 2.0.2 - - javax.el - com.springsource.javax.el - 1.0.0 - provided - javax.servlet com.springsource.javax.servlet diff --git a/spring-webflow-samples/booking-faces/src/main/java/org/springframework/webflow/samples/booking/Booking.java b/spring-webflow-samples/booking-faces/src/main/java/org/springframework/webflow/samples/booking/Booking.java index 98079fb3..8e7875c5 100755 --- a/spring-webflow-samples/booking-faces/src/main/java/org/springframework/webflow/samples/booking/Booking.java +++ b/spring-webflow-samples/booking-faces/src/main/java/org/springframework/webflow/samples/booking/Booking.java @@ -15,9 +15,6 @@ import javax.persistence.ManyToOne; import javax.persistence.Temporal; import javax.persistence.TemporalType; import javax.persistence.Transient; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Pattern; -import javax.validation.constraints.Size; import org.springframework.binding.message.MessageBuilder; import org.springframework.binding.message.MessageContext; @@ -37,17 +34,12 @@ public class Booking implements Serializable { private Hotel hotel; - @NotNull private Date checkinDate; - @NotNull private Date checkoutDate; - @Pattern(regexp = "[0-9]{16}", message = "Credit card number must be 16 digits.") private String creditCard; - @NotNull - @Size(min = 3, message = "A valid credit card name is required.") private String creditCardName; private int creditCardExpiryMonth; diff --git a/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/config/webflow-config.xml b/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/config/webflow-config.xml index 6addb4ff..876d4388 100644 --- a/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/config/webflow-config.xml +++ b/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/config/webflow-config.xml @@ -13,7 +13,8 @@ - + + @@ -26,7 +27,10 @@ - + + + + @@ -35,5 +39,5 @@ - + \ No newline at end of file diff --git a/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/faces-config-12.xml b/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/faces-config-12.xml deleted file mode 100644 index a1d74854..00000000 --- a/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/faces-config-12.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - com.sun.facelets.FaceletViewHandler - - \ No newline at end of file diff --git a/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/layouts/standard.xhtml b/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/layouts/standard.xhtml index 4464eec0..86a65b61 100644 --- a/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/layouts/standard.xhtml +++ b/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/layouts/standard.xhtml @@ -4,13 +4,15 @@ xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:c="http://java.sun.com/jsp/jstl/core" - xmlns:sf="http://www.springframework.org/tags/faces" contentType="text/html" encoding="UTF-8"> Spring Faces: Hotel Booking Sample Application - + - - javax.faces.PARTIAL_STATE_SAVING - false - - charEncodingFilter @@ -56,11 +50,11 @@ - + springSecurityFilterChain org.springframework.web.filter.DelegatingFilterProxy - + springSecurityFilterChain /* @@ -71,19 +65,6 @@ org.springframework.web.context.ContextLoaderListener - - - Resources Servlet - org.springframework.js.resource.ResourceServlet - 0 - - - - - Resources Servlet - /resources/* - - Spring MVC Dispatcher Servlet diff --git a/spring-webflow-samples/booking-portlet-faces/src/main/webapp/WEB-INF/config/hotelbooking-portlet-config.xml b/spring-webflow-samples/booking-portlet-faces/src/main/webapp/WEB-INF/config/hotelbooking-portlet-config.xml index 7b13834e..ba4a43d6 100644 --- a/spring-webflow-samples/booking-portlet-faces/src/main/webapp/WEB-INF/config/hotelbooking-portlet-config.xml +++ b/spring-webflow-samples/booking-portlet-faces/src/main/webapp/WEB-INF/config/hotelbooking-portlet-config.xml @@ -36,6 +36,7 @@ + @@ -49,6 +50,9 @@ + + + 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 c7188d5d..598640ab 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 @@ -61,6 +61,12 @@ public class ViewState extends TransitionableState { */ private Boolean redirect; + /** + * Whether or not a redirect should occur when the state is not exited (e.g. invalid form submission, a transition + * without a "to" attribute). + */ + private Boolean redirectInSameState = Boolean.FALSE; + /** * Whether or not the view should render as a popup. */ @@ -122,7 +128,7 @@ public class ViewState extends TransitionableState { * Returns whether this view state should request a flow execution redirect when entered. */ public boolean getRedirect() { - return redirect.booleanValue(); + return (redirect != null) ? redirect.booleanValue() : false; } /** @@ -133,6 +139,22 @@ public class ViewState extends TransitionableState { this.redirect = redirect; } + /** + * Returns whether this view state should request a flow execution redirect when the state hasn't been exited. + */ + public boolean getRedirectInSameState() { + return (redirectInSameState != null) ? redirectInSameState.booleanValue() : false; + } + + /** + * Sets whether this view state should requests a flow execution redirect when entered when processing is done but + * the state hasn't been exited (e.g. invalid form submissions). + * @param redirectInSameState the redirect flag + */ + public void setRedirectInSameState(Boolean redirectInSameState) { + this.redirectInSameState = redirectInSameState; + } + /** * Returns whether this view state should render as a popup. */ @@ -206,7 +228,7 @@ public class ViewState extends TransitionableState { if (externalContext.isAjaxRequest()) { render(context, view); } else { - if (shouldRedirect(context)) { + if (shouldRedirectInSameState(context)) { context.getFlashScope().put(View.USER_EVENT_STATE_ATTRIBUTE, view.getUserEventState()); externalContext.requestFlowExecutionRedirect(); } else { @@ -270,6 +292,14 @@ public class ViewState extends TransitionableState { } } + private boolean shouldRedirectInSameState(RequestControlContext context) { + if (redirectInSameState != null) { + return redirectInSameState.booleanValue(); + } else { + return shouldRedirect(context); + } + } + private void render(RequestControlContext context, View view) throws ViewRenderingException { if (logger.isDebugEnabled()) { logger.debug("Rendering + " + view); diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/FlowArtifactFactory.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/FlowArtifactFactory.java index 5b28218e..06e5fa56 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/FlowArtifactFactory.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/FlowArtifactFactory.java @@ -36,8 +36,8 @@ import org.springframework.webflow.execution.Action; import org.springframework.webflow.execution.ViewFactory; /** - * A factory for core web flow elements such as {@link Flow flows}, {@link State states}, and - * {@link Transition transitions}. + * A factory for core web flow elements such as {@link Flow flows}, {@link State states}, and {@link Transition + * transitions}. *

* This factory encapsulates the construction of each Flow implementation as well as each core artifact type. Subclasses * may customize how the core elements are created. @@ -86,6 +86,7 @@ public class FlowArtifactFactory { ViewState viewState = new ViewState(flow, id, viewFactory); viewState.addVariables(variables); viewState.setRedirect(redirect); + viewState.setRedirectInSameState(Boolean.FALSE); viewState.setPopup(popup); viewState.getRenderActionList().addAll(renderActions); configureCommonProperties(viewState, entryActions, transitions, exceptionHandlers, exitActions, attributes);