applied patch for SWF 283

This commit is contained in:
Keith Donald
2007-04-06 13:45:43 +00:00
parent f98611683f
commit b3f792c2bc
2 changed files with 196 additions and 82 deletions

View File

@@ -0,0 +1,147 @@
package org.springframework.webflow.executor.jsf;
/*
* Copyright 2004-2007 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.
*/
import javax.faces.component.UIComponent;
import javax.faces.component.UIComponentBase;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseId;
import javax.faces.render.Renderer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.StringUtils;
import org.springframework.webflow.execution.FlowExecution;
import org.springframework.webflow.execution.repository.FlowExecutionKey;
import org.springframework.webflow.execution.repository.FlowExecutionLock;
import org.springframework.webflow.execution.repository.FlowExecutionRepository;
/**
* This {@link UIComponent} instance can be added to the {@link UIViewRoot} before rendering so that the
* {@link FlowExecution} can be properly saved and then restored during the next request's {@link PhaseId#RESTORE_VIEW}
* phase.
*
* @author Jeremy Grelle
*/
public class FlowExecutionKeyStateHolder extends UIComponentBase {
/**
* Logger, usable by subclasses.
*/
protected final Log logger = LogFactory.getLog(getClass());
private static final String COMPONENT_FAMILY = "javax.faces.Parameter";
/**
* Immutable id of the flow execution key component for easier lookup later.
*/
public static final String COMPONENT_ID = "FlowExecutionKeyStateHolder";
/**
* The key value
*/
private String flowExecutionKey;
private boolean transientValue;
public String getId() {
return COMPONENT_ID;
}
public void setId(String id) {
// Do nothing so as to ensure the id never gets overwritten.
return;
}
public String getFamily() {
return COMPONENT_FAMILY;
}
public Renderer getRenderer() {
// this component is not rendered
return null;
}
/**
* Returns the flow execution key.
*/
public String getFlowExecutionKey() {
return flowExecutionKey;
}
/**
* Sets the tracked flow execution key used to restore the current flow execution during
* {@link #restoreState(FacesContext, Object)}.
* @param flowExecutionKey the flow execution key
*/
public void setFlowExecutionKey(String flowExecutionKey) {
this.flowExecutionKey = flowExecutionKey;
}
public boolean isTransient() {
return transientValue;
}
public void setTransient(boolean transientValue) {
this.transientValue = transientValue;
}
/**
* Restore the FlowExecution from the stored FlowExecutionKey
*/
public void restoreState(FacesContext context, Object state) {
Object values[] = (Object[]) state;
flowExecutionKey = (String) values[0];
restoreFlowExecution(context);
}
private void restoreFlowExecution(FacesContext facesContext) {
JsfExternalContext context = new JsfExternalContext(facesContext);
if (StringUtils.hasText(flowExecutionKey)) {
// restore flow execution from repository so it will be available to variable/property resolvers
// and the flow navigation handler (this could happen as part of a submission or flow execution redirect)
FlowExecutionRepository repository = getRepository(context);
FlowExecutionKey key;
// restore the key from the stored flowExecutionKey
key = repository.parseFlowExecutionKey(flowExecutionKey);
FlowExecutionLock lock = repository.getLock(key);
lock.lock();
FlowExecution flowExecution = repository.getFlowExecution(key);
if (logger.isDebugEnabled()) {
logger.debug("Loaded existing flow execution from repository with key '" + key + "'");
}
FlowExecutionHolderUtils.setFlowExecutionHolder(new FlowExecutionHolder(key, flowExecution, lock),
facesContext);
}
}
/**
* Save the just the current FlowExecutionKey value.
*/
public Object saveState(FacesContext context) {
Object values[] = new Object[1];
values[0] = flowExecutionKey;
return values;
}
public String getClientId(FacesContext context) {
return COMPONENT_ID;
}
private FlowExecutionRepository getRepository(JsfExternalContext context) {
return FlowFacesUtils.getExecutionRepository(context.getFacesContext());
}
}

View File

@@ -58,27 +58,23 @@ import org.springframework.webflow.executor.support.ResponseInstructionHandler;
* This phase listener implements the following algorithm:
* <ul>
* <li>On BEFORE_RESTORE_VIEW, restore the {@link FlowExecution} the user is participating in if a call to
* {@link FlowExecutorArgumentHandler#extractFlowExecutionKey(ExternalContext)} returns a submitted flow execution
* identifier. Place the restored flow execution in a holder that other JSF artifacts such as VariableResolvers,
* PropertyResolvers, and NavigationHandlers may access during the request lifecycle.
* {@link FlowExecutorArgumentHandler#extractFlowExecutionKey(ExternalContext)} returns a submitted flow execution key.
* Place the restored flow execution in a holder that other JSF artifacts such as VariableResolvers, PropertyResolvers,
* and NavigationHandlers may access during the request lifecycle.
* <li>On BEFORE_RENDER_RESPONSE, if a flow execution was restored in the RESTORE_VIEW phase generate a new key for
* identifying the updated execution within a the selected {@link FlowExecutionRepository}. Expose managed flow
* execution attributes to the views before rendering.
* identifying the updated execution within a the selected {@link FlowExecutionRepository}. Expose the new flow
* execution key as a component in the view root for restoration on the next request. Expose managed flow execution
* attributes to the views before rendering.
* <li>On AFTER_RENDER_RESPONSE, if a flow execution was restored in the RESTORE_VIEW phase <em>save</em> the updated
* execution to the repository using the new key generated in the BEFORE_RENDER_RESPONSE phase.
* </ul>
*
* @author Colin Sampaleanu
* @author Keith Donald
* @author Jeremy Grelle
*/
public class FlowPhaseListener implements PhaseListener {
/**
* The name of the attribute the flow execution key will be tracked under in the JSF View Root for managing the
* current flow execution key.
*/
private static final String FLOW_EXECUTION_KEY_VIEW_ROOT_ATTRIBUTE = "_flowExecutionKey";
/**
* Logger, usable by subclasses.
*/
@@ -157,7 +153,11 @@ public class FlowPhaseListener implements PhaseListener {
}
public void beforePhase(PhaseEvent event) {
if (event.getPhaseId() == PhaseId.RENDER_RESPONSE) {
if (event.getPhaseId() == PhaseId.RESTORE_VIEW) {
ExternalContextHolder.setExternalContext(new JsfExternalContext(event.getFacesContext()));
restoreFlowExecution(event.getFacesContext());
}
else if (event.getPhaseId() == PhaseId.RENDER_RESPONSE) {
if (FlowExecutionHolderUtils.isFlowExecutionRestored(event.getFacesContext())) {
prepareResponse(getCurrentContext(), FlowExecutionHolderUtils.getFlowExecutionHolder(event
.getFacesContext()));
@@ -166,11 +166,7 @@ public class FlowPhaseListener implements PhaseListener {
}
public void afterPhase(PhaseEvent event) {
if (event.getPhaseId() == PhaseId.RESTORE_VIEW) {
ExternalContextHolder.setExternalContext(new JsfExternalContext(event.getFacesContext()));
restoreFlowExecution(event.getFacesContext());
}
else if (event.getPhaseId() == PhaseId.RENDER_RESPONSE) {
if (event.getPhaseId() == PhaseId.RENDER_RESPONSE) {
try {
if (FlowExecutionHolderUtils.isFlowExecutionRestored(event.getFacesContext())) {
FlowExecutionHolder holder = FlowExecutionHolderUtils.getFlowExecutionHolder(event
@@ -193,29 +189,18 @@ public class FlowPhaseListener implements PhaseListener {
protected void restoreFlowExecution(FacesContext facesContext) {
JsfExternalContext context = new JsfExternalContext(facesContext);
if (argumentHandler.isFlowExecutionKeyPresent(context) || isFlowExecutionKeyInViewRoot(facesContext)) {
// restore flow execution from repository so it will be
// available to variable/property resolvers and the flow
// navigation handler (this could happen as part of a submission or
// flow execution redirect)
if (argumentHandler.isFlowExecutionKeyPresent(context)) {
// restore flow execution from repository so it will be available to variable/property resolvers
// and the flow navigation handler (this could happen as part of a submission or flow execution redirect)
FlowExecutionRepository repository = getRepository(context);
FlowExecutionKey flowExecutionKey;
if (argumentHandler.isFlowExecutionKeyPresent(context)) {
// extract it in the "traditional way" (request parameter in url by default)
flowExecutionKey = repository.parseFlowExecutionKey(argumentHandler.extractFlowExecutionKey(context));
}
else {
// restore the key from an attribute in the root of the component tree
flowExecutionKey = repository.parseFlowExecutionKey((String) facesContext.getViewRoot().getAttributes()
.get(FLOW_EXECUTION_KEY_VIEW_ROOT_ATTRIBUTE));
// remove it (it should always be placed back before response rendering)
facesContext.getViewRoot().getAttributes().remove(FLOW_EXECUTION_KEY_VIEW_ROOT_ATTRIBUTE);
}
// extract key in the "traditional way" (request parameter in url by default)
FlowExecutionKey flowExecutionKey = repository.parseFlowExecutionKey(argumentHandler
.extractFlowExecutionKey(context));
FlowExecutionLock lock = repository.getLock(flowExecutionKey);
lock.lock();
FlowExecution flowExecution = repository.getFlowExecution(flowExecutionKey);
if (logger.isDebugEnabled()) {
logger.debug("Loaded existing flow execution from repository with id '" + flowExecutionKey + "'");
logger.debug("Loaded existing flow execution from repository with key '" + flowExecutionKey + "'");
}
FlowExecutionHolderUtils.setFlowExecutionHolder(new FlowExecutionHolder(flowExecutionKey, flowExecution,
lock), facesContext);
@@ -311,11 +296,7 @@ public class FlowPhaseListener implements PhaseListener {
}
String flowExecutionKey = holder.getFlowExecution().isActive() ? holder.getFlowExecutionKey().toString() : null;
if (flowExecutionKey != null) {
// expose to view root for preservation in the component tree
if (viewRootAttributeMapPresent(facesContext)) {
facesContext.getViewRoot().getAttributes()
.put(FLOW_EXECUTION_KEY_VIEW_ROOT_ATTRIBUTE, flowExecutionKey);
}
saveInViewRoot(facesContext, flowExecutionKey);
}
Map requestMap = facesContext.getExternalContext().getRequestMap();
argumentHandler.exposeFlowExecutionContext(flowExecutionKey, holder.getFlowExecution(), requestMap);
@@ -353,35 +334,6 @@ public class FlowPhaseListener implements PhaseListener {
return (JsfExternalContext) ExternalContextHolder.getExternalContext();
}
/**
* Returns true if the root of the component tree contains the flow execution key attribute, used to restore the
* flow execution on subsequent reqests.
* @param facesContext the key
* @return true if yes, false otherwise
*/
private boolean isFlowExecutionKeyInViewRoot(FacesContext facesContext) {
if (viewRootAttributeMapPresent(facesContext)) {
return facesContext.getViewRoot().getAttributes().containsKey(FLOW_EXECUTION_KEY_VIEW_ROOT_ATTRIBUTE);
}
else {
return false;
}
}
/**
* Simple little helper that returns true if the view root attribute map is non-null.
* @param facesContext the faces context
* @return true if so, false otherwise
*/
private boolean viewRootAttributeMapPresent(FacesContext facesContext) {
if (facesContext.getViewRoot() != null && facesContext.getViewRoot().getAttributes() != null) {
return true;
}
else {
return false;
}
}
private void updateViewRoot(FacesContext facesContext, String viewId) {
UIViewRoot viewRoot = facesContext.getViewRoot();
if (viewRoot == null || hasViewChanged(viewRoot, viewId)) {
@@ -396,11 +348,27 @@ public class FlowPhaseListener implements PhaseListener {
return !viewRoot.getViewId().equals(viewId);
}
/**
* Saves the flow execution key in a component in the view root for restoration on subsequent RESTORE_VIEW operations.
* @param facesContext the faces context exposing the view root
* @param flowExecutionKey the flow execution key
*/
private void saveInViewRoot(FacesContext facesContext, String flowExecutionKey) {
// search for key holder in the component tree
FlowExecutionKeyStateHolder keyHolder = (FlowExecutionKeyStateHolder) facesContext.getViewRoot()
.findComponent(FlowExecutionKeyStateHolder.COMPONENT_ID);
if (keyHolder == null) {
keyHolder = new FlowExecutionKeyStateHolder();
// expose as the first component in the view root for preservation in the tree
facesContext.getViewRoot().getChildren().add(0, keyHolder);
}
keyHolder.setFlowExecutionKey(flowExecutionKey);
}
private void generateKey(JsfExternalContext context, FlowExecutionHolder holder) {
FlowExecution flowExecution = holder.getFlowExecution();
if (flowExecution.isActive()) {
// generate new continuation key for the flow execution
// before rendering the response
// generate new continuation key for the flow execution before rendering the response
FlowExecutionKey flowExecutionKey = holder.getFlowExecutionKey();
FlowExecutionRepository repository = getRepository(context);
if (flowExecutionKey == null) {
@@ -408,8 +376,7 @@ public class FlowPhaseListener implements PhaseListener {
flowExecutionKey = repository.generateKey(flowExecution);
}
else {
// it is an existing conversaiton, use same conversation id,
// generate a new continuation id
// it is an existing conversaiton, use same conversation id, generate a new continuation id
flowExecutionKey = repository.getNextKey(flowExecution, flowExecutionKey);
}
holder.setFlowExecutionKey(flowExecutionKey);
@@ -448,15 +415,6 @@ public class FlowPhaseListener implements PhaseListener {
}
}
/**
* Standard default view id resolver which uses the web flow view name as the jsf view id
*/
public static class DefaultViewIdMapper implements ViewIdMapper {
public String mapViewId(String viewName) {
return viewName;
}
}
private FlowDefinitionLocator getLocator(JsfExternalContext context) {
return FlowFacesUtils.getDefinitionLocator(context.getFacesContext());
}
@@ -468,4 +426,13 @@ public class FlowPhaseListener implements PhaseListener {
private FlowExecutionRepository getRepository(JsfExternalContext context) {
return FlowFacesUtils.getExecutionRepository(context.getFacesContext());
}
/**
* Standard default view id resolver which uses the web flow view name as the jsf view id
*/
public static class DefaultViewIdMapper implements ViewIdMapper {
public String mapViewId(String viewName) {
return viewName;
}
}
}