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 ec19833a..e3e54794 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 @@ -53,36 +53,46 @@ import org.springframework.webflow.executor.support.RequestParameterFlowExecutor import org.springframework.webflow.executor.support.ResponseInstructionHandler; /** - * JSF phase listener responsible for managing the {@link FlowExecution} object lifecycle in a JSF environment. This - * class handles restoring and saving a FlowExecution so other JSF artifacts that execute in different phases of the JSF - * lifecycle may access conversational state and utilize Web Flow navigation behavior. + * JSF phase listener responsible for managing the {@link FlowExecution} object + * lifecycle in a JSF environment. This class handles restoring and saving a + * FlowExecution so other JSF artifacts that execute in different phases of the + * JSF lifecycle may access conversational state and utilize Web Flow navigation + * behavior. *
- * A restored flow execution is placed in a holder that other JSF artifacts such as VariableResolvers, PropertyResolvers - * and NavigationHandlers may access during the request lifecycle. Once in the holder the execution is considered + * A restored flow execution is placed in a holder that other JSF artifacts such + * as VariableResolvers, PropertyResolvers and NavigationHandlers may access + * during the request lifecycle. Once in the holder the execution is considered * "restored" and referred to as the "current" flow execution for this request. *
*
* This phase listener implements the following algorithm: *
- * 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.
+ * 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. + * The default implementation simply exposes all request parameters as flow + * execution input attributes. May be null. */ private AttributeMapper inputMapper = new RequestParameterInputMapper(); @@ -145,22 +164,26 @@ public class FlowPhaseListener implements PhaseListener { } /** - * Sets the handler for arguments needed by this phase listener to restore and launch flow executions. This handler - * is responsible for two things: + * Sets the handler for arguments needed by this phase listener to restore + * and launch flow executions. This handler is responsible for two things: *
- * The default implementation simply exposes all request parameters as flow execution input attributes. May be null.
+ * The default implementation simply exposes all request parameters as flow
+ * execution input attributes. May be null.
+ *
* @see RequestParameterInputMapper
*/
public void setInputMapper(AttributeMapper inputMapper) {
@@ -194,7 +220,27 @@ public class FlowPhaseListener implements PhaseListener {
}
/**
- * Sets the JSF view id mapper used by this phase listener.
+ * Sets the JSF view id mapper used by this phase listener. The
+ * {@link ViewIdMapper} provides a mechanism to convert a logical SWF view
+ * id into JSF view id.
+ *
+ * View ids are important to JSF: it uses them to check whether a view has
+ * changed, and also to locate the physical template for the layout. The JSF
+ * view id is not the exact physical location of a view template, nor is it
+ * a purely logical value: it normally specifies the physical location of a
+ * view template minus a suffix.
+ *
+ * JSF replaces the suffix of any view id it gets with its own default
+ * suffix (e.g. ".jsp" or ".facelet"), and then tries to locate a physical
+ * template view. Thus the {@link ViewIdMapper} gives us a chance to convert
+ * a SWF view id into a physical path that will be used by JSF to locate the
+ * template. (It doesn't matter what suffix we use on the JSF view id, it
+ * will always be replaced to locate the phyical template.)
+ *
+ * The default value for the view id mapper is a {@link DefaultViewIdMapper}
+ * which just passes through the SWF viewId.
+ *
+ * @see ViewIdMapper
*/
public void setViewIdMapper(ViewIdMapper viewIdMapper) {
this.viewIdMapper = viewIdMapper;
@@ -206,13 +252,14 @@ public class FlowPhaseListener implements PhaseListener {
public void beforePhase(PhaseEvent event) {
if (event.getPhaseId() == PhaseId.RESTORE_VIEW) {
- ExternalContextHolder.setExternalContext(new JsfExternalContext(event.getFacesContext()));
+ 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()));
+ } else if (event.getPhaseId() == PhaseId.RENDER_RESPONSE) {
+ if (FlowExecutionHolderUtils.isFlowExecutionRestored(event
+ .getFacesContext())) {
+ prepareResponse(getCurrentContext(), FlowExecutionHolderUtils
+ .getFlowExecutionHolder(event.getFacesContext()));
}
}
}
@@ -220,20 +267,19 @@ public class FlowPhaseListener implements PhaseListener {
public void afterPhase(PhaseEvent event) {
if (event.getPhaseId() == PhaseId.RENDER_RESPONSE) {
try {
- if (FlowExecutionHolderUtils.isFlowExecutionRestored(event.getFacesContext())) {
- FlowExecutionHolder holder = FlowExecutionHolderUtils.getFlowExecutionHolder(event
- .getFacesContext());
+ if (FlowExecutionHolderUtils.isFlowExecutionRestored(event
+ .getFacesContext())) {
+ FlowExecutionHolder holder = FlowExecutionHolderUtils
+ .getFlowExecutionHolder(event.getFacesContext());
try {
saveFlowExecution(getCurrentContext(), holder);
- }
- finally {
+ } finally {
if (holder.getFlowExecutionLock() != null) {
holder.getFlowExecutionLock().unlock();
}
}
}
- }
- finally {
+ } finally {
ExternalContextHolder.setExternalContext(null);
}
}
@@ -242,42 +288,58 @@ public class FlowPhaseListener implements PhaseListener {
protected void restoreFlowExecution(FacesContext facesContext) {
JsfExternalContext context = new JsfExternalContext(facesContext);
if (argumentHandler.isFlowExecutionKeyPresent(context)) {
- // 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)
+ // 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);
+ FlowExecution 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]");
+ logger
+ .debug("Loaded existing flow execution key '"
+ + flowExecutionKey
+ + "' due to browser access "
+ + "[either via a flow execution redirect or direct browser refresh]");
}
- FlowExecutionHolderUtils.setFlowExecutionHolder(new FlowExecutionHolder(flowExecutionKey, flowExecution,
- lock), facesContext);
- }
- else if (argumentHandler.isFlowIdPresent(context)) {
+ FlowExecutionHolderUtils.setFlowExecutionHolder(
+ new FlowExecutionHolder(flowExecutionKey, flowExecution,
+ lock), facesContext);
+ } else if (argumentHandler.isFlowIdPresent(context)) {
// launch a new flow execution
- // (this could happen as part of direct browser access or a flow definition redirect)
+ // (this could happen as part of direct browser access or a flow
+ // definition redirect)
String flowId = argumentHandler.extractFlowId(context);
- FlowDefinition flowDefinition = getLocator(context).getFlowDefinition(flowId);
- FlowExecution flowExecution = getFactory(context).createFlowExecution(flowDefinition);
+ 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);
+ FlowExecutionHolderUtils.setFlowExecutionHolder(holder,
+ facesContext);
+ ViewSelection selectedView = flowExecution.start(
+ createInput(context), context);
holder.setViewSelection(selectedView);
if (logger.isDebugEnabled()) {
- logger.debug("Launched a new flow execution due to browser access "
- + "[either via a flow redirect or direct browser URL access]");
+ logger
+ .debug("Launched a new flow execution due to browser access "
+ + "[either via a flow redirect or direct browser URL access]");
}
}
}
/**
- * 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
+ * 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) {
@@ -285,18 +347,22 @@ public class FlowPhaseListener implements PhaseListener {
MutableAttributeMap inputMap = new LocalAttributeMap();
inputMapper.map(context, inputMap, null);
return inputMap;
- }
- else {
+ } else {
return null;
}
}
/**
- * Prepare the appropriate JSF response (e.g. rendering a view, sending a redirect, etc).
- * @param context the context
- * @param holder the holder
+ * Prepare the appropriate JSF response (e.g. rendering a view, sending a
+ * redirect, etc).
+ *
+ * @param context
+ * the context
+ * @param holder
+ * the holder
*/
- protected void prepareResponse(final JsfExternalContext context, final FlowExecutionHolder holder) {
+ protected void prepareResponse(final JsfExternalContext context,
+ final FlowExecutionHolder holder) {
generateKey(context, holder);
ViewSelection selectedView = holder.getViewSelection();
if (selectedView == null) {
@@ -304,25 +370,33 @@ public class FlowPhaseListener implements PhaseListener {
holder.setViewSelection(selectedView);
}
new ResponseInstructionHandler() {
- protected void handleApplicationView(ApplicationView view) throws Exception {
+ protected void handleApplicationView(ApplicationView view)
+ throws Exception {
prepareApplicationView(context.getFacesContext(), holder);
}
- protected void handleFlowDefinitionRedirect(FlowDefinitionRedirect redirect) throws Exception {
- String url = argumentHandler.createFlowDefinitionUrl(redirect, context);
+ protected void handleFlowDefinitionRedirect(
+ FlowDefinitionRedirect redirect) throws Exception {
+ String url = argumentHandler.createFlowDefinitionUrl(redirect,
+ context);
sendRedirect(url, context);
}
- protected void handleFlowExecutionRedirect(FlowExecutionRedirect redirect) throws Exception {
- String url = argumentHandler.createFlowExecutionUrl(holder.getFlowExecutionKey().toString(), holder
+ protected void handleFlowExecutionRedirect(
+ FlowExecutionRedirect redirect) throws Exception {
+ String url = argumentHandler.createFlowExecutionUrl(holder
+ .getFlowExecutionKey().toString(), holder
.getFlowExecution(), context);
sendRedirect(url, context);
}
- protected void handleExternalRedirect(ExternalRedirect redirect) throws Exception {
- String flowExecutionKey = holder.getFlowExecution().isActive() ? holder.getFlowExecutionKey()
- .toString() : null;
- String url = argumentHandler.createExternalUrl(redirect, flowExecutionKey, context);
+ protected void handleExternalRedirect(ExternalRedirect redirect)
+ throws Exception {
+ String flowExecutionKey = holder.getFlowExecution().isActive() ? holder
+ .getFlowExecutionKey().toString()
+ : null;
+ String url = argumentHandler.createExternalUrl(redirect,
+ flowExecutionKey, context);
sendRedirect(url, context);
}
@@ -330,50 +404,65 @@ public class FlowPhaseListener implements PhaseListener {
// nothing to do
}
- }.handleQuietly(new ResponseInstruction(holder.getFlowExecution(), selectedView));
+ }.handleQuietly(new ResponseInstruction(holder.getFlowExecution(),
+ selectedView));
}
/**
* Prepare the JSF view for rendering.
- * @param facesContext the faces context
- * @param holder the holder of the current flow execution
+ *
+ * @param facesContext
+ * the faces context
+ * @param holder
+ * the holder of the current flow execution
*/
- protected void prepareApplicationView(FacesContext facesContext, FlowExecutionHolder holder) {
+ protected void prepareApplicationView(FacesContext facesContext,
+ FlowExecutionHolder holder) {
ApplicationView view = (ApplicationView) holder.getViewSelection();
if (view != null) {
// expose the view's "model map" in the request map
- putInto(facesContext.getExternalContext().getRequestMap(), view.getModel());
+ putInto(facesContext.getExternalContext().getRequestMap(), view
+ .getModel());
// update the root component if necessary
- updateViewRoot(facesContext, viewIdMapper.mapViewId(view.getViewName()));
+ updateViewRoot(facesContext, viewIdMapper.mapViewId(view
+ .getViewName()));
}
- String flowExecutionKey = holder.getFlowExecution().isActive() ? holder.getFlowExecutionKey().toString() : null;
+ String flowExecutionKey = holder.getFlowExecution().isActive() ? holder
+ .getFlowExecutionKey().toString() : null;
if (flowExecutionKey != null) {
saveInViewRoot(facesContext, flowExecutionKey);
}
Map requestMap = facesContext.getExternalContext().getRequestMap();
- argumentHandler.exposeFlowExecutionContext(flowExecutionKey, holder.getFlowExecution(), requestMap);
+ argumentHandler.exposeFlowExecutionContext(flowExecutionKey, holder
+ .getFlowExecution(), requestMap);
}
/**
* Updates the current flow execution in the repository.
- * @param context the external context
- * @param holder the current flow execution holder
+ *
+ * @param context
+ * the external context
+ * @param holder
+ * the current flow execution holder
*/
- protected void saveFlowExecution(JsfExternalContext context, FlowExecutionHolder holder) {
+ protected void saveFlowExecution(JsfExternalContext context,
+ FlowExecutionHolder holder) {
FlowExecution flowExecution = holder.getFlowExecution();
FlowExecutionRepository repository = getRepository(context);
if (flowExecution.isActive()) {
// save the flow execution out to the repository
if (logger.isDebugEnabled()) {
- logger.debug("Saving continuation to repository with key " + holder.getFlowExecutionKey());
+ logger.debug("Saving continuation to repository with key "
+ + holder.getFlowExecutionKey());
}
- repository.putFlowExecution(holder.getFlowExecutionKey(), flowExecution);
- }
- else {
+ repository.putFlowExecution(holder.getFlowExecutionKey(),
+ flowExecution);
+ } else {
if (holder.getFlowExecutionKey() != null) {
// remove the flow execution from the repository
if (logger.isDebugEnabled()) {
- logger.debug("Removing execution in repository with key '" + holder.getFlowExecutionKey() + "'");
+ logger.debug("Removing execution in repository with key '"
+ + holder.getFlowExecutionKey() + "'");
}
repository.removeFlowExecution(holder.getFlowExecutionKey());
}
@@ -390,7 +479,8 @@ public class FlowPhaseListener implements PhaseListener {
UIViewRoot viewRoot = facesContext.getViewRoot();
if (viewRoot == null || hasViewChanged(viewRoot, viewId)) {
// create the specified view so that it can be rendered
- ViewHandler handler = facesContext.getApplication().getViewHandler();
+ ViewHandler handler = facesContext.getApplication()
+ .getViewHandler();
UIViewRoot view = handler.createView(facesContext, viewId);
facesContext.setViewRoot(view);
}
@@ -401,52 +491,64 @@ public class FlowPhaseListener implements PhaseListener {
}
/**
- * 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
+ * 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) {
+ private void saveInViewRoot(FacesContext facesContext,
+ String flowExecutionKey) {
// search for key holder in the component tree
- FlowExecutionKeyStateHolder keyHolder = (FlowExecutionKeyStateHolder) facesContext.getViewRoot().findComponent(
- FlowExecutionKeyStateHolder.COMPONENT_ID);
+ 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
+ // 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) {
+ 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) {
// it is an new conversation, generate a brand new key
flowExecutionKey = repository.generateKey(flowExecution);
- }
- else {
- // it is an existing conversaiton, use same conversation id, generate a new continuation id
- flowExecutionKey = repository.getNextKey(flowExecution, flowExecutionKey);
+ } else {
+ // it is an existing conversaiton, use same conversation id,
+ // generate a new continuation id
+ flowExecutionKey = repository.getNextKey(flowExecution,
+ flowExecutionKey);
}
holder.setFlowExecutionKey(flowExecutionKey);
}
}
/**
- * Utility method needed needed only because we can not rely on JSF RequestMap supporting Map's putAll method. Tries
- * putAll, falls back to individual adds
- * @param targetMap the target map to add the model data to
- * @param map the model data to add to the target map
+ * Utility method needed needed only because we can not rely on JSF
+ * RequestMap supporting Map's putAll method. Tries putAll, falls back to
+ * individual adds
+ *
+ * @param targetMap
+ * the target map to add the model data to
+ * @param map
+ * the model data to add to the target map
*/
private void putInto(Map targetMap, Map map) {
try {
targetMap.putAll(map);
- }
- catch (UnsupportedOperationException e) {
+ } catch (UnsupportedOperationException e) {
// work around nasty MyFaces bug where it's RequestMap doesn't
// support putAll remove after it's fixed in MyFaces
Iterator it = map.entrySet().iterator();
@@ -459,12 +561,13 @@ public class FlowPhaseListener implements PhaseListener {
private void sendRedirect(String url, JsfExternalContext context) {
try {
- url = context.getFacesContext().getExternalContext().encodeResourceURL(url);
+ url = context.getFacesContext().getExternalContext()
+ .encodeResourceURL(url);
context.getFacesContext().getExternalContext().redirect(url);
context.getFacesContext().responseComplete();
- }
- catch (IOException e) {
- throw new IllegalArgumentException("Could not send redirect to " + url);
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Could not send redirect to "
+ + url);
}
}
@@ -481,7 +584,8 @@ public class FlowPhaseListener implements PhaseListener {
}
/**
- * Standard default view id resolver which uses the web flow view name as the jsf view id
+ * 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) {