swf-290
This commit is contained in:
@@ -24,24 +24,22 @@ import org.springframework.webflow.execution.repository.FlowExecutionKey;
|
||||
import org.springframework.webflow.execution.repository.FlowExecutionLock;
|
||||
|
||||
/**
|
||||
* A holder storing a reference to a flow execution and the key of that flow
|
||||
* execution if it has been (or is about to be) managed in a repository.
|
||||
* A holder storing a reference to a flow execution and the key of that flow execution if it has been (or is about to
|
||||
* be) managed in a repository.
|
||||
*
|
||||
* @author Keith Donald
|
||||
*/
|
||||
public class FlowExecutionHolder implements Serializable {
|
||||
|
||||
/**
|
||||
* The flow execution continuation key (may be null if the flow execution
|
||||
* has not yet been generated a repository key). May change as well over the
|
||||
* life of this object, as a flow execution can be given a new key to
|
||||
* capture its state at another point in time.
|
||||
* The flow execution continuation key (may be null if the flow execution has not yet been generated a repository
|
||||
* key). May change as well over the life of this object, as a flow execution can be given a new key to capture its
|
||||
* state at another point in time.
|
||||
*/
|
||||
private FlowExecutionKey flowExecutionKey;
|
||||
|
||||
/**
|
||||
* The held flow execution representing the state of an ongoing conversation
|
||||
* at a point in time.
|
||||
* The held flow execution representing the state of an ongoing conversation at a point in time.
|
||||
*/
|
||||
private FlowExecution flowExecution;
|
||||
|
||||
@@ -56,8 +54,7 @@ public class FlowExecutionHolder implements Serializable {
|
||||
private ViewSelection viewSelection;
|
||||
|
||||
/**
|
||||
* Creates a new flow execution holder for a flow execution that has not yet
|
||||
* been placed in a repository.
|
||||
* Creates a new flow execution holder for a flow execution that has not yet been placed in a repository.
|
||||
* @param flowExecution the flow execution to hold
|
||||
*/
|
||||
public FlowExecutionHolder(FlowExecution flowExecution) {
|
||||
@@ -65,8 +62,7 @@ public class FlowExecutionHolder implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new flow execution holder for a flow execution that has been
|
||||
* restored from a repository.
|
||||
* Creates a new flow execution holder for a flow execution that has been restored from a repository.
|
||||
* @param flowExecutionKey the continuation key
|
||||
* @param flowExecution the flow execution to hold
|
||||
* @param flowExecutionLock the lock acquired on the flow execution
|
||||
@@ -98,7 +94,7 @@ public class FlowExecutionHolder implements Serializable {
|
||||
public FlowExecution getFlowExecution() {
|
||||
return flowExecution;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the flow execution lock
|
||||
*/
|
||||
@@ -121,6 +117,20 @@ public class FlowExecutionHolder implements Serializable {
|
||||
this.viewSelection = viewSelection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the current flow execution with the one provided. This method will clear out all state associated with
|
||||
* the original execution and unlock it if necessary.
|
||||
* @param flowExecution the new "current" flow execution
|
||||
*/
|
||||
public void replaceWith(FlowExecution flowExecution) {
|
||||
this.flowExecutionKey = null;
|
||||
this.viewSelection = null;
|
||||
if (flowExecutionLock != null) {
|
||||
flowExecutionLock.unlock();
|
||||
}
|
||||
this.flowExecution = flowExecution;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return new ToStringCreator(this).append("flowExecutionKey", flowExecutionKey).append("flowExecution",
|
||||
flowExecution).toString();
|
||||
|
||||
@@ -18,13 +18,26 @@ package org.springframework.webflow.executor.jsf;
|
||||
import javax.faces.application.NavigationHandler;
|
||||
import javax.faces.context.FacesContext;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.binding.mapping.AttributeMapper;
|
||||
import org.springframework.web.jsf.DecoratingNavigationHandler;
|
||||
import org.springframework.webflow.context.ExternalContext;
|
||||
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.execution.FlowExecution;
|
||||
import org.springframework.webflow.execution.FlowExecutionFactory;
|
||||
import org.springframework.webflow.execution.ViewSelection;
|
||||
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 resume flow executions by treating JSF action outcomes
|
||||
* 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.
|
||||
* <p>
|
||||
*
|
||||
* This class delegates to the standard NavigationHandler implementation when a navigation request does not pertain to a
|
||||
* flow execution.
|
||||
* <p>
|
||||
@@ -33,43 +46,169 @@ import org.springframework.webflow.execution.ViewSelection;
|
||||
* <p>
|
||||
* If a flow execution has been restored in the current request:
|
||||
* <ul>
|
||||
* <li>Continue the current flow execution by signaling the JSF action outcome as an event against the current state.
|
||||
* <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 simply delegate to the next NavigationHandler in
|
||||
* the chain and return.
|
||||
* 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.
|
||||
*
|
||||
* @author Craig McClanahan
|
||||
* @author Colin Sampaleanu
|
||||
* @author Keith Donald
|
||||
*/
|
||||
public class FlowNavigationHandler extends NavigationHandler {
|
||||
public class FlowNavigationHandler extends DecoratingNavigationHandler {
|
||||
|
||||
/**
|
||||
* The standard navigation handler to delegate to when this one does not apply.
|
||||
* Logger, usable by subclasses.
|
||||
*/
|
||||
private NavigationHandler handlerDelegate;
|
||||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
/**
|
||||
* Create a new {@link FlowNavigationHandler} wrapping the specified standard navigation handler implementation.
|
||||
* @param handlerDelegate the standard <code>NavigationHandler</code> this class decorates
|
||||
* A helper for extracting parameters needed by this flow navigation handler.
|
||||
*/
|
||||
public FlowNavigationHandler(NavigationHandler handlerDelegate) {
|
||||
this.handlerDelegate = handlerDelegate;
|
||||
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.
|
||||
*/
|
||||
private AttributeMapper inputMapper = new RequestParameterInputMapper();
|
||||
|
||||
/**
|
||||
* Create a new {@link FlowNavigationHandler} using the default constructor.
|
||||
*/
|
||||
public FlowNavigationHandler() {
|
||||
super();
|
||||
}
|
||||
|
||||
public void handleNavigation(FacesContext facesContext, String fromAction, String outcome) {
|
||||
if (FlowExecutionHolderUtils.isFlowExecutionRestored(facesContext)) {
|
||||
// a flow execution has been restored - signal an event against it
|
||||
FlowExecutionHolder holder = FlowExecutionHolderUtils.getFlowExecutionHolder(facesContext);
|
||||
JsfExternalContext context = new JsfExternalContext(facesContext, fromAction, outcome);
|
||||
ViewSelection selectedView = holder.getFlowExecution().signalEvent(outcome, context);
|
||||
holder.setViewSelection(selectedView);
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the argument extractor used by this navigation handler.
|
||||
*/
|
||||
public FlowExecutorArgumentExtractor getArgumentExtractor() {
|
||||
return argumentExtractor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configured flow execution input mapper.
|
||||
*/
|
||||
public AttributeMapper getInputMapper() {
|
||||
return inputMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
this.inputMapper = inputMapper;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// neither a flow launch or resume request: proceed with standard navigation
|
||||
originalNavigationHandler.handleNavigation(facesContext, fromAction, outcome);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
protected MutableAttributeMap createInput(ExternalContext context) {
|
||||
if (inputMapper != null) {
|
||||
MutableAttributeMap inputMap = new LocalAttributeMap();
|
||||
inputMapper.map(context, inputMap, null);
|
||||
return inputMap;
|
||||
}
|
||||
else {
|
||||
// no flow execution is restored, proceed with standard navigation
|
||||
handlerDelegate.handleNavigation(facesContext, fromAction, outcome);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// helpers
|
||||
|
||||
private FlowDefinitionLocator getLocator(JsfExternalContext context) {
|
||||
return FlowFacesUtils.getDefinitionLocator(context.getFacesContext());
|
||||
}
|
||||
|
||||
private FlowExecutionFactory getFactory(JsfExternalContext context) {
|
||||
return FlowFacesUtils.getExecutionFactory(context.getFacesContext());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
package org.springframework.webflow.executor.jsf;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.webflow.context.ExternalContext;
|
||||
import org.springframework.webflow.executor.support.FlowExecutorArgumentExtractionException;
|
||||
import org.springframework.webflow.executor.support.FlowExecutorArgumentExtractor;
|
||||
|
||||
/**
|
||||
* An {@link FlowExecutorArgumentExtractor} that is aware of JSF outcomes that communicate requests to launch flow
|
||||
* executions and signal event in existing flow executions. Designed to be used wih a {@link FlowNavigationHandler}.
|
||||
*
|
||||
* Note: this class only implements flow id and event id extraction methods. A FlowNavigationHandler is not expected to
|
||||
* extract a flow execution key, as flow execution restoration is fully handled by the {@link FlowPhaseListener} and the
|
||||
* JSF restore view phase.
|
||||
*
|
||||
* @author Keith Donald
|
||||
*/
|
||||
public class FlowNavigationHandlerArgumentExtractor implements FlowExecutorArgumentExtractor {
|
||||
|
||||
/**
|
||||
* The default prefix of a JSF outcome string that indicates a new flow should be launched.
|
||||
*/
|
||||
private static final String FLOW_ID_PREFIX = "flowId:";
|
||||
|
||||
/**
|
||||
* The prefix for JSF outcome strings indicating a new flow should be launched.
|
||||
*/
|
||||
private String flowIdPrefix = FLOW_ID_PREFIX;
|
||||
|
||||
/**
|
||||
* Returns the configured prefix for outcome strings that indicate a new flow should be launched.
|
||||
*/
|
||||
public String getFlowIdPrefix() {
|
||||
return flowIdPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the prefix of an outcome string that indicates a new flow should be launched.
|
||||
*/
|
||||
public void setFlowIdPrefix(String flowIdPrefix) {
|
||||
this.flowIdPrefix = flowIdPrefix;
|
||||
}
|
||||
|
||||
public boolean isFlowIdPresent(ExternalContext context) throws FlowExecutorArgumentExtractionException {
|
||||
String outcome = getOutcome(context);
|
||||
if (outcome != null && outcome.startsWith(getFlowIdPrefix())) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String extractFlowId(ExternalContext context) throws FlowExecutorArgumentExtractionException {
|
||||
// extract the flowId from a JSF outcome in format <code>${flowIdPrefix}${flowId}</code>
|
||||
String outcome = getOutcome(context);
|
||||
int index = outcome.indexOf(getFlowIdPrefix());
|
||||
if (index == -1) {
|
||||
throw new FlowExecutorArgumentExtractionException(
|
||||
"Unable to extract flow id; make sure the JSF outcome is prefixed with '" + getFlowIdPrefix()
|
||||
+ "' to launch a new flow execution");
|
||||
}
|
||||
String flowId = outcome.substring(getFlowIdPrefix().length());
|
||||
if (!StringUtils.hasText(flowId)) {
|
||||
throw new FlowExecutorArgumentExtractionException(
|
||||
"Unable to extract flow id; make sure the flow id is provided in the outcome string");
|
||||
}
|
||||
return flowId;
|
||||
}
|
||||
|
||||
public boolean isEventIdPresent(ExternalContext context) {
|
||||
return StringUtils.hasText(getOutcome(context));
|
||||
}
|
||||
|
||||
public String extractEventId(ExternalContext context) throws FlowExecutorArgumentExtractionException {
|
||||
// treat the action outcome string as the event id
|
||||
return getOutcome(context);
|
||||
}
|
||||
|
||||
public boolean isFlowExecutionKeyPresent(ExternalContext context) {
|
||||
throw new UnsupportedOperationException("Should not be called by a FlowNavigationHandler");
|
||||
}
|
||||
|
||||
public String extractFlowExecutionKey(ExternalContext context) throws FlowExecutorArgumentExtractionException {
|
||||
throw new UnsupportedOperationException("Should not be called by a FlowNavigationHandler");
|
||||
}
|
||||
|
||||
// helpers
|
||||
private String getOutcome(ExternalContext context) {
|
||||
return ((JsfExternalContext) context).getOutcome();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user