Core ADF 11: Abandoning a Bounded Taskflow with Stand-Alone Pages

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
  extends NavigationHandlerImpl
{
  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)
  {
    super(navHandler);
  }


  public void handleNavigation(FacesContext facesContext, String action,
                               String outcome)
  {
    if (outcome != null && outcome.startsWith(ABANDON_PREFIX))
    {
      abandonTaskFlowIfNeeded(facesContext, action, outcome);
    }
    else
    {
      super.handleNavigation(facesContext, action, outcome);
    }
  }


  public void abandonTaskFlowIfNeeded(FacesContext facesContext,
                                      String action, String outcome)
  {
    String strippedOutcome =
      outcome.substring(MyNavigationHandler.ABANDON_PREFIX.length());
    TaskFlowContext tfc =
      ControllerContext.getInstance().getCurrentViewPort().getTaskFlowContext();
    if (tfc.getTaskFlowId() == null)
    {
      // we are not in a bounded task flow, do normal navigation
      handleNavigation(facesContext, action, strippedOutcome);
    }
    else
    {
      String newOutcome = strippedOutcome;
      Map requestMap = 
          FacesContext.getCurrentInstance().getExternalContext().getRequestMap();
      requestMap.put(CURRENT_MENU_ITEM, newOutcome);
      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:

<task-flow-return id="CallMenuItem">
  <outcome>
    <name>CallMenuItem</name>
  </outcome>
</task-flow-return>
<control-flow-rule>
  <from-activity-id>*</from-activity-id>
  <control-flow-case>
    <from-outcome>CallMenuItem</from-outcome>
    <to-activity-id>CallMenuItem</to-activity-id>
  </control-flow-case>
</control-flow-rule>
</task-flow-template>

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)
  {
    return outcome;
  }
}

6. Add the following code to adfc-config.xml (the unbounded task flow):

<method-call id="CallMenuItem">
  <method>#{dynamicRouter.getOutcome}</method>
  <parameter>
    <class>java.lang.String</class>
    <value>#{requestScope.currentMenuItem}</value>
  </parameter>
  <outcome>
    <to-string/>
  </outcome>
</method-call>


<control-flow-rule>
  <from-activity-id>*</from-activity-id>
  <control-flow-case>
    <from-outcome>CallMenuItem</from-outcome>
    <to-activity-id>CallMenuItem</to-activity-id>
  </control-flow-case>
  ...
</control-flow-rule>  


  <managed-bean>
    <managed-bean-name>dynamicRouter</managed-bean-name>
    <managed-bean-class>view.DynamicRouter</managed-bean-class>
    <managed-bean-scope>request</managed-bean-scope>
  </managed-bean>

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.

Comments:

Hi Steven, Very nice post, but I registered I little bug in this issue. When I made my menu with this custom navigation handler everything is just fine, but the problem appears when you try to navigate from one bounded task flow to another bounded task flow, which is stated as a modal dialog. Intead of just one dialog, it shows 2 dialog boxes. When I remove the custom navigation handler it shows just one dialog how it is supposed to do. So, the problem probably is somewhere in this navigation handler. Any ideas why this happens?! Thanks in advance. Dimitar Saykov

Posted by Dimitar on September 01, 2009 at 10:36 PM PDT #

Dimitar, How do you launch the modal dialog taskflow? From the menu, or from a button, can you post the code snippet you use to launch the dialog? Steven.

Posted by steven.davelaar on September 03, 2009 at 04:46 PM PDT #

Hi, had the same problem. The issue is with calling the super NavigationHandler. If you are calling the popup the original NavigationHandler handleNavigation Method is called twice. First directly from the new implementation, the sceond time as delegate of the new implementation. I solved the problem this way: private NavigationHandler myDelegate; public MyNavigationHandler(NavigationHandler navHandler) { super(navHandler); myDelegate= navHandler; } public void handleNavigation(FacesContext facesContext, String action, String outcome) { if (outcome != null && outcome.startsWith(ABANDON_PREFIX)) { abandonTaskFlowIfNeeded(facesContext, action, outcome); } else { myDelegate.handleNavigation(facesContext, action, outcome); } }

Posted by Stefan Forner on March 17, 2010 at 02:52 AM PDT #

We recently found the cause of this behavior. The JhsNavigationHandlerImpl class was extending the wrong class, it should extend org.apache.myfaces.trinidadinternal.application.NavigationHandlerImpl Steven Davelaar, JHeadstart team.

Posted by steven.davelaar on March 17, 2010 at 09:22 PM PDT #

This is interesting but there is another way to do this that we used in our application and that is to define the template for all bounded taskflows containing the menu item actions but make them all "return" actions with an outcome equal to the name of the action directing you to the next task flow. Then define the same list of actions within the unbounded task flow that take you back into another bounded task flow. This is more visual as you can follow the flow in the template and it doesn't require a naming convention on the menu items. The only disadvantage is that when you add a new menu item you must add the flow rule in two places. Add the return action to the template and forward action to the unbounded task flow. Fairly easy to do though. Then make all of your bounded task flows inherit the template. This also has the added advantage of unwinding nested calls to task flow when you select a menu item. If all of the bounded task flows inherit from the template and one task flow calls another task flow then when you select a menu item it will return to the previous task flow which will return to the previous task flow and then back to the root unbounded task flow before going forward. It works nicely.

Posted by Don on March 26, 2010 at 06:12 AM PDT #

Since we use this technique with JHeadstart to generate applications, it was easier to not duplicate the actions in the taskflow template. The visual aspect is less important too, since we generate all the taskflows. But your solution is as valid, and it is good people can choose based on their preferences. Your reply did make us realize that we need to unwind any nested taskflow calls in the custom navigation handler. We will add that code. Thanks for your reply! Steven.

Posted by steven.davelaar on July 06, 2010 at 04:59 PM PDT #

Post a Comment:
  • HTML Syntax: NOT allowed
About

Java EE Consultants - JHeadstart, ADF, JSF

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today