Tuesday, July 28, 2009

Iterating through View Object RowIterator Bug.(NOT ADF BUG, Development Bug)

The oldest bug in writing code is losing a row in an iterator.
This happens in ADF also.
I got embarrassed today saying to a developer 'RTFM' and been proven wrong. So I will first post some parts of the javadoc of RowIterrator and then a test case that proves it wrong.

When a Row Iterator is first opened or created, the currency is placed on an imaginary "slot" before the first row. This enables the user to invoke next() on a fresh new Row Iterator and retrieve the first row. For example, the following block of code will print attribute "Ename" for all employees (it does not miss the first row):
ApplicationModule myAM;
...
ViewObject myVO = myAM.findViewObject("MyEmpVO");
Row row;
while ((row = myVO.next()) != null)
{
System.out.println("Ename: " + row.getAttribute("Ename"));
}



void reset()
Moves the currency to the slot before the first row.
After this method, the current slot status will be SLOT_BEFORE_FIRST except in cases where this iterator is associated to an iterator binding in an ADF application which sets the currency to the first row in the iterator if available. A subsequent invocation of next() will cause the first row to become the current row.



So I created a View Object that only gets 3 rows and a method in application module that iterrates according to the above descriptions through the iterator and return the rows visited in a string:
public String checkIterations() {
String res = "Iterated through : ";
RowIterator view = this.getDepartmentsView1();
view.reset();
Row row;
while ((row = view.next()) != null) {
System.out.println("rownum: " + row.getAttribute("RowNum"));
res += row.getAttribute("RowNum") + " ";
}
return res;
}
When I run Application nodule and test this method, it goes through all rows as expected.


But if I open the view object (execute query) and then run this method again, the first row is never visited.


So is this a bug?

Test Case:
http://adfbugs.googlecode.com/files/TestIterator.zip

As my friends in the comments said its not a bug
Its a bad development practice (development bug) to do it as described above.

I should say RATFM (readh all the f... manual):

9.7.6 What You May Need to Know About Programmatic Row Set Iteration

Any time your application logic accesses a row set to perform programmatic iteration, you should use a secondary row set iterator when working with view object instances in an application module's data model, or view link accessor row sets of these view object instances, since they may be bound to user interface components.
To create a secondary iterator, use the createRowSetIterator() method on the row set you are working with. When you are done using it, call the closeRowSetIterator() method on the row set to remove the secondary iterator from memory.
Example 9-7 shows a typical application module custom method that correctly uses a secondary row set iterator for programmatic iteration, because the EmpView1 view object instance in its data model may be bound to a user interface (either now or at a later time).
Example 9-7 Using a Secondary Row Set Iterator in an Application Module Custom Method

// Custom method in an application module implementation classpublic
void doSomeCustomProcessing() {
ViewObject vo = getEmpView1();
// create secondary row set iterator with system-assigned name
RowSetIterator iter = vo.createRowSetIterator(null);
while (iter.hasNext()) {
Row r = iter.next();
// Do something with the current row.
}
// close secondary row set iterator
iter.closeRowSetIterator();}


13 comments:

  1. You should close RowSetIterator after while loop. This should help.

    Regards,
    Andrej

    ReplyDelete
  2. Read - 9.7.6 What You May Need to Know About Programmatic Row Set Iteration from ADF Developer Guide :)

    Andrej

    ReplyDelete
  3. Not a bug. The section Andrej mentions above contains the explanation of why we recommend using a secondary RowSetIterator for programmatic iteration, "Iterator bindings force their row set iterator to be on a valid row to guarantee that UI components display data when the row set is not empty. This has the side-effect of preventing your custom logic from navigating to the slot either before the first row or to the slot after the last row (when it is using the same row set iterator as an iterator binding). In concrete terms, this means that a typical while (rowset.hasNext()) iteration loop will either be skipped or start by processing the second row instead of the first as shown in Example 9-8"

    ReplyDelete
  4. Thanks guys.
    Indeed this behaviour is documented elsewhere. May be javadoc needs update.
    Yet i wander if i createRowSetIterator() and change an attribute in the rows that i iterated, will it change also the attribute in view object?
    will it be propageted to the page iterator?
    I will check these and if its ok i will flag this thread as not a bug.

    ReplyDelete
  5. Indeed its not a bug its a feature. But it will cause a lot of developers bugs if its not known.
    i checked also updating and deleting rows.

    ReplyDelete
  6. So is it OK to insert into or delete from a VO used by a UI? The footnote says, no iteration, but it does not say no inserts or deletes. I ask because insert changes the row currency, yes?

    ReplyDelete
  7. WTF??????

    Now I'm really mad at Oracle. I have found many gaps in ADF documentation, but this case is the worst. The documentation is clearly misleading. It's a bug in the documentation, since it states that a piece of code would run correctly without mentioning the major pitfall waiting to happen.

    You cannot expect the reader to find the full explanation after being clearly instructed that «the following block of code will print attribute "Ename" for all employees (it does not miss the first row)». We do not read the whole documentation before starting to program. We read the parts that are necessary to the tasks at the moment.

    But wait, there's more: I can't even find that 9.7.6 chapter in the documentation I'm using: Oracle® Application Development Framework
    Developer’s Guide
    10g Release 3 (10.1.3.0)
    B28967-02
    June 2008

    Where's it? And how far is it from the misleading text?

    And what about Michael's question?


    If had not read the documentation and opted to use first() and next() instead of reset() and next(), I would have had better luck.

    Sorry for the rant, but I'm perplexed.

    ReplyDelete
  8. Tanks for the explanation !!

    ReplyDelete
  9. I have similirar problem. In short I have a tree table structure country-location-department in hr user and I want to update all departments under country. I don't know how to run second iterator inside while.

    I tried your method but I don't know how to get
    getEmpView1(); method in my case getDeptView1(); The method I have is for iterator

    I new in adf.

    ReplyDelete
  10. i have a similar problem. but in my cause its with a view criteria. here is the code
    public int getAvertActivity(String someID){
    Row[] row = null;
    if(StringUtils.isNotEmpty(avertID)){
    VOImpl voImpl = getvo();
    row = iter.getAllRowsInRange();
    voImpl.setId(someID);
    voImpl.setApplyViewCriteriaName("VOCriteria");
    voImpl.executeQuery();
    row = voImpl.getAllRowsInRange();

    }
    return row.length;
    }
    when i call this method for the first i am getting the expected output.but when i call the same method for second time i am losing all the data (data is lost at voImpl.executeQuery();) and having only the data which is returned in the first call

    could you please let me know what is the fix

    this is only with programmatic vo's only (In - memory data )

    ReplyDelete
  11. This is an old post, but it just solved a HUGE headache for me.

    Thanks for the post!

    ReplyDelete
  12. thanks a lot, It worked for me as well.

    ReplyDelete
  13. yay, bringing the pieces together. much appreciated.

    ReplyDelete