Oracle JDeveloper HowTo

 

Creating Search Pages with Both Fixed and Dynamic Criteria

Author: Steve Muench, Oracle ADF Development Team
Date: September 16, 2004

 

Abstract

Oracle ADF makes it simple to implement search pages that include query-by-example criteria for a fixed set of data collection attributes. After first reviewing the role of ADF iterator bindings and their "find mode" behavior, this article explains how to implement a search page where the user dynamically selects which attribute in the data collection they want to search against, by picking from a drop-down list of available search attributes. While first explaining how to implement using a formal Struts layer, the last section covers how to implement the same functionality using a "Model One" JSP approach, without Struts as well.


NOTE:

You can download the DynamicCriteria.zip workspace to try the example described in this paper in your own JDeveloper 10g environment. To run the sample you will need to have first defined a connection named scott pointing to the familiar SCOTT/TIGER sample schema.



NOTE:

It will be helpful if you've read the ADF Data Binding Primer and ADF/Struts Overview before reading this HowTo article.


Contents

ADF Iterator Bindings
Iterator Binding Find Mode
Building a Search Page with Fixed Search Attributes
Showing Search Criteria and Results With Multiple Iterator Bindings
Building a Simple Dynamic Search Page
Making the Search Criteria "Sticky"
Supporting Case-Insensitive Queries
ADF Support for "Classic" JSP Development Without Struts
Implementing Search Pages Without a Struts Controller Layer
Conclusion

ADF Iterator Bindings

The ADF Bindings layer provides iterator bindings to work with collections of data from a data control. As its name implies, an iterator binding is a binding object related to an iterator. This iterator provides access to the different instances in the data collection, keeping track of its current position in the collection.

While the data collection can contain simple scalar values, typically it comprises a collection of JavaBeans: each instance having a set of well-defined properties. When a collection contains JavaBeans whose properties hold the column values of a database query result, it's convenient to think of these JavaBeans as rows in a row set.

Following this convention, regardless of the actual backend model layer implementation, Oracle ADF provides developers the consistent RowSetIterator and Row interfaces to work programmatically with data from collections provided by a data control. In essence, an ADF iterator binding is simply a helper object in the binding layer that works with an underlying RowSetIterator to access the Rows in a row set.

As shown in Figure 1 , control value bindings are related to a particular iterator binding and expose the data from its underlying collection. Some control value bindings like the three Attribute Bindings shown in the figure are "wired" to a particular attribute of the current row in their related iterator binding. Accordingly, since the current row in the iterator binding is the first row — indicated by the arrow representing the position of its underlying RowSetIterator — the related control value bindings display their respective attribute from that first row. Other control bindings like the Range Binding expose all of the rows and columns in the iterator's data collection for display in a table.

Iterator Bindings Are Bound to a Data Collection
Figure 1: Iterator Bindings Are Bound to a Data Collection

As shown in Figure 2 , if you create multiple iterator bindings for the same collection, by default they will be bound to the same underlying default RowSetIterator for the collection. As the current row is changed in the single, underlying RowSetIterator that both of these iterator bindings share, the control bindings related to both iterator bindings will continue to see the same data.

By Default, Multiple Iterator Bindings Point to the Same RowSetIterator
Figure 2: By Default, Multiple Iterator Bindings Point to the Same RowSetIterator

On the other hand, if at design time you set the Rowset Iterator Name property of an iterator binding to some non-empty value (e.g. " Second", " RSI2", or whatever), then at runtime that iterator binding will create and use a separate, named RowSetIterator over its underlying data collection. As shown in Figure 3 , this allows the multiple iterator bindings over the same collection to navigate independently. In other words, the default RowSetIterator and the secondary, named RowSetIterators keep track of their current row in an independent manner.

Declaratively Creating Secondary RowSetIterators for Independent Navigation
Figure 3: Declaratively Creating Secondary RowSetIterators for Independent Navigation

Accordingly, this allows control value bindings that are bound to these separate iterator bindings to show data from different rows in the same underlying collection.

Iterator Binding Find Mode

In addition to its basic data iteration functionality, the iterator binding also cooperates with its underlying data collection to simplify implementing query-by-example capability for your application data by providing:

  • A collection of query-by-example criteria rows, and
  • An easy-to-use "Find Mode" to work with these query criteria

In Oracle ADF, each data collection has an associated ViewCriteria collection of zero or more ViewCriteriaRows. Each ViewCriteriaRow has the same attribute structure as a Row in its related data collection, except that the attribute values are all treated as a String data type so the user can enter query criteria containing comparison operators and wildcard characters.

For example, to indicate you want to find all departments whose department number is greater than 5 and whose department name matches the string 'ACC%', you would fill in the attributes of a ViewCriteriaRow related to a DeptView collection (based on a query over the familiar DEPT table in the SCOTT schema) like this:

ViewCriteriaRow Attribute Name Criteria Value
Deptno > 5
Dname ACC%

The iterator binding's Find Mode functionality makes it easy to create search pages that populate the attributes of the ViewCriteria collection for query-by-example functionality. As shown in Figure 4 , when an iterator binding is set to work in Find Mode, it switches to use a different RowSetIterator over the related ViewCriteria collection instead. This means that when Find Mode is set to true, control bindings related to that iterator binding display and update attributes in the current ViewCriteriaRow. Likewise, a Range Binding over an iterator binding in find mode allows you to render a table of current query-by-example ViewCriteria rows.

In Find Mode, Iterator Binding Switches to Iterate ViewCriteria Collection
Figure 4: In Find Mode, Iterator Binding Switches to Iterate ViewCriteria Collection

When Find Mode is set back to false, the iterator binding switches back to work with its RowSetIterator over the data collection. This can be done explicitly by calling the iterator binding's setFindMode() method, or implicitly by calling its executeQuery() method.


NOTE:

By calling the createRow() method on the iterator binding's RowSetIterator while in Find Mode, it's possible to create additional ViewCriteriaRows rows and then proceed to populate their attributes with additional criteria. The default semantics are that query-by-example criteria in the same ViewCriteriaRow are logically AND-ed together, while criteria resulting from separate ViewCriteriaRows are logically OR-ed together. While in practice using multiple ViewCriteriaRows is not a common use case, knowing it is possible is useful information.


While the ADF iterator binding provides its Find Mode functionality independent of the kind of backend data control you choose, currently only the ADF Business Components data control makes automatic use of the ViewCriteria collection of ViewCriteriaRows at runtime. It delegates the iterator binding's ViewCriteria functionality to the underlying ADF view object, which implements the query-by-example criteria by automatically building appropriate SQL WHERE clause predicates based on the ViewCriteriaRows.

Other data control types would currently require a subclassed data control implementaiton containing some custom coding to read the query-by-example criteria from the ViewCriteria collection and translate them into an appropriate runtime search implementation.

Building a Search Page with Fixed Search Attributes

Given what we've reviewed above about iterator bindings and Find Mode, we can easily build a search page using ADF and ADF Business Components. The Model project in the workspace provided is a simple ADF application module named test.model.TestModule exposing a single view object instance named DeptView in its data model. The underlying view object is related to a Dept entity object and both are related to the familiar DEPT table in the SCOTT schema.

The /findModeSingleIter data page in the ViewController project illustrates a page that I created by performing the following steps in the JDeveloper 10g IDE:

  1. Dropped a new DataPage onto the Struts Page Flow Diagram
  2. Gave it a name of findModeSingleIter
  3. Double-clicked the new DataPage icon and accepted the default JSP page name of findModeSingleIter.jsp
  4. Selected the DeptView collection in the Data Control Palette
  5. Chose Input Form from the Data Control Palette drop-down list
  6. Dropped the DeptView data collection onto the empty JSP page.
  7. Expanded the Operations folder under the DeptView collection in the Data Control Palette and dropped four built-in actions — Find, Execute, Next, and Previous — as Button onto the JSP page, taking care to drop them inside the dotted line box representing the HTML form.

NOTE:

The default, declarative Find action toggles the find mode on and off.


To further polish this default page, I switched to source mode and added some conditional logic — using the JSTL Core tag library <c:choose> and <c:if> — based on evaluating the EL expression ${bindings.DeptViewIterator.findMode} to detect whether the DeptViewIterator iterator binding is in find mode or not. I added a section at the top of the page to conditionally set the value two page-local variables named findButtonName and executeButtonName like this:

<c:choose>
  <c:when test="${bindings.DeptViewIterator.findMode}">
    <c:set var="findButtonName" value="Cancel Search"/>
    <c:set var="executeButtonName" value="OK"/>
  </c:when>
  <c:otherwise>
    <c:set var="findButtonName" value="Search"/>
    <c:set var="executeButtonName" value="Refresh Data"/>
  </c:otherwise>
</c:choose>

Later in the page I refer to the value of the findButtonName variable to show the user either a (Search) or a (Cancel Search) button depending on whether the DeptViewIterator is in find mode like this:

<input type="submit"
       name="event_Find"
       value="<c:out value='${findButtonName}'/>"/>

and likewise refer to the value of the executeButtonName variable to show the user either a (Refresh Data) button or an (OK) button.


NOTE:

The curious reader might wonder why I use the <c:choose> to conditionally setup a page-local variable as a separate step first, and then refer to the value of that variable in the value attribute of the <input> tag to label the button later. Couldn't I have just have used the <c:choose> directly inline like this?

<input type="submit"
       name="event_Find"
       value="<c:choose>
                <c:when test='${bindings.DeptViewIterator.findMode}'>
                  Cancel Search
                </c:when>
                <c:otherwise>Search</c:otherwise>
              </c:choose>"/>

The answer is that the in-line <c:choose> inside the attribute value would end up with unwanted whitespace due to the indenting of the tags and the way that JSP tag libraries are processed. The two-step technique works better to have precise control on the values that end up as your attribute value.


I added one final conditional behavior to only show the (Previous) and (Next) buttons when not in find mode. Figure 5 shows what the data page looks like when you run it. If the user clicks the (Search) button, the page toggles to display the current ViewCriteriaRow attributes. Of course, these query-by-example criteria are initially blank, waiting to be filled-in by the end-user.

Search Page Built With a Single Iterator Binding
Figure 5: Search Page Built With a Single Iterator Binding

While in Find Mode — since the (OK) button's name is event_Execute — clicking it will trigger the Execute action as part of the default event-handling behavior of the ADF page lifecycle. During the processUpdateModel phase of the lifecycle — which occurs before the processComponentEvents phase — the user's query criteria values are updated into the respective Deptno, Dname, and Loc control value binding objects. Recall that since find mode is still true when this occurs, their related iterator binding is still switched to work with the ViewCriteria collection and these user-supplied attribute values update their corresponding attributes in the current ViewCriteriaRow.

When the built-in Execute action is invoked later during the processComponentEvents phase of the lifecycle, it calls executeQuery() method on the iterator binding. This, in turn, executes its underlying view object's query. The view object automatically augments its query to include the user-entered query-by-example criteria to produce the filtered results.

If the user clicks the (Cancel Search) button while in find mode, this executes the built-in Find action which toggles the iterator binding out of Find Mode and empties the ViewCriteria collection.

Showing Search Criteria and Results With Multiple Iterator Bindings

Depending on your application, toggling the same set of user interface controls between Find Mode and regular mode as we did in the previous section may not be exactly that you want. There are two possible usability drawbacks to the approach above. The first issue is that the user sees the results only one row at a time, having to scroll next and previous through them to understand if their query produced the expected results. The second and more serious issue is that they cannot see their query-by-example criteria once they have toggled out of Find Mode. This could be confusing to many users.

Luckily, it's easy to have our cake and eat it too. In the sections above, we learned that we can use multiple iterator bindings in a binding container bound to the same data collection. Since the ADF Find Mode is a boolean property of each iterator binding, all we need to do is:

  • Create two iterator bindings for the DeptView collection
  • Force one of them to always be in Find Mode

Using this technique, we can allow the user to enter and continue to see their query-by-example criteria while viewing the results of the search in the same page. The fixedCriteria DataPage in the sample project adopts this approach to produce a page that looks like Figure 6 .

Seeing Search Criteria and Results at the Same Time
Figure 6: Seeing Search Criteria and Results at the Same Time

Inside JDeveloper 10g, if you click on the fixedCriteria DataPage and then select the UI Model tab of the Structure Window , you'll see that the binding objects in its binding container are setup as shown in Figure 7 . The DeptView range binding, related to the DeptViewIterator iterator binding, lets us show a table displaying the search results. We've named the second iterator binding DeptViewCriteriaIterator to make it clear that we created it for the purpose of working with the query-by-example criteria. This is the one we will force to always be in Find Mode, and with which the three TextField Bindings — to display Deptno, Dname, and Loc query criteria — are related.

Binding Container for the fixedCriteria DataPage
Figure 7: Binding Container for the fixedCriteria DataPage

To force the DeptViewCriteriaIterator to always be in Find Mode, we've created a custom FixedCriteriaAction DataForwardAction class for the fixedCriteria DataPage. We can do this by clicking on the DataPage and selecting Go to Code from the right-mouse menu. If no custom DataForwardAction class exists yet, a dialog appears giving you a chance to change the default class and package names.

In this class, we've overridden the prepareModel() phase of the ADF page lifecycle to add one line of code that calls setFindMode(true) on this iterator binding like this:

// From class test.controller.FixedCriteriaAction
  /*
   * Force DeptViewCriteriaIterator to always be in Find Mode
   */
  protected void prepareModel(DataActionContext ctx) throws Exception {
    super.prepareModel(ctx);
    ctx.getBindingContainer()
       .findIteratorBinding("DeptViewCriteriaIterator").setFindMode(true);
  }

To handle the Search event that the user will trigger when they press the (Search) button, we've written an appropriate onSearch() method with the correct signature in the same class like this:

// From class test.controller.FixedCriteriaAction
  /*
   * Handle the "Search" event.
   * 
   * Reapply the ViewCriteria and re-execute the iterator binding's query.
   */
  public void onSearch(DataActionContext ctx) {
    DCBindingContainer      bc = ctx.getBindingContainer();
    DCIteratorBinding deptIter = bc.findIteratorBinding("DeptViewIterator");
    deptIter.getViewObject().applyViewCriteria(deptIter.getViewCriteria());
    deptIter.executeQuery();
  }

Notice that for a nice additional touch, we've included the conditional display of a No department matches your search criteria message in case the user's query criteria returns no rows. To implement it, we again employ the familiar <c:choose> tag — with its nested (possibly multiple) <c:when> tags and one <c:otherwise> tag — to obtain an if/ then/ else style of display. The code excerpt from the fixedSearch.jsp page looks like this:

<c:choose>
  <c:when test="${empty bindings.DeptView.rangeSet}">
    No department matches your search criteria.
  </c:when>
  <c:otherwise>
      <%--
        Show the table of results using the DeptView range binding
       --%>
  </c:otherwise>
</c:choose>

Building a Simple Dynamic Search Page

We've seen that Find Mode makes building search pages easy when the set of query-by-example attributes is fixed. But what if we want to provide a user interface like the one shown in Figure 8 where the user picks one attribute to search against from a drop-down list showing all of the possible searchable attribute names?

Allowing the User to Select a Search Field
Figure 8: Allowing the User to Select a Search Field

In this section, we'll see that using the same basic concepts we've leveraged above, creating the above user interface is again straightforward.

The dynamicCriteria DataPage in the sample project illustrates a basic implementation of the approach. Figure 9 shows the simple binding container for this page containing a DeptViewIterator iterator binding, and a related DeptView range binding.

Binding Container for the dynamicCriteria DataPage
Figure 9: Binding Container for the dynamicCriteria DataPage

To create the drop-down list in the dynamicCriteria.jsp page, we use the JSTL <c:forEach> tag to iterate over the attributeNames collection exposed by the DeptView range binding. For our particular example here, the list will include the attribute names Deptno, Dname, Loc. Since most end-users will prefer to see more friendly names in the drop-down list, we use the UI label for each option value, instead of the actual attribute name. We accomplish this by using the value of the c:forEach loop variable attrName in the EL expression:

${bindings.DeptView.labels[attrName]}

as a key into the labels map of attribute UI labels exposed by the DeptView range binding. The part of the dynamicCriteria.jsp that creates the SearchField drop-down list, the SearchValue text field, and the event_Search (Search) button looks like this:

<form method="post" action="dynamicCriteria.do">Search  
  <select name="SearchField">
    <option value="">&lt;Choose Field></option>
    <c:forEach var="attrName" items="${bindings.DeptView.attributeNames}">
      <option value="<c:out value='${attrName}'/>"><c:out
             value='${bindings.DeptView.labels[attrName]}'
      /></option>
    </c:forEach>    
  </select>
  for 
  <input type="text" size="10" name="SearchValue"/>
  <input type="submit" value="Search" name="event_Search"/>
</form>

To handle the Search event triggered when the Search button is pressed, we've written an appropriate onSearch() method in the DynamicCriteriaAction class. Since the user is dynamically picking the attribute name to target for the search, we cannot rely on the automatic setting of the ViewCriteriaRow attributes as we did in previous examples when the search fields were a fixed set and Find Mode took care of the details. We need our onSearch() method to:

  1. Get the ViewCriteria collection from the DeptViewIterator binding
  2. Clear any previous criteria rows in the collection
  3. Create a new ViewCriteriaRow and add it to the collection
  4. Set the value of the ViewCriteriaRow attribute whose name is provided in the SearchField parameter to the value provided in the SearchValue parameter.
  5. Apply the view criteria and re-execute the iterator binding's query.

To code to accomplish the above looks like this:

// From Class test.controller.DynamicCriteriaAction
  public void onSearch(DataActionContext ctx) {
    HttpServletRequest request = ctx.getHttpServletRequest();
    DCBindingContainer      bc = ctx.getBindingContainer();
    DCIteratorBinding deptIter = bc.findIteratorBinding("DeptViewIterator");
    ViewCriteria criteria = deptIter.getViewCriteria();
    if (criteria.size() > 0) criteria.clear();
    String searchField = request.getParameter("SearchField");
    if (searchField != null && !searchField.equals("")) {
      Row criteriaRow = criteria.createViewCriteriaRow();
      criteria.add(criteriaRow);
      criteriaRow.setAttribute(searchField,request.getParameter("SearchValue"));
    }
    deptIter.getViewObject().applyViewCriteria(criteria);
    deptIter.executeQuery();
  }

The reason we clear out the ViewCriteria collection on each search is to avoid successive searches on different attributes from being cumulative. Suppose the user first searches for rows with a department Name matching " ACC%" and then changes the drop-down list to search for rows with a Location of " %N%". If we did not clear the ViewCriteria collection each time, then the second query would end up trying to find rows matching both of these criteria, instead of just the second criteria.


NOTE:

The reason that the user-supplied query-by-example criteria "hang around" between HTTP page requests in the ViewCriteria collection is because the ADF application module's state management feature consciously includes the view criteria as part of the interesting state its manages about an active view object instance. This information complements details about the current row, the value of any relevant bind variables, and a few other pieces of view object instance state.


To see the results of our work, just run the dynamicCriteria data page in the ViewController project and try out a few queries.

Making the Search Criteria "Sticky"

After trying out the previous page, you might find it frustrating that the SearchField drop-down list does not show the user's selection and the SearchValue text field does not show the query criteria entered. The table of results reflect the user's attribute selection and search criteria, but a page like this that surprises the end-user, leaving them wondering if they might have made a mistake, has obvious potential usability issues. In this section, we'll evolve the example above to make the drop-down list and text field be "sticky" to maintain the user's context by continuing to show them their selected attribute and query-by-example criteria upon clicking the (Search) button.

As they did in the fixedSearch example page, multiple iterator bindings and Find Mode will figure into our solution. As shown in Figure 10 , you'll notice we've added two new binding objects to this page's binding container, compared to the example above. The DeptViewCriteriaIterator is related to the same data collection as the original DeptViewIterator, and again we'll force this second iterator binding to always be in Find Mode as we did in a previous example.


NOTE:

I created the additional iterator binding and range binding in the explicit way described in the ADF Data Binding Primer, using the options on the right-mouse menu in the UI Model tab of the Structure Window .


With the additional DeptViewCriteria range binding, related to DeptViewCriteriaIterator, we'll be able to iterate over the ViewCriteriaRows in JSTL to determine the current name of the non-empty ViewCriteriaRow attribute, and its current query-criteria value. We'll use this information to keep the SearchField and SearchValue UI controls "sticky".

Binding Container for Sticky Dynamic Find Page
Figure 10: Binding Container for Sticky Dynamic Find Page

In the stickyDynamicCriteria.jsp page, we've slightly modified the logic from the simpler dynamicCriteria.jsp above in order to mark one of the options in the SearchField drop-down list of attributes as SELECTED. We accomplish this using the following nested <c:forEach> loop at the top of the page:

<c:forEach var="Row" items="${bindings.DeptViewCriteria.rangeSet}">
  <c:forEach var="attrName" items="${bindings.DeptViewCriteria.attributeNames}">
    <c:if test="${not empty Row[attrName]}">
      <c:set var="curCriteriaAttr" value="${attrName}"/>
      <c:set var="curCriteriaVal"  value="${Row[attrName]}"/>
    </c:if>
  </c:forEach>
</c:forEach>

Since we force the DeptViewCriteriaIterator iterator binding to always be in Find Mode, the rangeSet from the related DeptViewCriteria range binding will be the set of ViewCriteriaRows instead of a set of data rows. Inside this loop, we perform a nested loop over all the attribute names exposed by the DeptViewCriteria range binding. When we find a non-empty ViewCriteriaRow attribute, we set the page local variables curCriteriaAttr and curCriteriaVal to capture the current query criteria attribute name and value for use later in the page.

As you can see in the modified form code below, we use the <c:if> tag to test whether the current attribute name matches the value of the curCriteriaAttr we calculated above, and if so output the additional SELECTED tag attribute. For the SearchValue field, it's even easier. We just use the value of the curCriteraVal variable to provide the value of the <input> tag's value attribute.

<form method="POST" action="stickyDynamicCriteria.do">Search  
  <select name="SearchField">
    <option value="">&lt;Choose Field></option>
    <c:forEach var="attrName" items="${bindings.DeptViewCriteria.attributeNames}">
      <option value="<c:out value='${attrName}'/>"
        <c:if test="${curCriteriaAttr == attrName}">SELECTED</c:if>
       ><c:out value='${bindings.DeptViewCriteria.labels[attrName]}'/></option>
    </c:forEach>    
  </select>
  for 
  <input type="text" size="10"
         value="<c:out value='${curCriteriaVal}'/>" name="SearchValue"/>
  <input type="submit" value="Search" name="event_Search"/>
</form>

The code in the accompanying StickyDynamicCriteriaAction serves the same purpose as the code we saw in the DynamicCriteriaAction. The only thing different is that we've chosen to highlight an alternative coding technique in the onSearch() method for manually populating the ViewCriteria collection.

This alternative implementation leverages the fact that an iterator binding in find mode is iterating over the ViewCriteriaRows. Since we are only searching on one field at a time, here we iterate over the attribute names of the DeptViewCriteriaIterator — which again we force to always be in find mode in prepareModel() — and set every attribute in the view criteria row to some value. If the current attribute name in the loop matches the value of the SearchField parameter passed in, then we set its value to the value of the SearchValue parameter, otherwise we set it to null. This again insures that successive searches against different user-selected attributes to not end up being undesirably cumulative. The code for this onSearch() method in StickyDynamicCriteriaAction looks like this:

  public void onSearch(DataActionContext ctx) {
    HttpServletRequest request = ctx.getHttpServletRequest();
    DCBindingContainer      bc = ctx.getBindingContainer();
    DCIteratorBinding deptIter = bc.findIteratorBinding("DeptViewIterator");
    DCIteratorBinding critIter = bc.findIteratorBinding("DeptViewCriteriaIterator");
    AttributeDef[] attrDefs = critIter.getAttributeDefs();
    Row criteriaRow = critIter.getCurrentRow();
    String searchField = request.getParameter("SearchField");
    for (int z = 0, attrCount = attrDefs.length; z < attrCount; z++) {
      String curAttrName = attrDefs[z].getName();
      criteriaRow.setAttribute(curAttrName, (curAttrName.equals(searchField) ? 
                               request.getParameter("SearchValue") : null));        
    }
    deptIter.getViewObject().applyViewCriteria(deptIter.getViewCriteria());
    deptIter.executeQuery();
  } 

Supporting Case-Insensitive Queries

A frequently-asked question on the subject of searching is, "How can I get my queries to be case-insensitive?" Using ADF Business Components, the answer is easy. Each ViewCriteriaRow in your ViewCriteria collection of query-by-example records supports a boolean-valued upperColumns property. If you set the property to true by calling the vcRow.setUpperColumns(true) API, then the view object will generate UPPER( COLUMN_NAME) instead of just COLUMN_NAME in the WHERE clause fragment created for the non-null, character-typed criteria attributes in that ViewCriteriaRow.

We can create a custom extension of the base ViewObjectImpl framework that simplifies enabling case-insensitive querying for any view object we might need it on in the future. Example 1 shows the source code for our CustomViewObjectImpl class that does just that. This class extends ViewObjectImpl and overrides the framework method executeQueryForCollection() to conditionally set the upperColumns property to true on any ViewCriteriaRows in the ViewCriteria collection. In addition, it forces the query criteria values to also be in upper case. This generic code decides whether or not to perform the case-insensitive adjustment step based on the presence of a custom view object property named CaseInsensitiveViewCriteria.

Example 1: Generic Case-Insensitive Querying Driven Off Custom VO Property Metadata
package test.fwkext;
import java.util.Iterator;
import oracle.jbo.AttributeDef;
import oracle.jbo.ViewCriteria;
import oracle.jbo.ViewCriteriaRow;
import oracle.jbo.common.JboTypeMap;
import oracle.jbo.server.ViewObjectImpl;

public class CustomViewObjectImpl extends ViewObjectImpl  {
  /**
   * Overidden framework method to optionally force query-by-example
   * criteria to be treated case-insensitively if the custom view object
   * property named CaseInsensitiveViewCriteria exists in the VO definition
   * @param qc The query collection (internal rowset) being executed
   * @param params Array of bind parameters if any
   * @param noUserParams Number of developer-supplied params in array
   */
  protected void executeQueryForCollection(Object qc,
                                           Object[] params,
                                           int noUserParams) {
    if (getProperty("CaseInsensitiveViewCriteria") != null) {
      doCaseInsensitiveQBEOnStringsInViewCriteria();
    }
    super.executeQueryForCollection(qc, params, noUserParams);
  }
  /**
   * Set the view criteria rows to use case-insensitive querying and
   * uppercase any of the non-null query-by-example criteria provided.
   * @param iter IteratorBinding whose view criteria should be modified
   */  
  private void doCaseInsensitiveQBEOnStringsInViewCriteria() {
    ViewCriteria vc = getViewCriteria();
    if (vc != null) {
      AttributeDef[] defs = vc.getViewObject().getAttributeDefs();
      Iterator criteriaRows = vc.iterator();
      while (criteriaRows.hasNext()) {
        ViewCriteriaRow r = (ViewCriteriaRow)criteriaRows.next();
        if (r != null) {
          r.setUpperColumns(true);
          for (int j = 0, numAttrs = defs.length; j < numAttrs; j++) {
            if (JboTypeMap.isCharType(defs[j].getSQLType())) {
              String val = (String)r.getAttribute(j);
              if (val != null) {
                r.setAttribute(j,val.toUpperCase());
              }
            }
          }
        }
      }
    }
  }  
}

Figure 12 illustrates how you define a new view object to extend from your CustomViewObjectImpl class instead of using the default ViewObjectImpl class. By visiting the Java panel of the View Object Editor, you click on the Class Extends button to see the Extends dialog. You can see here that the DeptView view object in our example project uses the test.fwkext.CustomViewObjectImpl class as its view object base class.

Defining a Custom Base Class For Your View Object
Figure 11: Defining a Custom Base Class For Your View Object

Figure 12 shows how to set a custom VO property with the name CaseInsensitiveViewCriteria. By visiting the Custom Properties panel of the View Object Editor, you can simply type the name of any custom property and the value you'd like to assign it.

Setting a Custom VO Property
Figure 12: Setting a Custom VO Property

Our generic CustomViewObjectImpl class will invoke the additional logic to turn ViewCriteriaRows to be treated case-insensitively on any view object which:

  1. Extends from CustomViewObjectImpl as its base class, and
  2. Defines a custom view object property named CaseInsensitiveViewCriteria (with any value).

You can try out this case-insensitive query-by-example functionality in the example application by setting a custom CaseInsensitiveViewCriteria property — as shown in Figure 12 —on the test.model.DeptView view object in the Model project.

ADF Support for "Classic" JSP Development Without Struts

What happens if you want to implement search pages with a fixed or dynamic set of criteria attributes, but don't plan to use a formal Struts controller layer? Is there hope for the "classic" JSP page developers who aren't yet ready to include Struts in their application technology stack?

Why of course!

The Oracle ADF framework has been carefully designed to work well both with and without Struts. In fact, as the UML diagram in Figure 13 illustrates, the ADF PageLifecycle object is the one that implements the default page-handling "controller layer" behavior. Each method in the PageLifecycle class that implements an overridable phase of page-handling lifecycle gets passed a LifecycleContext object as an argument. The method's implementation uses this object to reference and modify key request-specific, page lifecycle context information.

For the Struts developer, ADF provides the DataAction and DataForwardAction classes whose overridable lifecycle methods mirror the ones available on PageLifecycle. By default, each one delegates to the analogous method on the PageLifecycle object being used by the current request. Each DataAction lifecycle method is passed a DataActionContext object, which extends the base LifecycleContext to add a few additional Struts-specific pieces of information.

ADF DataAction Delegates to PageLifecycle
Figure 13: ADF DataAction Delegates to PageLifecycle

NOTE:

The PageLifecycle object used for each request is created by a PageLifecycle factory object. For non-Struts environment, the default page lifecycle factory is DefaultPageLifecycleFactory in the oracle.adf.controller.lifecycle package. For the Struts-environment, the default page lifecycle factory is DefaultStrutsPageLifecycleFactory in the oracle.adf.controller.struts.actions package.

In case you ever want to customize the default page lifecycle factory, here are the details for accomplishing that. To customize the default page lifecycle factory for non-Struts JSP development — to return your own custom subclass of PageLifecycle — set the servlet initialization parameter named ADFPageLifecycleFactory to your subclass' fully-qualified class name. To change the default page lifecycle factory for the Struts environment, use the PageLifecycleFactoryPlugin that ADF provides.


This architecture allows developers building JSP pages without a Struts controller layer to customize the page handling lifecycle for each page — including writing ADF named event handler methods — by simply writing a class that extends PageLifecycle and associating it with the page. The ADF tag library provides the <adf:uimodelreference> tag that is used for this purpose.

Let's assume you are working with a JSP page named mypage.jsp, and you have created a class myapp.MyPageHandler that extends PageLifecycle:

package myapp;
import oracle.adf.controller.lifecycle.LifecycleContext;
import oracle.adf.controller.lifecycle.PageLifecycle;
public class MyPageHandler extends PageLifecycle {
  // page handler code goes here.
}

By declaring the ADF tag library in your JSP page:

<%@ taglib uri="http://xmlns.oracle.com/adf/ui/jsp/adftags" prefix="adf"%>

and including an <adf:uimodelreference> tag like this:

<adf:uimodelreference model="myPageUIModel" lifecycle="myapp.MyPageHandler" />

You indicate the name of your page's binding container and the custom page lifecycle class that will be used to override any lifecycle handling behavior and/or write its event handler methods. Of course, if a given page needs no custom code in its PageLifecycle class, then you can leave out the lifecycle attribute like this:

<adf:uimodelreference model="myPageUIModel"/>

and ADF will use the default PageLifecycle object instead.

This approach promotes a best practice coding style for "Model One" JSP development by neatly factoring the page's "controller layer" logic into a separate class instead of mixing it unmaintainably into the JSP page itself.

Implementing Search Pages Without a Struts Controller Layer

The DynamicCriteria workspace for this article comes with both a Struts-based ViewController project, as well as ViewNoController project that help to illustrate the points made in the previous section. The four JSP pages in the ViewNoController project:

  • findModeSingleIter.jsp
  • fixedCriteria.jsp
  • dynamicCriteria.jsp
  • stickyDynamicCriteria.jsp

implement identical functionality to the pages of the same names in the Struts-based ViewController project. In this section, we'll examine the minor differences between the two to highlight how the ADF concepts are exactly the same and coding techniques are nearly identical.

You'll notice that each JSP page in the ViewNoController project has a binding container that is setup with exactly the same binding objects as the ones used in the Struts-based project. Each page contains the <adf:uimodelreference> tag at the top to indicate which binding container the page is using. Except for the findModeSingleIter.jsp that required no custom code to implement, the remaining three JSP pages each indicate the name of their custom PageLifecycle class using the lifecycle attribute on their <adf:uimodelreference> tag.


NOTE:

Recall that in the Struts-based ADF environment, the binding container name and custom DataAction class are bits of metadata that are associated to the action in the struts-config.xml file.


For example, the dynamicCriteria.jsp page includes a tag like this:

<adf:uimodelreference model="dynamicCriteriaUIModel"
                      lifecycle="test.DynamicCriteriaPageHandler" />

The DynamicCriteriaPageHandler class contains exactly the same code as the Struts-based DynamicCriteriaAction class does, except for one minor difference: the method signatures of the overridden lifecycle methods and event handlers take an argument of type LifecycleContext instead of DataActionContext. So, for example, the DynamicCriteriaPageHandler class looks like this:

package test;
// other imports here
import oracle.adf.controller.lifecycle.LifecycleContext;
import oracle.adf.controller.lifecycle.PageLifecycle;
public class DynamicCriteriaPageHandler extends PageLifecycle {
  /*
   * Handle the "Search" event.
   */
  public void onSearch(LifecycleContext ctx) {
   // Otherwise exactly the same code as the DynamicCriteriaAction
   // from the previous second.
  }
}

Another small difference you can observe in the JSP pages with data-bound fields in the ViewNoController project is how these HTML controls are named. In the ADF/Struts environment, recall that ADF provides a multi-purpose BindingContainerActionForm, which is a Struts DynaActionForm that exposes the bindings in the current binding container as ActionForm dynaproperties. With Struts and this action form in the picture, data-bound fields are named exactly the same as the bindings. For example, a data-bound text field showing the value of the Deptno binding is realized with the tag:

<html:text property="Deptno"/>

With no Struts in the picture, it's obvious that there won't be a Struts ActionForm involved in handling the request, and therefore neither will we use the Struts HTML tag library, which depends on having an ActionForm. Outside the Struts environment, the ADF PageLifecycle uses a slightly different approach to recognize the names of bound fields in submitted web forms. Each binding supports a path property whose value represents the "path name" that ADF will use to recognize a submitted value for that binding. Accordingly, a data bound text field looks like this in a Model-One style JSP page:

<input name="<c:out value='${bindings.Deptno.path}'/>"
       value="<c:out value='${bindings.Deptno}'/>">

Other than these minor differences, you can see by running any of the JSP pages in the ViewNoController project that they offer the same functionality as their Struts-powered "twins" in the ViewController project.

Conclusion

In this article we've explored implementing simple search pages using both a fixed as well as a dynamic set of search criteria attributes. We learned about iterator bindings and find mode simplify the process, and how the concepts and implementation are virtually identical in both Model One and Model Two web development. Hopefully after going through this example, you'll find your next search page easier to tackle.

false ,,,,,,,,,,,,,,,