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.

This commit is contained in:
Rossen Stoyanchev
2010-08-03 11:15:20 +00:00
parent fe9229cf15
commit 6a56e9678a
28 changed files with 530 additions and 220 deletions

View File

@@ -8,8 +8,8 @@
<classpathentry combineaccessrules="false" kind="src" path="/spring-js"/>
<classpathentry combineaccessrules="false" kind="src" path="/spring-webflow"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="var" path="IVY_CACHE/com.sun.faces/jsf-impl/2.0.2/jsf-impl-2.0.2.jar" sourcepath="/IVY_CACHE/com.sun.faces/jsf-impl/2.0.2/jsf-impl-2.0.2-sources.jar"/>
<classpathentry kind="var" path="IVY_CACHE/javax.faces/jsf-api/2.0/jsf-api-2.0.jar" sourcepath="/IVY_CACHE/javax.faces/jsf-api/2.0/jsf-api-2.0-sources.jar"/>
<classpathentry kind="var" path="IVY_CACHE/com.sun.faces/jsf-impl/2.0.3/jsf-impl-2.0.3.jar" sourcepath="/IVY_CACHE/com.sun.faces/jsf-impl/2.0.3/jsf-impl-2.0.3-sources.jar"/>
<classpathentry kind="var" path="IVY_CACHE/com.sun.faces/jsf-api/2.0.3/jsf-api-2.0.3.jar" sourcepath="/IVY_CACHE/com.sun.faces/jsf-api/2.0.3/jsf-api-2.0.3-sources.jar"/>
<classpathentry kind="var" path="IVY_CACHE/javax.el/com.springsource.javax.el/1.0.0/com.springsource.javax.el-1.0.0.jar" sourcepath="IVY_CACHE/javax.el/com.springsource.javax.el/1.0.0/com.springsource.javax.el-sources-1.0.0.jar"/>
<classpathentry kind="var" path="IVY_CACHE/javax.servlet/com.springsource.javax.servlet/2.4.0/com.springsource.javax.servlet-2.4.0.jar" sourcepath="/IVY_CACHE/javax.servlet/com.springsource.javax.servlet/2.4.0/com.springsource.javax.servlet-sources-2.4.0.jar"/>
<classpathentry kind="var" path="IVY_CACHE/org.apache.commons/com.springsource.org.apache.commons.logging/1.1.1/com.springsource.org.apache.commons.logging-1.1.1.jar" sourcepath="IVY_CACHE/org.apache.commons/com.springsource.org.apache.commons.logging/1.1.1/com.springsource.org.apache.commons.logging-sources-1.1.1.jar"/>

View File

@@ -42,8 +42,8 @@
<dependency org="org.springframework.webflow" name="org.springframework.webflow" rev="latest.integration" conf="compile->runtime"/>
<!-- JSF 2.0 Mojarra dependencies -->
<dependency org="javax.faces" name="jsf-api" rev="2.0" conf="provided, mojarra20->runtime(*)" />
<dependency org="com.sun.faces" name="jsf-impl" rev="2.0.2" conf="provided, mojarra20->runtime(*)" />
<dependency org="com.sun.faces" name="jsf-api" rev="2.0.3" conf="provided, mojarra20->runtime(*)" />
<dependency org="com.sun.faces" name="jsf-impl" rev="2.0.3" conf="provided, mojarra20->runtime(*)" />
<dependency org="javax.servlet" name="com.springsource.javax.servlet.jsp.jstl" rev="1.2.0" conf="test, mojarra20->runtime"/>
<!-- JSF 2.0 MyFaces dependencies -->

View File

@@ -57,12 +57,6 @@
<artifactId>spring-webflow</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.sun.facelets</groupId>
<artifactId>jsf-facelets</artifactId>
<version>1.1.14</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.richfaces.framework</groupId>
<artifactId>richfaces-api</artifactId>
@@ -82,15 +76,15 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.faces</groupId>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-api</artifactId>
<version>2.0</version>
<version>2.0.3</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.faces</groupId>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-impl</artifactId>
<version>2.0.2</version>
<version>2.0.3</version>
<scope>provided</scope>
</dependency>
</dependencies>

View File

@@ -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() {

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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;
/**
* <p>
* 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.
* </p>
*
* <p>
* 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.
* </p>
*
* @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 = ("<input type=\"hidden\" name=\"" + ResponseStateManager.VIEW_STATE_PARAM
+ "\" id=\"" + ResponseStateManager.VIEW_STATE_PARAM + "\" value=\"").toCharArray();
private char[] stateFieldEnd = "\" />".toCharArray();
public FlowViewResponseStateManager(ResponseStateManager delegate) {
this.delegate = delegate;
}
/**
* <p>
* Wraps state in an instance of {@link FlowSerializedView} and stores it in view scope.
* </p>
*
* <p>
* 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.
* </p>
*/
@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);
}
/**
* <p>
* Retrieves the state from view scope as an instance of {@link FlowSerializedView} and turns it to an array before
* returning.
* </p>
*/
@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 <code>FacesContext</code> for the current request
* @param writer the <code>ResponseWriter</code> 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 <code>ResponseStateManager.RENDER_KIT_ID_PARAM</code> in
* {@link ResponseStateManager#writeState(FacesContext, Object)}.
*
* @param context the <code>FacesContext</code> for the current request
* @param writer the <code>ResponseWriter</code> 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");
}
}
}

View File

@@ -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
* <p>
* JSF 1.2 (or higher) version of state saving.
* </p>
*
* <p>
* 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.
* </p>
*/
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;
}
/**
* <p>
* 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.
* </p>
*/
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) {

View File

@@ -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);
}

View File

@@ -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() {

View File

@@ -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);

View File

@@ -88,6 +88,7 @@
<render-kit>
<render-kit-id>HTML_BASIC</render-kit-id>
<render-kit-class>org.springframework.faces.webflow.FlowRenderKit</render-kit-class>
<renderer>
<component-family>javax.faces.Command</component-family>

View File

@@ -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());
}
}

View File

@@ -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;
}

View File

@@ -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(
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<partial-response><redirect url=\"/target\"/></partial-response>",
extractResponseContent());
}
private String extractResponseContent() throws IOException {
MockPrintWriter writer = (MockPrintWriter) jsfMock.response().getWriter();
return new String(writer.content());
"<?xml version='1.0' encoding='utf-8'?>\n<partial-response><redirect url=\"/target\"/></partial-response>",
jsfMock.contentAsString());
}
}

View File

@@ -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());
}
}

View File

@@ -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);
}

View File

@@ -8,6 +8,7 @@
<classpathentry kind="var" path="IVY_CACHE/javax.el/com.springsource.javax.el/1.0.0/com.springsource.javax.el-1.0.0.jar" sourcepath="/IVY_CACHE/javax.el/com.springsource.javax.el/1.0.0/com.springsource.javax.el-sources-1.0.0.jar"/>
<classpathentry kind="var" path="IVY_CACHE/org.easymock/com.springsource.org.easymock/2.3.0/com.springsource.org.easymock-2.3.0.jar" sourcepath="/IVY_CACHE/org.easymock/com.springsource.org.easymock/2.3.0/com.springsource.org.easymock-sources-2.3.0.jar"/>
<classpathentry kind="var" path="IVY_CACHE/org.junit/com.springsource.junit/3.8.2/com.springsource.junit-3.8.2.jar" sourcepath="/IVY_CACHE/org.junit/com.springsource.junit/3.8.2/com.springsource.junit-sources-3.8.2.jar"/>
<classpathentry kind="var" path="IVY_CACHE/javax.servlet/com.springsource.javax.servlet/2.4.0/com.springsource.javax.servlet-2.4.0.jar" sourcepath="/IVY_CACHE/javax.servlet/com.springsource.javax.servlet/2.4.0/com.springsource.javax.servlet-sources-2.4.0.jar"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -21,7 +21,6 @@
<dependencies>
<!-- core dependencies -->
<dependency org="javax.el" name="com.springsource.javax.el" rev="1.0.0" conf="provided->runtime"/>
<dependency org="javax.persistence" name="com.springsource.javax.persistence" rev="1.0.0" conf="compile->runtime"/>
<dependency org="javax.servlet" name="com.springsource.javax.servlet" rev="2.4.0" conf="provided->runtime"/>
<dependency org="javax.servlet" name="com.springsource.javax.servlet.jsp.jstl" rev="1.2.0" conf="compile->runtime"/>
@@ -49,21 +48,8 @@
<dependency org="org.springframework.security" name="org.springframework.security.web" rev="3.0.2.RELEASE" conf="compile->runtime"/>
<dependency org="org.springframework.webflow" name="org.springframework.js" rev="latest.integration" conf="compile->runtime"/>
<dependency org="org.springframework.webflow" name="org.springframework.webflow" rev="latest.integration" conf="compile->runtime"/>
<!-- Mojarra 2.0 (use in conjunction with JSF 2 version of faces-config.xml) -->
<dependency org="org.springframework.webflow" name="org.springframework.faces" rev="latest.integration" conf="compile->mojarra20"/>
<!--
Switching to Mojarra 1.2:
1. Uncomment the below dependencies and comment out the mojarra20 dependency above.
2. Update the contents of faces-config.xml with the one from faces-config-12.xml.
3. Run 'ant clean jar' from the command line.
4. Refresh the project in Eclipse.
<dependency org="org.springframework.webflow" name="org.springframework.faces" rev="latest.integration" conf="compile->mojarra12"/>
<dependency org="com.sun.facelets" name="com.springsource.com.sun.facelets" rev="1.1.14" conf="compile->runtime" />
-->
<!-- test-time only dependencies -->
<dependency org="org.easymock" name="com.springsource.org.easymock" rev="2.3.0" conf="test->runtime" />
<dependency org="org.junit" name="com.springsource.junit" rev="3.8.2" conf="test->runtime"/>

View File

@@ -153,12 +153,6 @@
<version>2.0.2</version>
</dependency>
<!-- build time only dependencies -->
<dependency>
<groupId>javax.el</groupId>
<artifactId>com.springsource.javax.el</artifactId>
<version>1.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>com.springsource.javax.servlet</artifactId>

View File

@@ -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;

View File

@@ -13,7 +13,8 @@
<!-- Executes flows: the central entry point into the Spring Web Flow system -->
<webflow:flow-executor id="flowExecutor">
<webflow:flow-execution-listeners>
<webflow:flow-execution-listeners>
<webflow:listener ref="facesContextLifecycleListener"/>
<webflow:listener ref="jpaFlowExecutionListener" />
<webflow:listener ref="securityFlowExecutionListener" />
</webflow:flow-execution-listeners>
@@ -26,7 +27,10 @@
<!-- Configures the Spring Web Flow JSF integration -->
<faces:flow-builder-services id="facesFlowBuilderServices" development="true" />
<!-- Installs a listener that creates and releases the FacesContext for each request. -->
<bean id="facesContextLifecycleListener" class="org.springframework.faces.webflow.FlowFacesContextLifecycleListener"/>
<!-- Installs a listener that manages JPA persistence contexts for flows that require them -->
<bean id="jpaFlowExecutionListener" class="org.springframework.webflow.persistence.JpaFlowExecutionListener">
<constructor-arg ref="entityManagerFactory" />
@@ -35,5 +39,5 @@
<!-- Installs a listener to apply Spring Security authorities -->
<bean id="securityFlowExecutionListener" class="org.springframework.webflow.security.SecurityFlowExecutionListener" />
</beans>

View File

@@ -1,17 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE faces-config PUBLIC
"-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"
"http://java.sun.com/dtd/web-facesconfig_1_0.dtd">
<!--
For use in a JSF 1.2 runtime environment.
Swap this file out with faces-config.xml if testing with JSF 1.2 (see ivy.xml).
-->
<faces-config>
<application>
<view-handler>com.sun.facelets.FaceletViewHandler</view-handler>
</application>
</faces-config>

View File

@@ -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">
<html>
<h:head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Spring Faces: Hotel Booking Sample Application</title>
<sf:includeStyles/>
<!--
Note on serving resources:
In Spring 3.0.4 the below resources (css and images) will be served by Spring MVC's ResourceHttpRequestHandler.
-->
<link rel="stylesheet" href="${request.contextPath}/styles/blueprint/screen.css" type="text/css" media="screen, projection" />
<link rel="stylesheet" href="${request.contextPath}/styles/blueprint/print.css" type="text/css" media="print" />
<!--[if lt IE 8]>
@@ -19,7 +21,7 @@
<link rel="stylesheet" href="${request.contextPath}/styles/booking.css" type="text/css" media="screen" />
<ui:insert name="headIncludes"/>
</h:head>
<body class="tundra">
<body>
<div id="page" class="container">
<div id="header">
<div id="topbar">
@@ -34,7 +36,7 @@
</div>
<div id="logo">
<p>
<a href="#{request.contextPath}"><img src="${request.contextPath}/resources/images/header.jpg" alt="Spring Travel"/></a>
<a href="#{request.contextPath}"><img src="${request.contextPath}/images/header.jpg" alt="Spring Travel"/></a>
</p>
</div>
</div>
@@ -42,12 +44,12 @@
<div id="local" class="span-6">
<p>
<a href="http://www.thespringexperience.com">
<img src="${request.contextPath}/resources/images/diplomat.jpg" alt="generic hotel" />
<img src="${request.contextPath}/images/diplomat.jpg" alt="generic hotel" />
</a>
</p>
<p>
<a href="http://www.thespringexperience.com">
<img src="${request.contextPath}/resources/images/springone2gx.jpeg" alt="SpringOne 2GX" />
<img src="${request.contextPath}/images/springone2gx.jpeg" alt="SpringOne 2GX" />
</a>
</p>
</div>
@@ -58,7 +60,7 @@
<hr />
<div id="footer">
<a href="http://www.springframework.org">
<img src="${request.contextPath}/resources/images/powered-by-spring.png" alt="Powered by Spring" />
<img src="${request.contextPath}/images/powered-by-spring.png" alt="Powered by Spring" />
</a>
</div>
</div>

View File

@@ -30,12 +30,6 @@
<param-value>1</param-value>
</context-param>
<!-- Disables partial state saving -->
<context-param>
<param-name>javax.faces.PARTIAL_STATE_SAVING</param-name>
<param-value>false</param-value>
</context-param>
<!-- Enforce UTF-8 Character Encoding -->
<filter>
<filter-name>charEncodingFilter</filter-name>
@@ -56,11 +50,11 @@
</filter-mapping>
<!-- Enables Spring Security -->
<filter>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
@@ -71,19 +65,6 @@
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Serves static resource content from .jar files such as spring-faces.jar -->
<servlet>
<servlet-name>Resources Servlet</servlet-name>
<servlet-class>org.springframework.js.resource.ResourceServlet</servlet-class>
<load-on-startup>0</load-on-startup>
</servlet>
<!-- Map all /resources requests to the Resource Servlet for handling -->
<servlet-mapping>
<servlet-name>Resources Servlet</servlet-name>
<url-pattern>/resources/*</url-pattern>
</servlet-mapping>
<!-- The front controller of this Spring Web application, responsible for handling all application requests -->
<servlet>
<servlet-name>Spring MVC Dispatcher Servlet</servlet-name>

View File

@@ -36,6 +36,7 @@
<!-- Executes flows: the central entry point into the Spring Web Flow system -->
<webflow:flow-executor id="flowExecutor">
<webflow:flow-execution-listeners>
<webflow:listener ref="facesContextLifecycleListener"/>
<webflow:listener ref="jpaFlowExecutionListener" />
</webflow:flow-execution-listeners>
</webflow:flow-executor>
@@ -49,6 +50,9 @@
<!-- Configures the Spring Web Flow JSF integration -->
<faces:flow-builder-services id="facesFlowBuilderServices" />
<!-- Installs a listener that creates and releases the FacesContext for each request. -->
<bean id="facesContextLifecycleListener" class="org.springframework.faces.webflow.FlowFacesContextLifecycleListener"/>
<!-- Installs a listener that manages JPA persistence contexts for flows that require them -->
<bean id="jpaFlowExecutionListener" class="org.springframework.webflow.persistence.JpaFlowExecutionListener">

View File

@@ -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);

View File

@@ -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}.
* <p>
* 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);