25 Dec 2011

Task Flow Exception Handler. You must have it.

Introduction

Defining Exception handlers in your task flows is not only a good practice of ADF development.
Ignoring this practice can get you in some big problems, you will get unpredictable and difficult for understanding behavior of the system like Null Pointer Exceptions in trusted places or system's hanging.
Some time ago I faced a "performance" problem of ADF application. About a hundred of users (it's not too much load for that environment) were working with the system and it "hanged" every 20 minutes. A lot of performance tunings didn't help. There was a bug. One important Task Flow didn't have an exception handler.

Experiments
Let's say we have very simple bounded train task flow:
The task flow is executed as a region within some jspx page and it doesn't have an exception handler. NextView page fragment has the following source:

 <af:panelFormLayout>
  <af:group id="g1">
   <af:train id="pt_t2"
    value="#{controllerContext.currentViewPort.taskFlowContext.trainModel}"/>
   <af:commandButton text="Commit" id="cb1" action="*Commit"/>
   <af:commandToolbarButton text="Back" id="pt_cb1"
    action="#{controllerContext.currentViewPort.taskFlowContext.trainModel.getPrevious}"
    disabled="#{empty controllerContext.currentViewPort.taskFlowContext.trainModel.previous}"
    rendered="#{! empty controllerContext.currentViewPort.taskFlowContext.trainModel.previous}"
    immediate="true"/>

   <af:commandToolbarButton text="Forward" id="pt_cb2"
    action="#{pageFlowScope.WizzardFlowBean.nextButtonAction}"
    disabled="#{empty controllerContext.currentViewPort.taskFlowContext.trainModel.next}"/>
  </af:group>
 </af:panelFormLayout>

And it looks like this:

 
Managed bean method for BadCall activity has the following code:

    public void badCall() {
        int i = 1/0;
    }

In this example the situation is not very dangerous. We will just get the unsupported exception window like this:



But the system is still stable and is working correctly. We can click Back button, we can navigate using af:train at the top of the page. Andrejus Baranovskis explained in his blog how to define exception handler for the task flow to make the exception be caught and rendered correctly.

In the next experiment let's call our task flow from another one. So, let's make it inner.


In this scenario the same Arithmetic exception will crash the system. If we try to click Back button after the exception window, we will get Null Pointer Exception. Why???

The reason is in the controller's exception handling mechanism. In case of an exception during navigation within task flow it tries to find and execute an Exception Handler of the current task flow. If the current task flow does not specify an exception handling activity then the task flow will be popped from the task flow stack and control passed to the calling task flow's exception handler.  This process is continued until an exception handler is located or until all task flow's have been checked. So, in our example, when we don't have any exception handlers at all, our task flow (emp-flow-definition) is just popped out from the calling stack, control is passed to the calling flow, and the exception is thrown. That's why EL expression for Back button's action #{controllerContext.currentViewPort.taskFlowContext.trainModel.getPrevious}" will throw NPE. Current task flow at that moment is calling task flow, but we see the inner task flow on the screen. And off course calling task flow doesn't have any train model.

The situation is even worse. If we try to navigate using af:train, the system will hang!!! We will get a stuck thread!!! EL evaluator doesn't like when complex EL expression containing null values and it sometimes hangs instead of throwing exceptions like NPE. If we define any exception handler in our task flow  the problem is gone. It even doesn't matter what it does. It is just needed  by controller's exception handling mechanism.

Instead of adding exception handler to the inner task flow we can try to add it the calling task flow. And if this handler is just some method call activity without navigating to any View activity, we will face the same problem, because we will still see on the screen "dead" task flow.

So the solution is either to add any exception handler to the inner task flow or to add some handler with a navigation to a view activity to the calling task flow. But! The simplest and the most correct rule - don't have any task flows without exception handlers!


That's it.



11 Dec 2011

Dynamic ADF Train. Showing train stops programmatically.

In one of my previous posts I showed how to create train stops programmatically. And I got a comment with a question on the post - "Is it possible to show different pages on each of the dynamic train stop?". The answer is - Yes, off course!
In this post I'm going to give an example of showing train stops programmatically. So, I need to show or hide some stops dynamically at runtime. Everybody knows that TrainStop has Ignore attribute

 
And if we could dynamically change it's value or put there some EL expression that could be evaluated during the taskflow's life cycle, It would be the best approach of showing/hiding train stops at runtime. But Ignore attribute is evaluated only once, at the task flow initialization phase and cannot be modified further.  But! As it was shown in this post we can programmatically add (or remove) train stops to the train model of the task flow. So, we can do it!

Let's say I've got the following train TaskFlow:


PassenerView activity, LuggageView and MealView  by default have <ignore>true</ignore> attribute and at the very beginning, after task flow initialization, are hidden. The train model doesn't have stops for these activities. On the first stop of the task flow I'm going to decide which activity should be included in the train and which one is hidden. On the task flow initialization I call the following managed bean method:

    private static String START_VIEW = "StartView";
    //Fill map with all activities of the TaskFlow
    //except StartView
    private void InitIgnoredStops() {
        for (Iterator it= getTaskFlowDefinition().getActivities().values().iterator(); it.hasNext();) {
          Activity act = (Activity) it.next();
          if (!START_VIEW.equals(act.getIdAttribute())) {
                ignoredStops.put(act.getId(), "false");
            }

        }
        
    }

The ignoredStops map is shown on the StartView page using techniques explained in this post. So, I have the following page:



The following method is called on Submit button's action:

public String buttonPress() {

    TrainModel trainModel = TrainUtils.findCurrentTrainModel();

    //Loop over the map
    for (Object actid : ignoredStops.keySet().toArray()) {
        //If activity is not chosen 
        if (!Boolean.parseBoolean(ignoredStops.get(actid).toString())) {
            // if activity is included in the train then remove it
            if (trainModel.getTrainStops().get(actid) != null)
                 trainModel.getTrainStops().remove(actid);
        } else {
            //If activity is chosen and it is not included in the train 
            // then we need to include it
            if (trainModel.getTrainStops().get(actid) == null) {
                MetadataService metadataService =
                    MetadataService.getInstance();
                
                //Get activity and its train stop definition
                Activity activity =
                    metadataService.getActivity((ActivityId)actid);                
                TrainStopContainer stopContainer =
                        (TrainStopContainer)activity.getMetadataObject();
                TrainStop ts = stopContainer.getTrainStop();
                
                
                //Create new train stop model and add it to the train
                trainModel.getTrainStops().put((ActivityId)actid,
                                                   new TrainStopModel(ts,
                                                                      (ActivityId)actid));
                }
            }


        }
    
}

If needed activities are chosen we can press Submit  button and get the following picture:




That's it!