greatly simplified flow navigation handler implementation by removing its dual responsibilty of launching and resuming flows - SWF 290

This commit is contained in:
Keith Donald
2007-04-12 20:02:03 +00:00
parent a81f067c37
commit aa8ce4d470
2 changed files with 21 additions and 257 deletions

View File

@@ -18,26 +18,13 @@ 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 launch and resume flow executions, treating JSF action outcomes
* Responsible for delegating to Spring Web Flow to resume flow executions by 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>
@@ -46,159 +33,43 @@ import org.springframework.webflow.executor.support.FlowExecutorArgumentExtracto
* <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>Continue the current 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.
* If a flow execution has not been restored in the current request simply delegate to the next NavigationHandler in
* the chain and return.
*
* @author Craig McClanahan
* @author Colin Sampaleanu
* @author Keith Donald
*/
public class FlowNavigationHandler extends DecoratingNavigationHandler {
public class FlowNavigationHandler extends NavigationHandler {
/**
* Logger, usable by subclasses.
* The standard navigation handler to delegate to when this one does not apply.
*/
protected final Log logger = LogFactory.getLog(getClass());
private NavigationHandler delegateHandler;
/**
* A helper for extracting parameters needed by this flow navigation handler.
* Create a new {@link FlowNavigationHandler} wrapping the specified standard navigation handler implementation.
* @param delegateHandler the standard <code>NavigationHandler</code> this class decorates
*/
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 FlowNavigationHandler(NavigationHandler delegateHandler) {
this.delegateHandler = delegateHandler;
}
/**
* 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);
public void handleNavigation(FacesContext facesContext, String fromAction, String outcome) {
if (FlowExecutionHolderUtils.isFlowExecutionRestored(facesContext)) {
// the flow execution has been restored, now see if we need to signal an event against it
if (argumentExtractor.isEventIdPresent(context)) {
// a flow execution has been restored, signal an event in it
String eventId = argumentExtractor.extractEventId(context);
FlowExecutionHolder holder = FlowExecutionHolderUtils.getFlowExecutionHolder(facesContext);
ViewSelection selectedView = holder.getFlowExecution().signalEvent(eventId, context);
holder.setViewSelection(selectedView);
}
// 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);
}
else {
// no flow execution exists, see if we need to launch one if the flow id is present
if (argumentExtractor.isFlowIdPresent(context)) {
// a flow execution launch has been requested, start it
String flowId = argumentExtractor.extractFlowId(context);
FlowDefinition flowDefinition = getLocator(context).getFlowDefinition(flowId);
FlowExecution flowExecution = getFactory(context).createFlowExecution(flowDefinition);
FlowExecutionHolder holder = new FlowExecutionHolder(flowExecution);
FlowExecutionHolderUtils.setFlowExecutionHolder(holder, facesContext);
ViewSelection selectedView = flowExecution.start(createInput(context), context);
holder.setViewSelection(selectedView);
}
else {
// no flow id submitted, proceed with std navigation
originalNavigationHandler.handleNavigation(facesContext, fromAction, outcome);
}
// no flow execution is restored, proceed with std navigation
delegateHandler.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 {
return null;
}
}
// helpers
private FlowDefinitionLocator getLocator(JsfExternalContext context) {
return FlowFacesUtils.getDefinitionLocator(context.getFacesContext());
}
private FlowExecutionFactory getFactory(JsfExternalContext context) {
return FlowFacesUtils.getExecutionFactory(context.getFacesContext());
}
}

View File

@@ -1,107 +0,0 @@
/*
* 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();
}
}