Wednesday, June 30, 2010

Case Insensitive Search

We had an interesting problem when migrating to the PS2 version of jdeveloper. All of our  QBE table filters that previously were case insensitive became case sensitive.


ADF supports case-insensitive queries but it relies on use of the UPPER() function to wrap the criteria and column selection.  We made design decisions a long time ago, that (A) the business data would not be converted to all upper case, and (B) the application search screens would not convert user criteria to upper case. To make all searches case-insensitive, the application use Oracle’s case-insensitive string comparison feature NLS_COMP=LINGUISTIC and NLS_SORT=BINARY_CI (where CI means case-insensitive).  Furthermore, to support indexed case-insensitive searches, the data modeler added linguist-sort indexes on all indexed character strings. That works fine for the “=” operator but we found out it does not work for the LIKE operator. This has been fixed in the 11g database but if using a 10g database,  filtering using the like operator will invalidate the indexes and produce a full table scan.  Furthermore, the use of the UPPER() function on the table column will invalidate the linguistic sort index so we can't use the ADF way of making filters be case insensitive.

The reason filters on tables became case sensitive in PS2 was that even though the database was case insensitive by default, ADF would perform an in-memory filter immediately after executing the query and the in-memory filter, being case sensitive would remove all of the non-matching rows from the result set.  The fix for this was a little tricky.   All of our view objects inherit from a custom base class we created.  In this class we have overridden the getViewCriteraClause(boolean forQuery) method.   This method is called whenever ADF is about to apply view criteria (i.e. filter)  for either a database or a in-memory query.   It conveniently lets you know which type (forQuery) it is requesting.  So now all we need to do is return null when forQuery==false and no criteria will be applied to in-memory filters.   But what if we actually want to perform in-memory filtering?   Well, we can check the query mode to determine if the query is a "database only" query  and no in-memory filtering should be performed.   The problem caused in PS2 was because ADF was performing this in-memory filter even though the query mode had not been set to do so. Therefore we can detect this condition and only allow in-memory filtering when the view's query mode is set to do so.

In the example code below, I'm allowing the in-memory filter to occur but simply calling vc.setUpperColumns(true); to put the UPPER() clause around the in-memory filter making it case insensitive. I want to do just the opposite when the query is bound for the database. There is one section of code added there to support date filters where we skip the in-memory filter all together by returning null. I'll talk about that in a later blog.




   public String getViewCriteriaClause(boolean forQuery) { 

    if (forQuery == false){
         // clause is for cache (in-memory)
    
    
  // WARNING - It is possible to set the criteria mode to cache and the query mode
  // to database.  That will query for a larger result set, then immediately filter it down in-memory
  // queryModeDoesNotUseCache will be true in that case even though it performs a
  // in-memory filter after the fact.  Doing so is not recommended as it wastes memory unless you
  

            boolean queryModeDoesNotUseCache =
                ((getQueryMode() & ~QUERY_MODE_SCAN_DATABASE_TABLES) == 0);


            ViewCriteria[] vcs =
                getApplyViewCriterias(ViewCriteria.CRITERIA_MODE_CACHE);
            if (vcs != null && vcs.length > 0) {

                boolean criteriaForCacheModeOnlyFound = false; //
                for (ViewCriteria vc : vcs) {

                    criteriaForCacheModeOnlyFound |=
                            (vc.getCriteriaMode() == ViewCriteria.CRITERIA_MODE_CACHE);

                    if (vc.isUpperColumns() == false) {
                        vc.setUpperColumns(true); // puts UPPER(..) around the values for filtering in-memory
                    }
                }
                if (queryModeDoesNotUseCache &&
                    !criteriaForCacheModeOnlyFound) {
                    // if query mode does not apply to cache and criteria mode
                    // is not "cache only" mode (i.e not for both cache & database)
                    // don't apply any in-memory filter.
                    // This is needed in PS2 to prevent in-memory filtering of dates which causes
                    // no results because the time component is compared as well.
                    // see CustomSQLBuilderImpl which has a PS2 workaround to trunc the date
                    // so the date filters work (without comparing time component)
                    // trunc will fail if applied to a in-memory filter on PS2
                    return null;
                }


            }
    } else{
      
      ViewCriteria[] vcs =
          getApplyViewCriterias(ViewCriteria.CRITERIA_MODE_QUERY);
      if (vcs != null && vcs.length > 0) {
          for (ViewCriteria vc : vcs) {

              if (vc.isUpperColumns()) {
                  vc.setUpperColumns(false); // remove UPPER(..) around the values for database queryies
              }
          }
      
      
      }
    }
        String clause = super.getViewCriteriaClause(forQuery);

        return clause;

    }

Tuesday, June 29, 2010

Creating Page level accelerator (hot) keys

ADF allows you to set access keys on buttons.  For example
<af:commandtoolbarbutton accesskey="o" 
            action="OpenDetails" text="Open" />

The accessKey must be a single character and in firefox, in order to use the access key you have to hit Alt+Shift+o.

What if you want to use an accelerator key for other types of keystrokes? Menu Items are the only component that supports accelerator keys so for example to add a hot key for CTRL+0  you would need to add a menu item somewhere on the page 
<af:commandMenuItem action="OpenDetails" 
                       accelerator="ctrl 0" />

A menu item needs to hang inside a af:menu and af:menu needs to hang inside a menuBar so if you only want to use the accelerator feature without displaying a menu you will need to hide the menu container (menuBar).

using inlineStyle="display:none".   Now have the menu item perform the same action as the button and you have just created an accelerator key stroke for your button.

 Oracle Documentation on Accelerator Keys

Tuesday, June 15, 2010

Displaying newly added row in a table

With the new release  of jdeveloper (PS2) There was a table that I thought wasn’t refreshing after adding a new row to it. It actually was refreshing but when the table refreshed, the scroll bar moved slightly but not noticeably down so that that the added row was scrolled off the top of the screen so that the table looked like it hadn’t changed. I discovered that there is a property of the table “DisplayRow” that can contain "default", "first", "last" or selected. The dropdown shows that selecting “default” should be the same as selecting “first” but I found this not to be the case. When I changed it from “default” to “first” it fixed the problem and now the added row is displayed at the top of the table after refreshing without moving the scrollbar.

Friday, June 4, 2010

Preventing long numbers from displaying in exponential notation when exporting to excel

The af:exportCollectionActionListener behavior in ADF Faces Rich client provides a simple way of exporting table content to excel spreadsheets. There is one issue with that though in that long numbers will display in exponential notation. This is the fault of Excel not the export. Opening the spreadsheet in "Open Office" doesn't exhibit that behavior. There was a work-around for this problem as described in a blog by Duncan Mills but that work-around no longer works in the PS2 release of Jdeveloper, however I was able to modify that solution and make it work. The work around was to put hidden text around the data in the column that put a =TEXT(...) or =TRIM(...) tag around the long data value. This tag would get exported and inform excel that the value was to be displayed as text. This didn't work in PS2 because elements with the visible='false' attribute no longer get exported. My work-around was to set the element to be visible but add a inline style to the text component of "display:none". Now it gets exported to excel but doesn't show up in the UI. Problem solved. 

example


<af:panelGroupLayout layout="horizontal" >
<af:outputText value="=TRIM(" visible="true" inlineStyle="display:none;"/>
<af:outputText value="121212121212121212121" />
<af:outputText value=")" visible="true" inlineStyle="display:none;"/>
</af:panelGroupLayout>