Thursday, January 29, 2009

Hopping from one Train Stop to the next, causes the old stop to reexecute its bindings

We have a page with several tabs within it. If you click tab 2, the bindings for both tab 1 (the starting point) and tab 2 get executed. If you then go to tab 3, the bindings for tabs 1, 2, and 3 get executed. The more tabs you visit the more crunching that goes on behind the scenes. It's obvious that we don't need tabs that we aren't currently viewing, to get executed, just the one that we have most recently clicked.

To avoid this, we have customized some of the ADF data control classes, namely DCTaskFlowBinding. What we have done, is added code to release the list of executables from a task-flow’s bindings when the task-flow is navigated away from. This avoids having its executables refreshed.

The underlying ISSUE:
As new train-stops are visited, the bindings for each of the train stops, which are task-flows in our case, are added to the main train stop task-flow's list of executables within it bindings. Each time you visit a new tab, or rather a new train stop, the bindings for the main task-flow get re-executed. This causes all of its executables, which basically are the bindings for each of the visited tabs' task-flows, to get executed as well. Which, as you may have figured, causes the entire tree of bindings to get refreshed. Since this main task-flow is a major part of our application, there are probably hundreds of iterators within this tree, especially after visiting each of the tabs. This was causing a major slow down.

The FIX:
Our solution to this problem was to simply release the old tab's executable bindings as it was navigated away from. There is a navigation listener on the main task-flow which contains the activityId of the currently selected train stop. Once a new train stop is selected, this listener gets called. The last part of the listener method calls cleanupBeforeRelease in the DCTaskFlowBinding class. What we have done is, customized the DCTaskFlowBinding class to, as a part of this method, release all of this task-flows's executable bindings. Releasing these bindings essentially removes them from the binding container itself, which prevents them from being executed when the top level task-flow navigates to a new tab. This old tab's bindings still get executed, however, it is much quicker since the costly executables have been released.

The CODE:
  1. In DataBindings.cpx
    Replaced:
    <factory nameSpace="http://xmlns.oracle.com/adf/controller/binding"
    className="com.collectamerica.aquila.common.TaskFlowBindingDefFactoryImpl"/>


    with

    <factory nameSpace="http://xmlns.oracle.com/adf/controller/binding"
    className="com.collectamerica.aquila.common.CustomTaskFlowBindingDefFactoryImpl"/>


  2. Create CustomTaskFlowBindingDefFactoryImpl.java and extend TaskFlowBindingDefFactoryImpl

    Within that class we basically replaced:
    DCTaskFlowBindingDef taskflowBindingDef = new DCTaskFlowBindingDef();


    with

    DCTaskFlowBindingDef taskflowBindingDef = new CustomDCTaskFlowBindingDef();


    to end up with:

    public class CustomTaskFlowBindingDefFactoryImpl extends TaskFlowBindingDefFactoryImpl {
    public CustomTaskFlowBindingDefFactoryImpl() {
    super();
    }

    /**
    * {@inheritDoc}
    * @param element {@inheritDoc}
    * @return {@inheritDoc}
    */
    public DCDefBase createDefinition(DefElement element)
    {
    // In our case there is only one object we can create but we still do
    // the check.
    if (element.getNodeName().equals(Constants.ENAME_TASKFLOWBINDING))
    {
    DCTaskFlowBindingDef taskflowBindingDef = new CustomDCTaskFlowBindingDef();
    return taskflowBindingDef;
    }
    return null;
    }
    }


  3. Create a class named CustomDCTaskFlowBindingDef which extends DCTaskFlowBindingDef, and basically this changes the binding container class name to point to our own custom class that we'll create next:

    public class CustomDCTaskFlowBindingDef extends DCTaskFlowBindingDef {
    public CustomDCTaskFlowBindingDef() {
    super();
    }

    @Override
    public void init(HashMap initValues) {
    super.init(initValues);

    // Overide BindingContainer class name with our special TaskFlowBinding.
    mBindingContainerClassName = CustomDCTaskFlowBinding.class.getName();
    }
    }


  4. Create a class named CustomDCTaskFlowBinding which extends DCTaskFlowBinding and override the cleanupBeforeRelease method and implement it as follows:

    public class CustomDCTaskFlowBinding extends DCTaskFlowBinding {
    public CustomDCTaskFlowBinding() {
    super();
    }

    /*
    * This method has been customized in order to release bindings which are no
    * no longer needed. You'll find that in the super's method,
    * cleanupBeforeRelease, that there is a TODO which states that exactly what
    * we're adding should be done. This will prevent these old bindings from being
    * reexecuted everytime we switch between task-flows, and in the specific case
    * this was originally done for, will prevent all previous tabs from reexectuing
    * their executable bindings when switching tabs.
    */
    @Override
    protected void cleanupBeforeRelease(String childViewPortId) {
    super.cleanupBeforeRelease(childViewPortId);

    //if this task-flow is being notified, it is because this task-flow is being
    // navigated away from, so release its executable bindings (the expensive ones)
    // which will get relisted when the page is actually displayed again
    List executables = this.getExecutableBindings();

    if(executables != null){
    for(int i=0; i<executables.size(); i++){
    Object oExec = executables.get(i);

    if(oExec instanceof JUFormBinding){
    JUFormBinding jExec = (JUFormBinding)oExec;
    jExec.release(DCDataControl.REL_ALL_REFS);
    }
    }
    }
    }
    }


No comments: