You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by sk...@apache.org on 2008/10/15 13:39:50 UTC
svn commit: r704866 - in
/myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow:
FlowHandler.java FlowNavigationHandler.java FlowNavigator.java
FlowViewHandler.java
Author: skitching
Date: Wed Oct 15 04:39:49 2008
New Revision: 704866
URL: http://svn.apache.org/viewvc?rev=704866&view=rev
Log:
Always use redirect at flow entry and exit, so browser URL reflects new path and new conversationContext value.
Add initial support for modal windows.
Removed:
myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowNavigator.java
Modified:
myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowHandler.java
myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowNavigationHandler.java
myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowViewHandler.java
Modified: myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowHandler.java
URL: http://svn.apache.org/viewvc/myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowHandler.java?rev=704866&r1=704865&r2=704866&view=diff
==============================================================================
--- myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowHandler.java (original)
+++ myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowHandler.java Wed Oct 15 04:39:49 2008
@@ -18,21 +18,26 @@
*/
package org.apache.myfaces.orchestra.flow;
+import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import javax.faces.FacesException;
+import javax.faces.application.ViewHandler;
import javax.faces.component.UIViewRoot;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
+import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.myfaces.orchestra.conversation.ConversationContext;
import org.apache.myfaces.orchestra.conversation.ConversationManager;
+import org.apache.myfaces.orchestra.flow.components.ModalFlow;
import org.apache.myfaces.orchestra.flow.config.FlowAccept;
import org.apache.myfaces.orchestra.flow.config.FlowCall;
import org.apache.myfaces.orchestra.flow.config.FlowConfig;
@@ -52,6 +57,7 @@
public class FlowHandler
{
private static final Log log = LogFactory.getLog(FlowHandler.class);
+ private static final String VIEW_TO_RESTORE_KEY = FlowHandler.class.getName() + ":viewToRestore";
/**
* Return the FlowCall object associated with the current conversation
@@ -110,6 +116,74 @@
return fa;
}
+
+ /**
+ *
+ * @param facesContext
+ * @param viewId
+ * @param flowInfo
+ */
+ private static void redirectTo(FacesContext facesContext, String viewId, FlowInfo flowInfo)
+ {
+ ExternalContext ec = facesContext.getExternalContext();
+ ViewHandler viewHandler = facesContext.getApplication().getViewHandler();
+ String url = viewHandler.getActionURL(facesContext, viewId);
+ url = ec.encodeActionURL(url);
+
+ if (!flowInfo.isModalFlow())
+ {
+ try
+ {
+ ec.redirect(url);
+ return;
+ }
+ catch(IOException ioe)
+ {
+ throw new FacesException("Unable to redirect to " + viewId, ioe);
+ }
+ }
+
+ // Ok, we are closing a modal flow. We therefore need to render some javascript that
+ // will close the modal window and force the parent window to fetch the appropriate url.
+ //
+ // Note that we cannot allow navigation to occur then send the redirect in the createView
+ // method because we would need a valid outcome to return here; without that the NavigationHandler
+ // just does a "null" navigation, ie returns the current UIViewRoot and never calls the ViewHandler
+ // object.
+ //
+ // And we cannot re-render the popup window because that would need the child context to be
+ // active in order to work in all cases (though re-rendering the popup window after the postback
+ // that closes it might itself cause problems eg reuse of committed persistent objects). But
+ // if we need the child context in order to render the child view, then when do we destroy it?
+ //
+ // So here we do a truly ugly hack: send some raw html to the response stream right here.
+ // The major problem here is that this assumes an HTML renderkit. Well, actually the below
+ // is also valid XHTML too..
+ StringBuffer buf = new StringBuffer();
+ buf.append("<html><head><script>//<![CDATA[\n");
+ buf.append("var url='"+ url + "';\n");
+ buf.append(flowInfo.getOnExitScript());
+ buf.append("\n//]]>\n");
+ buf.append("</script></head></html>");
+ try
+ {
+ // Here we cannot use facesContext.getResponseWriter(). That returns null
+ // until after ViewHandler.renderView has been invoked. So here we use
+ // the raw response stream. Hey, all this html is so hacky it seems no
+ // worse to access the response stream here.
+ //
+ // Of course this won't work with portlets, but the idea of a portlet popping
+ // up a modal window to invoke a flow is rather unlikely anyway..
+ HttpServletResponse rsp = (HttpServletResponse) ec.getResponse();
+ rsp.getWriter().write(buf.toString());
+ }
+ catch(IOException ioe)
+ {
+ throw new FacesException(ioe);
+ }
+ facesContext.responseComplete();
+ }
+
/**
* Do actions that do not depend on properties of the called flow (if any).
* <p>
@@ -124,8 +198,12 @@
* This is expected to be called from the FlowNavigationHandler after some
* postback has caused a navigation to occur. Note that in that case we do
* not yet know what viewId we are going <i>to</i>.
+ *
+ * @return a navigation outcome string to pass to the NavigationHandler. Note
+ * however that if this method has sent a redirect then the return value will
+ * simply be ignored.
*/
- static void processPreNav(FlowNavigator nav, FacesContext facesContext, String oldViewId, String outcome)
+ static String processPreNav(FacesContext facesContext, String oldViewId, String outcome)
{
log.debug("processCall: [" + String.valueOf(oldViewId) + "] outcome: " + outcome);
@@ -152,8 +230,12 @@
// Mark flowInfo as cancelled
flowInfo.cancel();
- nav.returnToView(callerViewRoot, callerViewId);
- return;
+ // Store viewRoot from flowInfo into parent context
+ parent.setAttribute(VIEW_TO_RESTORE_KEY, callerViewRoot);
+
+ // And redirect. This sets responseComplete, so navigation is skipped.
+ redirectTo(facesContext, callerViewId, flowInfo);
+ return outcome;
}
if (outcome.equals(fa.getCommitWhen()))
@@ -178,27 +260,50 @@
cm.activateConversationContext(parent);
cm.removeAndInvalidateConversationContext(ctx);
- // Now restore the view tree of the caller.
- nav.returnToView(callerViewRoot, callerViewId);
+ // Now restore the view tree of the caller.
+ facesContext.setViewRoot(callerViewRoot);
// And with the original context active, write parameters back then
// invoke any custom actions the caller wants to run.
//
// Note that because the original view has already been restored,
- // the caller can safely access stuff in its view.
+ // the caller can safely access stuff in its view.
+ //
+ // Note that the ViewController methods are not invoked even though
+ // the backing bean for the view is. This is not a problem really, just
+ // something to be aware of.
flowInfo.getFlowCall().writeAcceptParams(facesContext, map);
FlowOnCommit onCommit = flowInfo.getFlowCall().getOnCommit();
- if (onCommit != null)
+ if (onCommit == null)
+ {
+ // Store viewRoot from flowInfo into parent context
+ parent.setAttribute(VIEW_TO_RESTORE_KEY, callerViewRoot);
+ redirectTo(facesContext, callerViewId, flowInfo);
+ }
+ else
{
Object actionOutcome = onCommit.execute(facesContext);
- if (actionOutcome != null)
+ if (actionOutcome == null)
+ {
+ parent.setAttribute(VIEW_TO_RESTORE_KEY, callerViewRoot);
+ redirectTo(facesContext, callerViewId, flowInfo);
+ }
+ else
{
- nav.goToOutcome(callerViewRoot, callerViewId, actionOutcome.toString());
- return;
+ // Compute the viewId that this outcome maps to, then redirect to it. Note that
+ // we have restored the caller's view so navigation rules for the caller's view
+ // are used.
+ //
+ // Tricky: any attempt to compute the new viewId will trigger createView. We therefore
+ // need to set a flag telling the ViewHandler to just do a redirect in createView. Ugly,
+ // and might have problems when other custom ViewHandlers are also configured, but JSF
+ // gives us no option here.
+ facesContext.getExternalContext().getRequestMap().put("isRedirectOnReturn", Boolean.TRUE);
+ return actionOutcome.toString();
}
}
- return;
+ return outcome;
}
}
@@ -213,16 +318,38 @@
// Build a FlowInfo object for the called flow to use.
flowInfo = new FlowInfo(oldViewId, facesContext.getViewRoot(), flowCall, data);
- // create child context
+ // create child context and activate it
ConversationManager cm = ConversationManager.getInstance(true);
ConversationContext parent = cm.getCurrentConversationContext();
ConversationContext child = cm.createConversationContext(parent);
+ cm.activateConversationContext(child);
+ // Handle triggering of modalflow if any
+ ModalFlow f = ModalFlow.getForOutcome(facesContext, outcome);
+ if (f != null)
+ {
+ // The current view contains a ModalFlow component that declares that it will handle
+ // the new flow by opening the entry page in a new frame or window. We mark that component
+ // as "active" so that the component renders javascript to open that window. We also mark
+ // the FlowInfo as modal; this is checked in FlowViewHandler.createView.
+ //
+ // TODO: also handle <redirect/> rules, which instead invoke the
+ // ExternalContext.redirect method rather than ViewHandler.createView?
+ flowInfo.setModalFlow(true);
+ flowInfo.setOnExitScript(f.getOnExit());
+ f.setActive(true);
+ }
+
+ // Store the flowInfo in the child context
child.setAttribute("flowInfo", flowInfo);
- cm.activateConversationContext(child);
- nav.callFlow(outcome, flowInfo);
+ // And let normal navigation occur. Hopefully the new page will be the entry-point
+ // for a flow, in which case we finish setting up the FlowInfo object then redirect
+ // to the flow entry point; see FlowViewHander.createView. If something goes wrong,
+ // then FlowViewHandler.createView will clean up.
}
+
+ return outcome;
}
/**
@@ -275,6 +402,11 @@
return false;
}
+ private static class ViewHolder
+ {
+ UIViewRoot root;
+ }
+
/**
* Handle case where we have just navigated to a new page, and the page that caused the
* navigation did a flow-call.
@@ -287,7 +419,8 @@
* This is expected to be called from FlowViewHandler when a new view is being created
* (due either to a GET or POST from the user, or an internal forward due to navigation.
*/
- static boolean doNewFlowEntry(FacesContext facesContext, FlowInfo flowInfo, String newViewId)
+ static boolean doNewFlowEntry(FacesContext facesContext, ViewHandler viewHandler,
+ ViewHolder viewHolder, FlowInfo flowInfo, String newViewId)
{
if (flowInfo == null)
{
@@ -307,8 +440,12 @@
if (flowAccept == null)
{
// TODO: discard all flow stacks here for safety?
- log.debug("isNewFlowEntry: Error: invocation of flow without callee declaration");
- throw new OrchestraException("Invocation of flow without callee declaration");
+ StringBuffer msg = new StringBuffer();
+ msg.append("Invocation of flow without callee declaration. ");
+ msg.append("Calling view is " + flowInfo.getCallerViewId() + ". ");
+ msg.append("Called view is " + newViewId + ".");
+ log.debug("isNewFlowEntry: Error: " + msg.toString());
+ throw new OrchestraException(msg.toString());
}
log.debug("isNewFlowEntry: new flow detected.");
@@ -323,35 +460,179 @@
Map argsIn = flowInfo.getArgsIn();
flowAccept.writeAcceptParams(facesContext, argsIn);
- return true;
+ ExternalContext externalContext = facesContext.getExternalContext();
+
+ // The "postback" part of the current request must have started a flow call.
+ if (flowInfo.isModalFlow())
+ {
+ // First compute the URL for the *new* viewId (the flow entry page) while the child
+ // context is active so the conversationContext query param gets set right.
+ String newViewUrl = viewHandler.getActionURL(facesContext, newViewId);
+ newViewUrl = externalContext.encodeActionURL(newViewUrl);
+
+ // Verify that this really is the entry point for a new flow.
+ // Do import On error, reset
+ // context to parent then throw exception.
+ // And copy input parameters for the
+ // Now we want to re-render the *old* view, so we need to activate the parent
+ // context for the remainder of this request. Note that the ConversationManager
+ // must exist as we have a FlowInfo object.
+ ConversationManager cm = ConversationManager.getInstance();
+ ConversationContext child = cm.getCurrentConversationContext();
+ ConversationContext parent = child.getParent();
+ cm.activateConversationContext(parent);
+
+ // Set some variables that allows components in the page to
+ // access the URL that the new modal window should fetch.
+ setNewViewInfo(newViewId, newViewUrl);
+
+ // And just return the current view rather than creating a new one.
+ viewHolder.root = facesContext.getViewRoot();
+ return true;
+ }
+
+ // Force a redirect to the entry page for the flow. Note that the currently activated context
+ // is the child one, so getActionURL encodes the conversationContext value appropriately.
+ String newViewUrl = viewHandler.getActionURL(facesContext, newViewId);
+ newViewUrl = externalContext.encodeActionURL(newViewUrl);
+ try
+ {
+ facesContext.getExternalContext().redirect(newViewUrl);
+ return true;
+ }
+ catch(IOException ioe)
+ {
+ throw new FacesException("Unable to enter flow", ioe);
+ }
+
+ }
+
+ private static Object rmvContextAttribute(String id)
+ {
+ ConversationManager cm = ConversationManager.getInstance(false);
+ if (cm == null)
+ {
+ return null;
+ }
+
+ ConversationContext ctx = cm.getCurrentConversationContext();
+ if (ctx == null)
+ {
+ return null;
+ }
+
+ Object o = ctx.removeAttribute(id);
+ return o;
+ }
+
+ private static void setNewViewInfo(String viewId, String viewUrl)
+ {
+ FacesContext fc = FacesContext.getCurrentInstance();
+ Map reqMap = fc.getExternalContext().getRequestMap();
+ reqMap.put("orchestraFlowId", viewId);
+ reqMap.put("orchestraFlowUrl", viewUrl);
}
/**
- * Invoked to handle actions that require info about the called page, but not the caller.
+ * Special createView handling for Orchestra Flows.
+ * <p>
+ * When this request was a postback that started a flowcall:
+ * <ul>
+ * <li>validate that this new view is a suitable flow entry point
+ * <li>import passed parameters into child context
+ * <li>for non-modal call: send a redirect to this view
+ * <li>for modal call: rerender the *old* view, which should then
+ * trigger a GET to the new view in a new window or frame.
+ * </ul>
+ * <p>
+ * When this request was a postback that started a flowreturn:
+ * <ul>
+ * <li>Normally the view to return to is known so this code is not executed.
+ * <li>When the view to return to has a "return handler" that provides
+ * a nav-outcome then this code is executed; just redirect to the desired view.
+ * </ul>
+ * <p>
+ * Throws OrchestraException if there is a flow error, eg if the view
+ * specified is the entry point for a flow but the previous view did
+ * not do a FlowCall.
+ *
+ * @return a UIViewRoot to use when rendering this request. If null is returned
+ * then the standard ViewHandler.createView method is used to create one. Note
+ * however that if this method has sent a redirect then the return value will
+ * simply be ignored.
*/
- static void processPreCreateView(FacesContext facesContext, String newViewId)
+ static UIViewRoot processPreCreateView(FacesContext facesContext, ViewHandler viewHandler, String newViewId)
{
log.debug("processAccept: [" + newViewId + "]");
+
FlowInfo flowInfo = getFlowInfo();
+
+ boolean isRedirectOnReturn = facesContext.getExternalContext().getRequestMap()
+ .containsKey("redirectOnReturn");
+ if (isRedirectOnReturn)
+ {
+ // The current request contained a postback that triggered a flow-return. The caller also had a
+ // return-handler that returned an outcome to navigate to, ie the caller wants an immediate
+ // bounce to somewhere else rather than redisplaying itself. We couldn't compute the url to redirect
+ // to earlier due to the brain-dead NavigationHandler api, so have to handle it here.
+ //
+ // Force a redirect to the entry page for the flow. Note that the currently activated context
+ // is the child one, so getActionURL encodes the conversationContext value appropriately.
+ ExternalContext ec = facesContext.getExternalContext();
+ String newViewUrl = viewHandler.getActionURL(facesContext, newViewId);
+ newViewUrl = ec.encodeActionURL(newViewUrl);
+ try
+ {
+ ec.redirect(newViewUrl);
+ return null;
+ }
+ catch(IOException ioe)
+ {
+ throw new FacesException("Unable to enter flow", ioe);
+ }
+ }
- if (doNewFlowEntry(facesContext, flowInfo, newViewId))
+ UIViewRoot viewToRestore = (UIViewRoot) rmvContextAttribute(VIEW_TO_RESTORE_KEY);
+ if (viewToRestore != null)
+ {
+ // The previous request triggered a flowreturn, ie this request is re-rendering the calling view.
+ // We therefore may have a cached viewRoot object that holds the view saved when the call was made..
+ //
+ // TODO: possibly check that viewToRestore.viewId == newViewId. This should always be true, as we
+ // only set that attribute immediately before sending a redirect to the matching id.
+ return viewToRestore;
+ }
+
+ ViewHolder viewHolder = new ViewHolder();
+ if (doNewFlowEntry(facesContext, viewHandler, viewHolder, flowInfo, newViewId))
{
- return;
+ // We have just entered a new flow
+ return viewHolder.root;
}
if (isAbnormalFlowExit(flowInfo, newViewId))
{
- return;
+ // User has leapt out of an existing flow
+ return null;
}
+ // Below here are just sanity checks, looking for weird situations..
+
FlowAccept flowAccept = getFlowAccept(flowInfo, newViewId);
boolean isFlowEntryPoint = (flowAccept != null);
boolean isInFlow = (flowInfo != null);
if (isFlowEntryPoint && !isInFlow)
{
- log.debug("processAccept: Error: invocation of flow without caller declaration");
- throw new OrchestraException("Invocation of flow without caller declaration");
+ // The new page is a flow entry point, but the earlier page did a normal navigation,
+ // without any flow-call. Therefore we do not have a child context set up, and no
+ // FlowInfo object. Unfortunately we therefore do not know what the previous
+ // page was...
+ StringBuffer msg = new StringBuffer();
+ msg.append("Invocation of flow without caller declaration. ");
+ msg.append("Called view is " + newViewId + ".");
+ log.debug("processAccept: Error: " + msg.toString());
+ throw new OrchestraException(msg.toString());
}
@@ -379,6 +660,9 @@
throw new OrchestraException("Child flow not properly terminated.");
}
}
+
+ // all ok, let view be created in normal manner
+ return null;
}
/**
@@ -432,7 +716,7 @@
* Return the FlowInfo object stored in the current conversation
* context (if any).
*/
- private static FlowInfo getFlowInfo()
+ static FlowInfo getFlowInfo()
{
ConversationManager cm = ConversationManager.getInstance(false);
if (cm == null)
Modified: myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowNavigationHandler.java
URL: http://svn.apache.org/viewvc/myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowNavigationHandler.java?rev=704866&r1=704865&r2=704866&view=diff
==============================================================================
--- myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowNavigationHandler.java (original)
+++ myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowNavigationHandler.java Wed Oct 15 04:39:49 2008
@@ -40,19 +40,8 @@
}
/**
- * Special handleNavigation processing for Orchestra Flows.
- * <p>
- * In most cases this method simply delegates to the wrapped instance to perform a
- * normal navigation. However there are the following exceptions:
- * <ul>
- * <li>When a flow is active, and the outcome matches the "commit" outcome for the flow
- * <li>When a flow is active, and the outcome matches the "cancel" outcome for the flow
- * </ul>
- * Note that in these special cases, the underlying navigation handler is never called;
- * we leap directly back to the flow that called the one that just ended.
- * <p>
- * There are also some special logic that needs to be triggered when the navigation
- * outcome matches a "flowCall" value for the current view.
+ * Ensure that the FlowHandler gets the chance to run after the navigation outcome has been
+ * determined.
*/
public void handleNavigation(FacesContext facesContext, String fromAction, String outcome)
{
@@ -60,21 +49,25 @@
// (which are attached to a FacesContext)? I guess if this is important then it can also
// be attached to the FlowInfo object...
//
- // TODO: what about RedirectTracker's functionality to save and restore messages,
- // request-scoped beans etc? Presumably that will not run. So are there any cases where
- // redirectTracker and "commit/cancel" have unexpected behaviour? Probably yes if a
- // page with request-scoped beans does a redirect to a flow start. The RedirectTracker
- // will save request-scoped beans and messages when leaving the caller page. Then it
- // will try to restore them into the child context (yecch). On return to the caller,
- // if it runs *earlier* then it will try to copy stuff from the flow exit page back to
- // the caller; if it runs later then it never runs at all.
+ // Note that the Tomahawk RedirectTracker is no problem here. It triggers only when a JSF
+ // navigation-rule specifies the <redirect/> tag, and we never expect that to
+ // occur for rules that navigate to the start of a new flow. So RedirectTracker
+ // and Flow effectively have no overlap and will not interfere with each other.
UIViewRoot currViewRoot = facesContext.getViewRoot();
String oldViewId = currViewRoot.getViewId();
log.debug("handleNavigation: view=" + oldViewId + ",outcome=" + outcome);
- FlowNavigator nav = new FlowNavigator(facesContext, delegate, fromAction, outcome);
- FlowHandler.processPreNav(nav, facesContext, oldViewId, outcome);
- nav.doNavigation();
+ outcome = FlowHandler.processPreNav(facesContext, oldViewId, outcome);
+
+ if (!facesContext.getResponseComplete())
+ {
+ delegate.handleNavigation(facesContext, fromAction, outcome);
+ }
+
+ // TODO: handle isRedirectOnReturn here rather than in FlowViewHandler.createView.
+ // After handleNavigation has returned, we have the new UIViewRoot object so just need
+ // to invoke sendRedirect here. This would mean that we run "flow entry" code in the
+ // same request as the original, rather than after the redirect. But that seems ok..
}
}
Modified: myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowViewHandler.java
URL: http://svn.apache.org/viewvc/myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowViewHandler.java?rev=704866&r1=704865&r2=704866&view=diff
==============================================================================
--- myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowViewHandler.java (original)
+++ myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowViewHandler.java Wed Oct 15 04:39:49 2008
@@ -21,7 +21,6 @@
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Locale;
-import java.util.Map;
import javax.faces.FacesException;
import javax.faces.application.ViewHandler;
@@ -175,69 +174,43 @@
/**
* Special createView handling for Orchestra Flows.
* <p>
- * This method always delegates to the wrapped instance to create the view
- * that the user asked for (unless we report a flow error), but we first do
- * flow-related things like:
+ * When this request was a postback that started a flowcall:
* <ul>
- * <li>import parameters from calling to called context if this is the
- * first page of a new flow
- * <li>discard some conversation contexts if this view is not in the
- * currently active flow
- * </li>
+ * <li>validate that this new view is a suitable flow entry point
+ * <li>import passed parameters into child context
+ * <li>for non-modal call: send a redirect to this view
+ * <li>for modal call: rerender the *old* view, which should then
+ * trigger a GET to the new view in a new window or frame.
+ * </ul>
+ * <p>
+ * When this request was a postback that started a flowreturn:
+ * <ul>
+ * <li>Normally the view to return to is known so this code is not executed.
+ * <li>When the view to return to has a "return handler" that provides
+ * a nav-outcome then this code is executed; just redirect to the desired view.
+ * </ul>
* <p>
* Throws OrchestraException if there is a flow error, eg if the view
* specified is the entry point for a flow but the previous view did
* not do a FlowCall.
*/
- public UIViewRoot createView(FacesContext context, String newViewId)
+ public UIViewRoot createView(FacesContext facesContext, String newViewId)
{
- if (isCaptureNavigation())
+ UIViewRoot viewRoot = FlowHandler.processPreCreateView(facesContext, delegate, newViewId);
+ if (viewRoot == null)
{
- // Cache the target view in the request, but stay on current view.
- // See documentation for method captureNavigation for more details.
- String newViewUrl = getActionURL(context, newViewId);
- setNewViewInfo(newViewId, newViewUrl);
- return context.getViewRoot();
+ if (facesContext.getResponseComplete())
+ {
+ // We will never use the viewRoot returned from this method because the response
+ // is already complete. However this method is not allowed to return null, so
+ // here we return a dummy root object.
+ viewRoot = new UIViewRoot();
+ }
+ else
+ {
+ viewRoot = delegate.createView(facesContext, newViewId);
+ }
}
- else
- {
- FlowHandler.processPreCreateView(context, newViewId);
- return delegate.createView(context, newViewId);
- }
- }
-
- /**
- * Enable or disable "navigation capture" for the current request.
- * <p>
- * When enabled, method createView will not load a new view, and will simply stay on the current
- * view. However the information about where it _would_ have navigated to is cached in the request.
- * <p>
- * This is intended to be used by "modal dialog" support, so that a page which triggers a new flow
- * can be re-rendered unaltered and instead (somehow) the new view can be loaded using a separate
- * request from the browser (in a new window or frame).
- * <p>
- * This method must only be called when the current request is a postback, ie there is a
- * "current view" to stay on.
- */
- public static void captureNavigation(boolean state)
- {
- FacesContext fc = FacesContext.getCurrentInstance();
- Map reqMap = fc.getExternalContext().getRequestMap();
- reqMap.put("captureNavigation", Boolean.valueOf(state));
- }
-
- private static boolean isCaptureNavigation()
- {
- FacesContext fc = FacesContext.getCurrentInstance();
- Map reqMap = fc.getExternalContext().getRequestMap();
- return Boolean.TRUE.equals(reqMap.get("captureNavigation"));
- }
-
- private static void setNewViewInfo(String viewId, String viewUrl)
- {
- FacesContext fc = FacesContext.getCurrentInstance();
- Map reqMap = fc.getExternalContext().getRequestMap();
- reqMap.put("orchestraFlowId", viewId);
- reqMap.put("orchestraFlowUrl", viewUrl);
+ return viewRoot;
}
}