swf 302 and 303

This commit is contained in:
Keith Donald
2007-05-01 00:09:48 +00:00
parent 0c157f002f
commit 5aa24fc470
6 changed files with 175 additions and 110 deletions

View File

@@ -9,6 +9,12 @@ Package org.springframework.webflow.config
* FlowExecutorFactoryBean now has an 'inputMapper' property to conveniently configure the 'inputMapper'
property of the created FlowExecutorImpl object.
Package org.springframework.webflow.executor
* JSF integration code now manages flow execution locks properly in exceptional situations and when the
RENDER RESPONSE phase is bypassed (SWF-302).
* Restored compatability with 1.0.1 allowing default navigation rules to be queried if no transition could
be matched against the state of the current flow execution (SWF-303).
Changes in version 1.0.3 (19.04.2007)
-------------------------------------

View File

@@ -133,11 +133,18 @@ public class FlowExecutionHolder implements Serializable {
public void replaceWith(FlowExecution flowExecution) {
this.flowExecutionKey = null;
this.viewSelection = null;
unlockFlowExecutionIfNecessary();
this.flowExecution = flowExecution;
}
/**
* Unlock the held flow execution if necessary.
*/
public void unlockFlowExecutionIfNecessary() {
if (flowExecutionLock != null) {
flowExecutionLock.unlock();
this.flowExecutionLock = null;
}
this.flowExecutionLock = null;
this.flowExecution = flowExecution;
}
public String toString() {

View File

@@ -91,6 +91,17 @@ public class FlowExecutionHolderUtils {
}
}
/**
* Unlocks the current flow execution in the faces context if necessary.
* Can be safely called even if no execution is bound or one is bound but not locked.
* @param context the faces context
*/
public static void unlockCurrentFlowExecutionIfNecessary(FacesContext context) {
if (isFlowExecutionRestored(context)) {
getFlowExecutionHolder(context).unlockFlowExecutionIfNecessary();
}
}
private static String getFlowExecutionHolderKey() {
return FlowExecutionHolder.class.getName();
}

View File

@@ -36,6 +36,7 @@ import org.springframework.webflow.execution.repository.FlowExecutionRepository;
* phase.
*
* @author Jeremy Grelle
* @author Keith Donald
*/
public class FlowExecutionKeyStateHolder extends UIComponentBase {
@@ -116,17 +117,24 @@ public class FlowExecutionKeyStateHolder extends UIComponentBase {
// restore the "current" 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 view action like a form submission)
FlowExecutionRepository repository = getRepository(context);
FlowExecutionKey key;
// restore the key from the stored flowExecutionKey
key = repository.parseFlowExecutionKey(flowExecutionKey);
// restore the key from the stored encoded key string
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 + "'");
FlowExecution flowExecution;
try {
flowExecution = repository.getFlowExecution(key);
if (logger.isDebugEnabled()) {
logger.debug("Loaded existing flow execution from repository with key '" + key + "'");
}
} catch (RuntimeException e) {
lock.unlock();
throw e;
} catch (Error e) {
lock.unlock();
throw e;
}
FlowExecutionHolderUtils.setFlowExecutionHolder(new FlowExecutionHolder(key, flowExecution, lock),
facesContext);
FlowExecutionHolderUtils.setFlowExecutionHolder(new FlowExecutionHolder(key, flowExecution, lock), facesContext);
}
}

View File

@@ -27,6 +27,7 @@ import org.springframework.webflow.core.collection.LocalAttributeMap;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.definition.FlowDefinition;
import org.springframework.webflow.definition.registry.FlowDefinitionLocator;
import org.springframework.webflow.engine.NoMatchingTransitionException;
import org.springframework.webflow.execution.FlowExecution;
import org.springframework.webflow.execution.FlowExecutionFactory;
import org.springframework.webflow.execution.ViewSelection;
@@ -34,38 +35,33 @@ import org.springframework.webflow.executor.RequestParameterInputMapper;
import org.springframework.webflow.executor.support.FlowExecutorArgumentExtractor;
/**
* An implementation of a JSF <code>NavigationHandler</code> that provides integration with Spring Web Flow.
* Responsible for delegating to Spring Web Flow to launch and resume flow executions, treating JSF action outcomes
* (like a command button click) as web flow events.
* An implementation of a JSF <code>NavigationHandler</code> that provides
* integration with Spring Web Flow. Responsible for delegating to Spring Web
* Flow to launch and resume flow executions, treating JSF action outcomes (like
* a command button click) as web flow events.
*
* This class delegates to the standard NavigationHandler implementation when a navigation request does not pertain to a
* flow execution.
* <p>
* The following navigation handler algorithm is implemented by default:
* </p>
* <p>
* If a flow execution has been restored in the current request:
* <ul>
* <li>Resume the flow execution by signaling the JSF action outcome as an event against the current state.
* <li>Once event processing completes expose the selected view as the "current" {@link ViewSelection}.
* </ul>
* </p>
* <p>
* If a flow execution has not been restored in the current request:
* <ul>
* <li>If the specified logical outcome is of the form <em>flowId:xxx</em> look up the corresponding
* {@link FlowDefinition} with that id and launch a new flow execution in the starting state. Expose the new execution
* as the "current" flow execution for this request. Expose the first selected view as the "current" view selection.
* <li>If the specified logical outcome is not of the form <em>flowId:xxx</em>, simply delegate to the standard
* <code>NavigationHandler</code> implementation and return.
* </ul>
* </p>
* How the flowId and eventId arguments are extracted can be customized by setting a custom
* This class delegates to the standard NavigationHandler implementation when a
* navigation request does not pertain to a flow execution. <p> The following
* navigation handler algorithm is implemented by default: </p> <p> If a flow
* execution has been restored in the current request: <ul> <li>Resume the flow
* execution by signaling the JSF action outcome as an event against the current
* state. <li>Once event processing completes expose the selected view as the
* "current" {@link ViewSelection}. </ul> </p> <p> If a flow execution has not
* been restored in the current request: <ul> <li>If the specified logical
* outcome is of the form <em>flowId:xxx</em> look up the corresponding
* {@link FlowDefinition} with that id and launch a new flow execution in the
* starting state. Expose the new execution as the "current" flow execution for
* this request. Expose the first selected view as the "current" view selection.
* <li>If the specified logical outcome is not of the form <em>flowId:xxx</em>,
* simply delegate to the standard <code>NavigationHandler</code>
* implementation and return. </ul> </p> How the flowId and eventId arguments
* are extracted can be customized by setting a custom
* {@link #setArgumentExtractor(FlowExecutorArgumentExtractor) argument extractor}.
*
* Note about customization: since NavigationHandlers managed directly by the JSF provider cannot be benefit from
* DependencyInjection, See Spring's {@link org.springframework.web.jsf.DelegatingNavigationHandlerProxy} when you need
* to customize a FlowNavigationHandler instance.
* Note about customization: since NavigationHandlers managed directly by the
* JSF provider cannot be benefit from DependencyInjection, See Spring's
* {@link org.springframework.web.jsf.DelegatingNavigationHandlerProxy} when you
* need to customize a FlowNavigationHandler instance.
*
* @author Craig McClanahan
* @author Colin Sampaleanu
@@ -79,19 +75,20 @@ public class FlowNavigationHandler extends DecoratingNavigationHandler {
protected final Log logger = LogFactory.getLog(getClass());
/**
* A helper for extracting parameters needed by this flow navigation handler.
* A helper for extracting parameters needed by this flow navigation
* handler.
*/
private FlowExecutorArgumentExtractor argumentExtractor = new FlowNavigationHandlerArgumentExtractor();
/**
* The service responsible for mapping attributes of an {@link ExternalContext} to a new {@link FlowExecution}
* during the {@link #launch(String, ExternalContext) launch flow} operation.
* <p>
* This allows developers to control what attributes are made available in the <code>inputMap</code> to new
* top-level flow executions. The starting execution may then choose to map that available input into its own local
* scope.
* <p>
* The default implementation simply exposes all request parameters as flow execution input attributes. May be null.
* The service responsible for mapping attributes of an
* {@link ExternalContext} to a new {@link FlowExecution} during the
* {@link #launch(String, ExternalContext) launch flow} operation. <p> This
* allows developers to control what attributes are made available in the
* <code>inputMap</code> to new top-level flow executions. The starting
* execution may then choose to map that available input into its own local
* scope. <p> The default implementation simply exposes all request
* parameters as flow execution input attributes. May be null.
*/
private AttributeMapper inputMapper = new RequestParameterInputMapper();
@@ -103,8 +100,10 @@ public class FlowNavigationHandler extends DecoratingNavigationHandler {
}
/**
* Create a new {@link FlowNavigationHandler}, wrapping the specified standard navigation handler implementation.
* @param originalNavigationHandler Standard <code>NavigationHandler</code> we are wrapping
* Create a new {@link FlowNavigationHandler}, wrapping the specified
* standard navigation handler implementation.
* @param originalNavigationHandler Standard <code>NavigationHandler</code>
* we are wrapping
*/
public FlowNavigationHandler(NavigationHandler originalNavigationHandler) {
super(originalNavigationHandler);
@@ -118,8 +117,8 @@ public class FlowNavigationHandler extends DecoratingNavigationHandler {
}
/**
* Sets the argument extractor to use by this navigation handler. Call to customize how flow id and event id
* arguments are extracted.
* Sets the argument extractor to use by this navigation handler. Call to
* customize how flow id and event id arguments are extracted.
*/
public void setArgumentExtractor(FlowExecutorArgumentExtractor argumentExtractor) {
this.argumentExtractor = argumentExtractor;
@@ -133,10 +132,10 @@ public class FlowNavigationHandler extends DecoratingNavigationHandler {
}
/**
* Sets the service responsible for mapping attributes of an {@link ExternalContext} to a new {@link FlowExecution}
* during a launch flow operation.
* <p>
* The default implementation simply exposes all request parameters as flow execution input attributes. May be null.
* Sets the service responsible for mapping attributes of an
* {@link ExternalContext} to a new {@link FlowExecution} during a launch
* flow operation. <p> The default implementation simply exposes all request
* parameters as flow execution input attributes. May be null.
* @see RequestParameterInputMapper
*/
public void setInputMapper(AttributeMapper inputMapper) {
@@ -145,49 +144,66 @@ public class FlowNavigationHandler extends DecoratingNavigationHandler {
public void handleNavigation(FacesContext facesContext, String fromAction, String outcome,
NavigationHandler originalNavigationHandler) {
JsfExternalContext context = new JsfExternalContext(facesContext, fromAction, outcome);
// first see if we need to launch a new flow execution if the flow id is present
if (argumentExtractor.isFlowIdPresent(context)) {
// a flow execution launch has been requested - create the new execution
String flowId = argumentExtractor.extractFlowId(context);
FlowDefinition flowDefinition = getLocator(context).getFlowDefinition(flowId);
FlowExecution flowExecution = getFactory(context).createFlowExecution(flowDefinition);
// check to see if this execution was created while another was running
if (FlowExecutionHolderUtils.isFlowExecutionRestored(facesContext)) {
// replace the current flow execution with the new one
FlowExecutionHolderUtils.getFlowExecutionHolder(facesContext).replaceWith(flowExecution);
} else {
// bind the new execution as the 'current execution'
FlowExecutionHolder holder = new FlowExecutionHolder(flowExecution);
FlowExecutionHolderUtils.setFlowExecutionHolder(holder, facesContext);
}
// start the new execution
ViewSelection selectedView = flowExecution.start(createInput(context), context);
// set the starting view to render
FlowExecutionHolderUtils.getFlowExecutionHolder(facesContext).setViewSelection(selectedView);
} else {
// not a launch request - see if this is a resume request to continue an existing execution
if (FlowExecutionHolderUtils.isFlowExecutionRestored(facesContext)) {
// a flow execution has been restored - see if we need to signal an event against it
if (argumentExtractor.isEventIdPresent(context)) {
// signal the event against the current flow execution
String eventId = argumentExtractor.extractEventId(context);
FlowExecutionHolder holder = FlowExecutionHolderUtils.getFlowExecutionHolder(facesContext);
ViewSelection selectedView = holder.getFlowExecution().signalEvent(eventId, context);
// set the next view to render
holder.setViewSelection(selectedView);
try {
JsfExternalContext context = new JsfExternalContext(facesContext, fromAction, outcome);
// first see if we need to launch a new flow execution if the flow id is present
if (argumentExtractor.isFlowIdPresent(context)) {
// a flow execution launch has been requested - create the new execution
String flowId = argumentExtractor.extractFlowId(context);
FlowDefinition flowDefinition = getLocator(context).getFlowDefinition(flowId);
FlowExecution flowExecution = getFactory(context).createFlowExecution(flowDefinition);
// check to see if this execution was created while another was running
if (FlowExecutionHolderUtils.isFlowExecutionRestored(facesContext)) {
// replace the current flow execution with the new one
FlowExecutionHolderUtils.getFlowExecutionHolder(facesContext).replaceWith(flowExecution);
}
else {
// bind the new execution as the 'current execution'
FlowExecutionHolderUtils.setFlowExecutionHolder(new FlowExecutionHolder(flowExecution), facesContext);
}
// start the new execution
ViewSelection selectedView = flowExecution.start(createInput(context), context);
// set the starting view to render
FlowExecutionHolderUtils.getFlowExecutionHolder(facesContext).setViewSelection(selectedView);
}
else {
// neither a flow launch or resume request: proceed with standard navigation
originalNavigationHandler.handleNavigation(facesContext, fromAction, outcome);
// not a launch request - see if this is a resume request to continue an existing execution
if (FlowExecutionHolderUtils.isFlowExecutionRestored(facesContext)) {
// a flow execution has been restored - see if we need to signal an event against it
if (argumentExtractor.isEventIdPresent(context)) {
// signal the event against the current flow execution
String eventId = argumentExtractor.extractEventId(context);
try {
FlowExecutionHolder holder = FlowExecutionHolderUtils.getFlowExecutionHolder(facesContext);
ViewSelection selectedView = holder.getFlowExecution().signalEvent(eventId, context);
// set the next view to render
holder.setViewSelection(selectedView);
} catch (NoMatchingTransitionException e) {
// not a valid event in the current state: proceed with standard navigation
originalNavigationHandler.handleNavigation(facesContext, fromAction, outcome);
}
}
}
else {
// neither a flow launch or resume request: proceed with standard navigation
originalNavigationHandler.handleNavigation(facesContext, fromAction, outcome);
}
}
}
catch (RuntimeException e) {
FlowExecutionHolderUtils.unlockCurrentFlowExecutionIfNecessary(facesContext);
throw e;
}
catch (Error e) {
FlowExecutionHolderUtils.unlockCurrentFlowExecutionIfNecessary(facesContext);
throw e;
}
}
/**
* Factory method that creates the input attribute map for a newly created {@link FlowExecution}. This
* implementation uses the registered input mapper, if any.
* Factory method that creates the input attribute map for a newly created
* {@link FlowExecution}. This implementation uses the registered input
* mapper, if any.
* @param context the external context
* @return the input map, or null if no input
*/

View File

@@ -222,38 +222,48 @@ public class FlowPhaseListener implements PhaseListener {
}
public void beforePhase(PhaseEvent event) {
FacesContext context = event.getFacesContext();
if (event.getPhaseId() == PhaseId.RESTORE_VIEW) {
ExternalContextHolder.setExternalContext(new JsfExternalContext(event.getFacesContext()));
ExternalContextHolder.setExternalContext(new JsfExternalContext(context));
restoreFlowExecution(event.getFacesContext());
}
else if (event.getPhaseId() == PhaseId.RENDER_RESPONSE) {
if (FlowExecutionHolderUtils.isFlowExecutionRestored(event.getFacesContext())) {
prepareResponse(getCurrentContext(), FlowExecutionHolderUtils.getFlowExecutionHolder(event
.getFacesContext()));
try {
prepareResponse(getCurrentContext(), FlowExecutionHolderUtils.getFlowExecutionHolder(context));
} catch (RuntimeException e) {
FlowExecutionHolderUtils.unlockCurrentFlowExecutionIfNecessary(context);
throw e;
} catch (Error e) {
FlowExecutionHolderUtils.unlockCurrentFlowExecutionIfNecessary(context);
throw e;
}
}
}
}
public void afterPhase(PhaseEvent event) {
FacesContext context = event.getFacesContext();
if (event.getPhaseId() == PhaseId.RENDER_RESPONSE) {
try {
if (FlowExecutionHolderUtils.isFlowExecutionRestored(event.getFacesContext())) {
FlowExecutionHolder holder = FlowExecutionHolderUtils.getFlowExecutionHolder(event
.getFacesContext());
if (FlowExecutionHolderUtils.isFlowExecutionRestored(context)) {
FlowExecutionHolder holder = FlowExecutionHolderUtils.getFlowExecutionHolder(context);
try {
saveFlowExecution(getCurrentContext(), holder);
}
finally {
if (holder.getFlowExecutionLock() != null) {
// unlock the flow execution
holder.getFlowExecutionLock().unlock();
}
FlowExecutionHolderUtils.unlockCurrentFlowExecutionIfNecessary(context);
}
}
}
finally {
ExternalContextHolder.setExternalContext(null);
}
} else {
// unlock if some other JSF artifact marked 'response complete' to short-circuit the lifecycle early
if (context.getResponseComplete()) {
FlowExecutionHolderUtils.unlockCurrentFlowExecutionIfNecessary(context);
}
}
}
@@ -263,17 +273,24 @@ public class FlowPhaseListener implements PhaseListener {
// restore flow execution from repository so it will be available to other JSF artifacts
// (this could happen as part of a flow execution redirect or browser refresh)
FlowExecutionRepository repository = getRepository(context);
FlowExecutionKey flowExecutionKey = repository.parseFlowExecutionKey(argumentHandler
.extractFlowExecutionKey(context));
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 key '" + flowExecutionKey + "' due to browser access "
FlowExecution flowExecution;
try {
flowExecution = repository.getFlowExecution(flowExecutionKey);
if (logger.isDebugEnabled()) {
logger.debug("Loaded existing flow execution key '" + flowExecutionKey + "' due to browser access "
+ "[either via a flow execution redirect or direct browser refresh]");
}
} catch (RuntimeException e) {
lock.unlock();
throw e;
} catch (Error e) {
lock.unlock();
throw e;
}
FlowExecutionHolderUtils.setFlowExecutionHolder(new FlowExecutionHolder(flowExecutionKey, flowExecution,
lock), facesContext);
FlowExecutionHolderUtils.setFlowExecutionHolder(new FlowExecutionHolder(flowExecutionKey, flowExecution, lock), facesContext);
}
else if (argumentHandler.isFlowIdPresent(context)) {
// launch a new flow execution
@@ -438,8 +455,8 @@ public class FlowPhaseListener implements PhaseListener {
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);
// expose as the in the view root for preservation in the tree
facesContext.getViewRoot().getChildren().add(keyHolder);
}
keyHolder.setFlowExecutionKey(flowExecutionKey);
}