diff --git a/spring-webflow/src/main/java/org/springframework/webflow/executor/jsf/FlowNavigationHandler.java b/spring-webflow/src/main/java/org/springframework/webflow/executor/jsf/FlowNavigationHandler.java index 8456883f..610a3aab 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/executor/jsf/FlowNavigationHandler.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/executor/jsf/FlowNavigationHandler.java @@ -20,14 +20,17 @@ 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; /** @@ -80,6 +83,21 @@ public class FlowNavigationHandler extends DecoratingNavigationHandler { */ 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. + *
+ * This allows developers to control what attributes are made available in
+ * the inputMap to new top-level flow executions. The
+ * starting execution may then choose to map that available input into its
+ * own local scope.
+ *
+ * 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. */ @@ -110,7 +128,26 @@ public class FlowNavigationHandler extends DecoratingNavigationHandler { 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. + *
+ * 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); @@ -135,7 +172,7 @@ public class FlowNavigationHandler extends DecoratingNavigationHandler { FlowExecution flowExecution = getFactory(context).createFlowExecution(flowDefinition); FlowExecutionHolder holder = new FlowExecutionHolder(flowExecution); FlowExecutionHolderUtils.setFlowExecutionHolder(holder, facesContext); - ViewSelection selectedView = flowExecution.start(createInput(flowExecution, context), context); + ViewSelection selectedView = flowExecution.start(createInput(context), context); holder.setViewSelection(selectedView); } else { @@ -155,12 +192,19 @@ public class FlowNavigationHandler extends DecoratingNavigationHandler { /** * Factory method that creates the input attribute map for a newly created - * {@link FlowExecution}. TODO - add support for input mappings here - * @param flowExecution the new flow execution (yet to be started) + * {@link FlowExecution}. This implementation uses the registered input mapper, + * if any. * @param context the external context - * @return the input map + * @return the input map, or null if no input */ - protected MutableAttributeMap createInput(FlowExecution flowExecution, ExternalContext context) { - return null; + protected MutableAttributeMap createInput(ExternalContext context) { + if (inputMapper != null) { + MutableAttributeMap inputMap = new LocalAttributeMap(); + inputMapper.map(context, inputMap, null); + return inputMap; + } + else { + return null; + } } } \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/executor/jsf/FlowPhaseListener.java b/spring-webflow/src/main/java/org/springframework/webflow/executor/jsf/FlowPhaseListener.java index 8da8720f..4d9abd5e 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/executor/jsf/FlowPhaseListener.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/executor/jsf/FlowPhaseListener.java @@ -28,9 +28,11 @@ import javax.faces.event.PhaseListener; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.binding.mapping.AttributeMapper; import org.springframework.webflow.context.ExternalContext; import org.springframework.webflow.context.ExternalContextHolder; 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; @@ -43,6 +45,7 @@ import org.springframework.webflow.execution.support.ApplicationView; import org.springframework.webflow.execution.support.ExternalRedirect; import org.springframework.webflow.execution.support.FlowDefinitionRedirect; import org.springframework.webflow.execution.support.FlowExecutionRedirect; +import org.springframework.webflow.executor.RequestParameterInputMapper; import org.springframework.webflow.executor.ResponseInstruction; import org.springframework.webflow.executor.support.FlowExecutorArgumentHandler; import org.springframework.webflow.executor.support.RequestParameterFlowExecutorArgumentHandler; @@ -76,12 +79,27 @@ public class FlowPhaseListener implements PhaseListener { * Logger, usable by subclasses. */ protected final Log logger = LogFactory.getLog(getClass()); - + /** - * A helper for handling arguments needed by this phase listener. + * A helper for handling arguments needed by this phase listener to resume and launch flow executions. */ private FlowExecutorArgumentHandler argumentHandler = new RequestParameterFlowExecutorArgumentHandler(); + /** + * The service responsible for mapping attributes of an + * {@link ExternalContext} to a new {@link FlowExecution} during the + * {@link #launch(String, ExternalContext) launch flow} operation. + *
+ * This allows developers to control what attributes are made available in
+ * the inputMap to new top-level flow executions. The
+ * starting execution may then choose to map that available input into its
+ * own local scope.
+ *
+ * The default implementation simply exposes all request parameters as flow + * execution input attributes. May be null. + */ + private AttributeMapper inputMapper = new RequestParameterInputMapper(); + /** * Resolves selected Web Flow view names to JSF view ids. */ @@ -101,6 +119,25 @@ public class FlowPhaseListener implements PhaseListener { this.argumentHandler = argumentHandler; } + /** + * 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. + *
+ * 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; + } + /** * Returns the JSF view id resolver used by this phase listener. */ @@ -142,7 +179,9 @@ public class FlowPhaseListener implements PhaseListener { saveFlowExecution(getCurrentContext(), holder); } finally { - holder.getFlowExecutionLock().unlock(); + if (holder.getFlowExecutionLock() != null) { + holder.getFlowExecutionLock().unlock(); + } } } } @@ -167,6 +206,7 @@ public class FlowPhaseListener implements PhaseListener { 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 + "'"); @@ -182,7 +222,7 @@ public class FlowPhaseListener implements PhaseListener { FlowExecution flowExecution = getFactory(context).createFlowExecution(flowDefinition); FlowExecutionHolder holder = new FlowExecutionHolder(flowExecution); FlowExecutionHolderUtils.setFlowExecutionHolder(holder, facesContext); - ViewSelection selectedView = flowExecution.start(createInput(flowExecution, context), context); + ViewSelection selectedView = flowExecution.start(createInput(context), context); if (logger.isDebugEnabled()) { logger.debug("Started new flow execution"); } @@ -192,13 +232,20 @@ public class FlowPhaseListener implements PhaseListener { /** * Factory method that creates the input attribute map for a newly created - * {@link FlowExecution}. TODO - add support for input mappings here - * @param flowExecution the new flow execution (yet to be started) + * {@link FlowExecution}. This implementation uses the registered input mapper, + * if any. * @param context the external context - * @return the input map + * @return the input map, or null if no input */ - protected LocalAttributeMap createInput(FlowExecution flowExecution, ExternalContext context) { - return null; + protected MutableAttributeMap createInput(ExternalContext context) { + if (inputMapper != null) { + MutableAttributeMap inputMap = new LocalAttributeMap(); + inputMapper.map(context, inputMap, null); + return inputMap; + } + else { + return null; + } } protected void prepareResponse(final JsfExternalContext context, final FlowExecutionHolder holder) { diff --git a/spring-webflow/src/main/java/org/springframework/webflow/executor/jsf/FlowPropertyResolver.java b/spring-webflow/src/main/java/org/springframework/webflow/executor/jsf/FlowPropertyResolver.java index 036141b7..68a5ce19 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/executor/jsf/FlowPropertyResolver.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/executor/jsf/FlowPropertyResolver.java @@ -29,10 +29,6 @@ import org.springframework.webflow.execution.FlowExecution; /** * Custom property resolve that resolves active flow session scope properties * from a thread bound flow execution. - *
- * TODO: this class probably needs to delegate to a strategy object pulled out - * of the appcontext, to provide ability to override and configure, as JSF - * provides no other way to customize and configure this instance. * * @author Colin Sampaleanu */