Core ADF 11: Abandoning a Bounded Taskflow with Stand-Alone Pages
By Steven Davelaar on Apr 02, 2009
In the first post of this Core ADF 11 series, I discussed various options for designing your page structure. This post applies when you choose to use bounded task flows with stand-alone .jspx pages. If you make these bounded task flows available through a menu where each menu item calls a bounded task flow, you will hit the "cannot abandon bounded taskflow" issue: when you start the home page of your application (located in the unbounded taskflow), and then click on a menu item for the first time, you start the bounded taskflow associated with the menu item. Now, the menu is still visible, and the end user might decide to click on another menu item before he correctly finished the bounded task flow. In other words, he wants to abandon the task flow he started through the first menu item. However, when the user clicks on another menu item, nothing will happen! This is because the control flow rules that start each bounded taskflow are defined in the unbounded taskflow (in adfc-config.xml). They are not available in the context of the first bounded taskflow started by the user.
The "poor man's" solution to this problem is hiding or disabling all menu items and other global actions that will launch a bounded task flow (like "Help" and "Preferences") until the user executes a bounded task flow return action and returns to a page in the unbounded task flow. Not a very user friendly solution....
Another technique is to use a bounded taskflow template for all your bounded task flows. In the template you duplicate all control flow rules and all task-flow call activities that are defined in the unbounded task flow. While this might seem to work at first sight, this is not a viable option. What happens with this approach is that each time you click on another menu item, a new bounded sub task flow is started in the context of the current task flow: in other words, you keep on nesting bounded task flows. This is highly undesirable and will cause problems sooner or later with memory resources and/or transaction management. Since the task flows are nested, taskflow resources are never released, and transactions are never rolled back. Don't do this!
The only proper solution is to execute a return activity to quit the current task flow before starting the new bounded task flow. In this post, I will describe how you can implement this in a generic way which hides the complexity for your developers. The outline of the solution is as follows:
- The action property of all menu items as defined in the XML menu Model is prefixed with "abandon:" (This is all what your developers need to know)
- A custom navigation handler is used that checks whether the navigation outcome starts with "abandon:". If so, and the current task flow context is a bounded task flow, then the navigation outcome as specified after the "abandon:" prefix is stored in a request attribute named "callMenuItem", and the super navigation handler is called with the CallMenuItem outcome. If we are not in the context of a bound taskflow, we call the super navigation handler directly with the navigation outcome as specified after the "abandon:" prefix.
- The CallMenuItem outcome navigates to a generic task flow return activity named "CallMenuItem" which in turn navigates to the CallMenuItem activity in the unbounded taskflow.
- The CallMenuItem activity in the unbounded taskflow is a "dynamic router" a Java class with a getOutcome method that returns the value of the EL expression passed in as argument. In our case, the router returns the value of the currentMenuItem request attribute, which causes navigation to the end target: either some page in the unbounded task flow, or another bounded task flow call activity in the unbounded task flow.
Here are the detailed steps to implement this:
1. Prefix the value of the action property in all your menu items in XML MenuModel with "abandon:"
2. Create a custom navigation handler with the following code:
public class MyNavigationHandler
public static final String ABANDON_PREFIX = "abandon:";
private static final String CURRENT_MENU_ITEM = "currentMenuItem";
private static final String CALL_MENU_ITEM = "CallMenuItem";
public MyNavigationHandler(NavigationHandler navHandler)
public void handleNavigation(FacesContext facesContext, String action,
if (outcome != null && outcome.startsWith(ABANDON_PREFIX))
abandonTaskFlowIfNeeded(facesContext, action, outcome);
super.handleNavigation(facesContext, action, outcome);
public void abandonTaskFlowIfNeeded(FacesContext facesContext,
String action, String outcome)
String strippedOutcome =
TaskFlowContext tfc =
if (tfc.getTaskFlowId() == null)
// we are not in a bounded task flow, do normal navigation
handleNavigation(facesContext, action, strippedOutcome);
String newOutcome = strippedOutcome;
Map requestMap =
handleNavigation(facesContext, null, CALL_MENU_ITEM);
3. Register your custom navigation handler in the faces-config.xml. Go to the Overview tab, click on Application, and set your class name in the navigation handler property.
4. Create a bounded taskflow template used by all your bounded task flows, and add the following code to it:
When using declarative transaction management, you can add that a rollback should be performed as part of the return activity.
5. Create a Java class named DynamicRouter with the following code:
public class DynamicRouter implements Serializable
public String getOutcome(String outcome)
6. Add the following code to adfc-config.xml (the unbounded task flow):
That's it. You can download a sample workspace that illustrates the solution here. In a later post, we will discuss how to add an alert to warn the end user about unsaved changes when he abandons a task flow.