A common mistake while iterating through <af:table> rows

One common mistake while iterating through the rows of a table is failing to restore the row currency after the iteration.
Consider a use case scenario where selected rows of a table need to be processed whenever user does some action. Solution is to stamp each row and process them on by one. The most common mistake here is forgetting to restore the row currency.

Wrong Implementation:

public void someEvent(ActionEvent actionEvent) {
  RowKeySet selectedRowKeys = someTable.getSelectedRowKeys();
  if (selectedRowKeys != null) {
    Iterator iter = selectedRowKeys.iterator();
    if (iter != null && iter.hasNext()) {
    
    Object rowKey = iter.next();
    //stamp row
    someTable.setRowKey(rowKey);
    JUCtrlHierNodeBinding rowData =
        (JUCtrlHierNodeBinding)someTable.getRowData();
        
    //Do something here...
    
    }
 }
}

Ooops...Unfortunately the above code fails with the below error.


java.lang.NullPointerException
 at oracle.adfinternal.view.faces.model.binding.RowDataManager.getRowIndex(RowDataManager.java:187)
 at oracle.adfinternal.view.faces.model.binding.FacesCtrlHierBinding$FacesModel.getRowIndex(FacesCtrlHierBinding.java:570)
 at org.apache.myfaces.trinidad.component.UIXIterator._fixupFirst(UIXIterator.java:414)
 at org.apache.myfaces.trinidad.component.UIXIterator.__encodeBegin(UIXIterator.java:392)
 at org.apache.myfaces.trinidad.component.UIXTable.__encodeBegin(UIXTable.java:168)
 at org.apache.myfaces.trinidad.component.UIXCollection.encodeBegin(UIXCollection.java:517)
 at org.apache.myfaces.trinidad.component.UIXComponentBase.__encodeRecursive(UIXComponentBase.java:1478)
 at org.apache.myfaces.trinidad.component.UIXComponentBase.encodeAll(UIXComponentBase.java:771)

What went wrong here?

A close analysis shows that stamping a row modifies the currency each time. So once the processing is over, it's mandatory to restore the currency with original value. Otherwise row Index may point to different(wrong RowKey) currency which is missing from RowDataManager of the binding layer, and result in NullPointerException when the page refreshes. Solution is simple, change the above code as shown below to restore currency as last step.

Correct Implementation:

public void someEvent(ActionEvent actionEvent) {

  RowKeySet selectedRowKeys = someTable.getSelectedRowKeys();
  
  //Store original rowKey
  Object oldRowKey = someTable.getRowKey();
  try{ 

    if (selectedRowKeys != null) {
      Iterator iter = selectedRowKeys.iterator();
      if (iter != null && iter.hasNext()) {
      Object rowKey = iter.next();
      someTable.setRowKey(rowKey); //stamp row
      JUCtrlHierNodeBinding rowData =
        (JUCtrlHierNodeBinding)someTable.getRowData();
      //Do something here
      }
    }

  }finally{

    //Restore the original rowKey
    someTable.setRowKey(oldRowKey);

  }
}


Learn More ...
There are a lot more points like this. If  you are curious to learn the internals of the ADF Business Components and ADF Binding Layer,  the following book is for you - Oracle ADF Real World Developer’s Guide.
More details about this book can be found in this post- http://jobinesh.blogspot.in/2012/07/pre-order-your-copy-of-oracle-adf-real.html

Comments

  1. That helps. I was an instant fan, and no doubt, will be a lifelong subscriber to this blog after reading your posts.

    In your example, we did not perform a #{bindings.[tablename].treemodel.makeCurrent}. Then why is the restoration of old RowKey so important to ADF?

    ReplyDelete
  2. Thanks Swapnil!
    I guess the issue what you are seeing could be related to the default 'selected row', which is set by ADF Model layer.

    ReplyDelete
  3. Hi Jobinesh,

    Thank your for the great post, it is very useful.

    What happens if you delete the current row?
    In your example for: //Do something here
    to have row.remove();
    and the selection to contains the current row.

    Is it a good ideea to have something like:
    someTable.setRowKey(null);

    Greetings,
    Bogdan

    ReplyDelete
  4. Bogdan,
    I would expect this part to be taken care by the framework. Do you see any error t runtime if you don't set someTable.setRowKey(null)? If yes please post the stack trace here

    ReplyDelete
  5. Hi Jobinesh,

    Thanks for the insight on this error. I had no idea why this error is coming in my application. Your blog helped to solve the problem.

    -Satya

    ReplyDelete
  6. Hi all ,
    In my project i need to select multiple rows from a table.

    The code i have tried using after looking forum are as below:-


    ==========
    1. RowKeySet selectRegion=getReAssignUserToLocationTable().getSelectedRowKeys();
    Iterator itr=selectRegion.iterator();
    DCBindingContainer binding=(DCBindingContainer)BindingContext.getCurrent().getCurrentBindingsEntry();
    DCIteratorBinding reg=binding.findIteratorBinding("accountContactsIterator");
    RowSetIterator rtr=reg.getRowSetIterator();
    System.out.println("setting value"+rtr);
    while(itr.hasNext()){
    System.out.println("inside loop");
    Key key=(Key)((List)itr.next()).get(0);
    System.out.println("inside loop2"+key); // till this point its working good
    Row currentRow = rtr.getRow(key); // fetching null at this stage
    System.out.println("inside loop3"+currentRow);
    ===> System.out.println(currentRow.getAttribute("LNAccountSeqId")); // due to above line fetching null we getting "null pointer exception"

    }
    =======================================
    In the above method i am getting "null pointer exception" at marked step( ==>), after debugging i came to know that the iterator from where i am trying to fetch row data is returning null while quering with row key inside the loop. "we are not using VIEW OBJECT in our project because of which above code is not working for us. we are using "non ADF iterator and jaxp java classes" to call the webservice.
    in the above code we are able to populate rowkeys set and able to print the keys value for the rows we selecting from table.

    ==> need your help in order to fetch data of a selected row inside while loop for a key. kindly assist.

    -==================================
    And another option we tried is below, In this we are using only table to get keys and fetching data from the row but this also getting null pointer exception at marked row (====>),
    i am not understanding why for this method also i am getting same error ???
    =====================================
    :-
    2. RowKeySet rowKeys = reAssignUserToLocationTable.getSelectedRowKeys();
    CollectionModel cm =(CollectionModel)reAssignUserToLocationTable.getValue();
    System.out.println(rowKeys);
    Object oldRowKey = reAssignUserToLocationTable.getRowKey();
    try{
    for (Object rowkey:rowKeys) {
    System.out.println("inside for loop");
    int i =rowkey.hashCode();
    System.out.println(" i = " +i);
    System.out.println("row data is "+rowkey);


    reAssignUserToLocationTable.setRowKey(rowkey);
    System.out.println("index="+i);
    System.out.println("row"+ reAssignUserToLocationTable.getRowCount());// working fine till this point
    Row r= (Row)reAssignUserToLocationTable.getrow; // fetching null
    =====> System.out.println("row fetched"+ r.getAttribute("LNAccountSeqId")); // due to above line fetching null we getting " null pointer exception'"


    }
    }
    finally{
    //Restore the original rowKey
    reAssignUserToLocationTable.setRowKey(oldRowKey);
    }
    ==========================================
    waiting for valuable suggestion !
    thanks
    mohit

    ReplyDelete
  7. Mohit,
    I've noticed this behavioral if th underlying model(data control) is missing Key definition . Is that your case also?Please check

    ReplyDelete
  8. Jobinesh,

    Need your help.My UI page is like, when I select a particular node in a tree on left pane, the details of that selected node should come on right pane.
    My problem is for the first click on any particular record, it is not displaying the correct record.Instead for first click, it always giving the details of first record.I went and debug the code and found that getRowData is returning null though there is a row key associated with that.And depending on the row data(if not null) we are doing something.since the row data here is null, we are getting the correct results.Code for selecting a node is like this.
    UIXTree rt = treetable();
    Object oldKey = rt.getRowKey();
    try{
    for(Object rowKey :treetable.getSelectedRowKeys()){
    rt.setRowKey(rowKey);
    return (JUCtrlHierNodeBindingrt.getRowData();
    }
    }
    finally{
    rt.setRowKey(oldKey);
    }

    This happens only for the first click.After that if we click on other record, every thing is going fine.Please help me.

    ReplyDelete
  9. Jobinesh,

    Very helpful tip.

    Thanks!
    Siva

    ReplyDelete
  10. i want to display table data in multiplepages(pagination).
    my code is like this.

    public void previousActionListener(ActionEvent actionEvent) {

    this.getPurVO1Iterator().setFirst(this.getPurVO1Iterator().getFirst() - rowsPerPage);
    }

    public void nextActionListener(ActionEvent actionEvent) {


    this.getPurVO1Iterator().setFirst(this.getPurVO1Iterator().getFirst()+rowsPerPage);
    }



    is there any better solution for this..? because my table have more than 3000 rows its taking time for navigating the pages

    ReplyDelete
  11. Hi Jobinesh,


    I have a treeTable based on a VO, and i have a button which performs certain action (modifying one attribute of that row from true to false). That button is enabled if attribute is initially false.
    And i have a set disabled = #{....handleDisableCompositeOT1} for the button which first checks if the row is selected and then checks the attribute. For checking if any row is selected i use below method:

    public int getRowSel() {
    RichTreeTable treeTable = null;
    int selcount = 0;
    Object obj = AdfUtil.findComponent(TABLE_ID);
    treeTable = (RichTreeTable)obj;

    if (treeTable != null) {
    selcount =((treeTable.getRowCount() != 0) ? treeTable.getSelectedRowKeys().size() : 0);
    }
    return (selcount);
    }

    public void handleDisableCompositeOT1(ActionEvent event)
    {

    logger.entering(AssociationBean.class.getName(),
    "handleDisableCompositeOT1", new Object[]
    { event });
    RichTreeTable table =
    (RichTreeTable) AdfUtil.findComponent(TABLE_ID);

    RowKeySet rks = table.getSelectedRowKeys();
    Iterator selectedRowIterator = rks.iterator();

    // get the resource bundle.
    FacesContext context = FacesContext.getCurrentInstance();
    HttpServletRequest request =
    (HttpServletRequest) context.getExternalContext().getRequest();
    Locale locale = NLSUtil.getClientLocale(request);
    ResourceBundle rb =
    ResourceBundle.getBundle("oracle.sysman.core.gccompliance.view.CoreGccomplianceUiMsg",
    locale);

    while (selectedRowIterator.hasNext())
    {
    Object key = selectedRowIterator.next();
    table.setRowKey(key);
    JUCtrlHierNodeBinding rowWrapper = null;
    rowWrapper = (JUCtrlHierNodeBinding) table.getRowData();
    ....................................
    // activate the Enable Button and de-activate Disable button.
    setDisableMenuOT1(true);
    setEnableMenuOT1(false);
    AdfUtil.addPartialTarget(table);
    doPPR(TABLE_COUNT_ID);
    doPPR(TABLE_ID);
    logger.exiting(AssociationBean.class.getName(),
    "handleDisableCompositeOT1");
    }

    First time when i select any row getRowSel() performs normally and then if we click on the button to perform the function #{....handleDisableCompositeOT1} is called and debugger shows normal execution of the function.
    But after the execution of function is finished and control again goes into getRowSel(), it throws NPE as below:

    Caused by: java.lang.NullPointerException
    at oracle.adfinternal.view.faces.model.binding.RowDataManager._getRowCount(RowDataManager.java:541)
    at oracle.adfinternal.view.faces.model.binding.RowDataManager.getEstimatedRowCount(RowDataManager.java:321)
    at oracle.adfinternal.view.faces.model.binding.RowDataManager.getRowCount(RowDataManager.java:241)
    at oracle.adfinternal.view.faces.model.binding.FacesCtrlHierBinding$FacesModel.getRowCount(FacesCtrlHierBinding.java:613)
    at org.apache.myfaces.trinidad.component.UIXCollection.getRowCount(UIXCollection.java:348)
    at oracle.sysman.core.gccompliance.view.library.standard.assoc.AssociationBean.getRowSel(AssociationBean.java:315)

    Can you please point out the mistake i am making ?

    ReplyDelete

Post a Comment