39 Advanced View Object Techniques

This chapter describes advanced techniques you can use while designing and working with your view objects.

This chapter includes the following sections:

39.1 Advanced View Object Concepts and Features

This section describes a number of interesting view object concepts and features that have not been discussed in previous chapters.

Note:

To experiment with the examples in this chapter, use the AdvancedViewObjectsExamples workspace in the StandaloneExamples module of the Fusion Order Demo application, as described in Section 2.4.5, "Standalone Applications in the AdvancedViewObjectExamples Application Workspace." For information about how to obtain and install the Fusion Order Demo, see Section 2.2, "Setting Up the Fusion Order Demo Application."

39.1.1 Limiting the View Object Max Fetch Size to Fetch the First n Rows

The default maximum fetch size of a view object is minus one (-1), which indicates there should be no limit to the number of rows that can be fetched. Keep in mind that by default, rows are fetched as needed, so -1 does not imply a view object will necessarily fetch all the rows. It simply means that if you attempt to iterate through all the rows in the query result, you will get them all.

However, you might want to put an upper bound on the maximum number of rows that a view object will retrieve. You can use the following settings:

  • You can configure a global threshold for all view objects queries using the Row Fetch Limit property on the Business Components page of the overview editor for the adf-config.xml file. You can locate the file in the Application Resources pane by expanding the Descriptors-ADF META-INF folder.

    Note: Since Row Fetch Limit specifies a global threshold for all query operations in the application (including iterator binding property RowCountThreshold used to determine an estimated row count for the iterator result set), using this property means you can avoid changing settings for individual query operations where that operation's default behavior allows all rows to be fetched. If you do specify a fetch limit for individual view objects, the Row Fetch Limit setting will be ignored in those cases.

  • You can configure a local threshold for specific view object queries using the Max Fetch Size with the Only up to row number field selected in the Tuning section of the General page of the overview editor for the view object.

Tip:

If you want to set the global threshold for query operations using Row Fetch Limit and you still need to allow specific view object queries to return all available rows, then you can set the Max Fetch Size with the Only up to row number field for those view objects to a very large number.

For example, if you write a query containing an ORDER BY clause and only want to return the first n rows to display the "Top-N" entries in a page, you can use the overview editor for the view object to specify a value for the Only up to row number field in the Tuning section of the General page. For example, to fetch only the first five rows, you would enter "5" in this field. This is equivalent to calling the setMaxFetchSize() method on your view object to set the maximum fetch size to 5. The view object will stop fetching rows when it hits the maximum fetch size. Often you will combine this technique with specifying a Query Optimizer Hint of FIRST_ROWS also on the Tuning section of the General page of the overview editor. This gives a hint to the database that you want to retrieve the first rows as quickly as possible, rather than trying to optimize the retrieval of all rows.

39.1.2 Maintaining New Row Consistency in View Objects Based on the Same Entity

When multiple instances of entity-based view objects in an application module are based on the same underlying entity object, a new row created in one of them can be automatically added (without having to re-query) to the row sets of the others to keep your user interface consistent or simply to consistently reflect new rows in different application pages for a pending transaction. Consider the Fusion Order Demo application's orderSummary.jspx page that displays a customers list of orders. If the customer goes to create a new order, this task is performed through a different view object and handled by a custom application module method. Using this view object new row consistency feature, the newly created order automatically appears in the customer's list of open orders on the orderSummary.jspx page without having to re-query the database.

For historical reasons, this capability is known as the view link consistency feature because in prior releases of Oracle Application Development Framework (Oracle ADF) the addition of new rows to other relevant row sets only was supported for detail view object instances in a view link based on an association. Now this view link consistency feature works for any view objects for which it is enabled, regardless of whether they are involved in a view link or not.

39.1.2.1 What Happens at Runtime When View Link Consistency is Enabled

Consider two entity-based view objects OrdersViewSummary and OrdersView both based on the same underlying Orders entity object. When a new row is created in a row set for one of these view objects (like OrdersView) and the row's primary key is set, any of the other row sets for view objects based on the same Orders entity object (like OrdersViewSummary) receive an event indicating a new row has been created. If their view link consistency flag is enabled, then a copy of the new row is inserted into their row set as well.

39.1.2.2 How to Change the Default View Link Consistency Setting

You can use the Edit Business Components Configuration dialog to control the default setting for the view link consistency feature using the jbo.viewlink.consistent configuration parameter, as shown in Figure 39-1.

Figure 39-1 jbo.viewlink.consistent Property Setting

jbo.viewlink.consistent property in Configuration Editor

To display the configuration editor, right-click the application module in the Application Navigator and choose Configurations. Then, in the Manage Configurations dialog, select the configuration and click Edit. In the Edit Business Components Configuration dialog, select the Properties tab. The default setting for this parameter is the word "DEFAULT" which has the following meaning. If your view object has:

  • A single entity usage, view link consistency is enabled

  • Multiple entity usages, and:

    • If all secondary entity usages are marked as contributing reference information, then view link consistency is enabled

    • If any secondary entity usage marked as not being a reference view link consistency is disabled.

You can globally disable this feature by setting the jbo.viewlink.consistent to the value false in your configuration. Conversely, you could globally enable this feature by setting jbo.viewlink.consistent to the value true, but Oracle does not recommend doing this. Doing so would force view link consistency to be set on for view objects with secondary entity usages that are not marked as a reference which presently do not support the view link consistency feature well.

To set the feature programmatically, use the setAssociationConsistent() API on any RowSet. When you call this method on a view object, it affects its default row set.

39.1.2.3 How to Use a RowMatch to Qualify Which New, Unposted Rows Get Added to a Row Set

If a view object has view link consistency enabled, any new row created by another view object based on the same entity object is added to its row set. By default the mechanism adds new rows in an unqualified way. If your view object has a design-time WHERE clause that queries only a certain subset of rows, you can apply a RowMatch object to your view object to perform the same filtering in-memory. The filtering expression of the RowMatch object you specify prevents new rows from being added that wouldn't make sense to appear in that view object.

For example, an OrdersByStatus view object might include a design time WHERE clause like this:

WHERE /* ... */ AND STATUS LIKE NVL(:StatusCode,'%') 

Its custom Java class overrides the create() method as shown in Example 39-1 to force view link consistency to be enabled. It also applies a RowMatch object whose filtering expression matches rows whose Status attribute matches the value of the :StatusCode named bind variable (or matches any row if :StatusCode = '%'). This RowMatch filter is used by the view link consistency mechanism to qualify the row that is a candidate to add to the row set. If the row qualifies by the RowMatch, it is added. Otherwise, it is not.

Example 39-1 Providing a Custom RowMatch to Control Which New Rows are Added

// In OrdersByStatusImpl.java
protected void create() {
  super.create();
  setAssociationConsistent(true);
  setRowMatch(new RowMatch("Status = :StatusCode or :StatusCode = '%'"));  
} 

See Section 39.5.4, "Performing In-Memory Filtering with RowMatch" for more information on creating and using a RowMatch object. For a list of supported SQL operators see Table 39-2. For a list of supported SQL function, see Table 39-3.

Note:

If the RowMatch facility does not provide enough control, you can override the view object's rowQualifies() method to implement a custom filtering solution. Your code can determine whether a new row qualifies to be added by the view link consistency mechanism or not.

39.1.2.4 What You May Need to Know About the Dynamic WHERE Clause and View Link Consistency

If you call setWhereClause() on a view object to set a dynamic WHERE clause, the view link consistency feature is disabled on that view object. If you have provided an appropriate custom RowMatch object to qualify new rows for adding to the row set, you can call setAssociationConsistent(true) after setWhereClause() to reenable view link consistency.

If a row set has view link consistency enabled, then new rows added due to creation by other row sets are added to the bottom of the row set.

If a row set has view link consistency enabled, then when you call the executeQuery() method, any qualifying new, unposted rows are added to the top of the row set before the queried rows from the database are added.

39.1.3 Understanding View Link Accessors Versus Data Model View Link Instances

View objects support two different styles of master-detail coordination:

39.1.3.1 Enabling a Dynamic Detail Row Set with Active Master-Detail Coordination

When you add a view link instance to your application module's data model, you connect two specific view object instances. The use of the view link instance indicates that you want active master-detail coordination between the two. At runtime the view link instance in the data model facilitates the eventing that enables this coordination. Whenever the current row is changed on the master view object instance, an event causes the detail view object to be refreshed by automatically invoking executeQuery() with a new set of bind parameters for the new current row in the master view object.

A key feature of this data model master-detail is that the master and detail view object instances are stable objects to which client user interfaces can establish bindings. When the current row changes in the master — instead of producing a new detail view object instance — the existing detail view object instance updates its default row set to contain the set of rows related to the new current master row. In addition, the user interface binding objects receive events that allow the display to update to show the detail view object's refreshed row set.

Another key feature that is exclusive to data model hierarchy is that a detail view object instance can have multiple master view object instances. For example, an PaymentOptions view object instance may be a detail of both a Customers and a Orders view object instance. Whenever the current row in either the Customers or Orders view object instance changes, the default row set of the detail PaymentOptions view object instance is refreshed to include the row of payment information for the current customer and the current order. See Section 39.1.6, "Setting Up a Data Model with Multiple Masters" for details on setting up a detail view object instance with multiple-masters.

39.1.3.2 Accessing a Stable Detail Row Set Using View Link Accessor Attributes

When you need to programmatically access the detail row set related to a view object row by virtue of a view link, you can use the view link accessor attribute. You specify the finder name of the view link accessor attribute from the overview editor for the view link. Click the Edit icon in the Accessors section of the Relationship page and in the Edit View Link Properties dialog, edit the name of the view link accessor attribute.

Example 39-2 shows the XML for the view link that defines the _findername value of the <Attr> element.

Example 39-2 View Link Accessor Attribute Name

<ViewLinkDefEnd
    Name="Orders"
    Cardinality="1"
    Owner="devguide.advanced.multiplemasters.Orders"
    Source="true">
    <AttrArray Name="Attributes">
      <Item Value="devguide.advanced.multiplemasters.Orders.PaymentOptionId"/>
    </AttrArray>
    <DesignTime>
      <Attr Name="_minCardinality" Value="1"/>
      <Attr Name="_isUpdateable" Value="true"/>
      <Attr Name="_finderName" Value="Orders"/>
    </DesignTime>
</ViewLinkDefEnd>

Assuming you've named your accessor attribute AccessorAttrName, you can access the detail row set using the generic getAttribute() API like:

RowSet detail = (RowSet)currentRow.getAttribute("AccessorAttrName");

If you've generated a custom view row class for the master view object and exposed the getter method for the view link accessor attribute on the client view row interface, you can write strongly-typed code to access the detail row set like this:

RowSet detail = (RowSet)currentRow.getAccessorAttrName();

Unlike the data model master-detail, programmatic access of view link accessor attributes does not require a detail view object instance in the application module's data model. Each time you invoke the view link accessor attribute, it returns a RowSet containing the set of detail rows related to the master row on which you invoke it.

Using the view link accessor attribute, the detail data rows are stable. As long as the attribute value(s) involved in the view link definition in the master row remain unchanged, the detail data rows will not change. Changing of the current row in the master does not affect the detail row set which is "attached" to a given master row. For this reason, in addition to being useful for general programmatic access of detail rows, view link accessor attributes are appropriate for UI objects like the tree control, where data for each master node in a tree needs to retain its distinct set of detail rows.

39.1.3.3 Accessor Attributes Create Distinct Row Sets Based on an Internal View Object

When you combine the use of data model master-detail with programmatic access of detail row sets using view link accessor, it is even more important to understand that they are distinct mechanisms. For example, imagine that you:

  • Define PersonsVO and OrdersVO view objects

  • Define a view link between them, naming the view link accessor PersonsToOrders

  • Add instances of them to an application module's data model named master (of type PersonsVO) and detail (of type OrdersVO) coordinated actively by a view link instance.

If you find a person in the master view object instance, the detail view object instance updates as expected to show the corresponding orders. At this point, if you invoke a custom method that programmatically accesses the PersonsToOrders view link accessor attribute of the current PersonsVO row, you get a RowSet containing the set of OrdersVO rows. You might reasonably expect this programmatically accessed RowSet to have come from the detail view object instance in the data model, but this is not the case.

The RowSet returned by a view link accessor always originates from an internally created view object instance, not one you that added to the data model. This internal view object instance is created as needed and added with a system-defined name to the root application module.

The principal reason a distinct, internally-created view object instance is used is to guarantee that it remains unaffected by developer-related changes to their own view objects instances in the data model. For example, if the view row were to use the detail view object in the data model for view link accessor RowSet, the resulting row set could be inadvertently affected when the developer dynamically:

  1. Adds a WHERE clause with new named bind parameters

    If such a view object instance were used for the view link accessor result, unexpected results or an error could ensue because the dynamically-added WHERE clause bind parameter values have not been supplied for the view link accessor's RowSet: they were only supplied for the default row set of the detail view object instance in the data model.

  2. Adds an additional master view object instance for the detail view object instance in the data model.

    In this scenario, the semantics of the accessor would be changed. Instead of the accessor returning OrdersVO rows for the current PersonsVO row, it could all of a sudden start returning only the OrdersVO rows for the current PersonsVO that were created by a current logged in customer, for example.

  3. Removes the detail view object instance or its containing application module instance.

    In this scenario, all rows in the programmatically-accessed detail RowSet would become invalid.

Furthermore, Oracle ADF needs to distinguish between the data model master-detail and view link accessor row sets for certain operations. For example, when you create a new row in a detail view object, the framework automatically populates the attributes involved in the view link with corresponding values of the master. In the data model master-detail case, it gets these values from the current row(s) of the possibly multiple master view object instances in the data model. In the case of creating a new row in a RowSet returned by a view link accessor, it populates these values from the master row on which the accessor was called.

39.1.4 Presenting and Scrolling Data a Page at a Time Using the Range

To present and scroll through data a page at a time, you can configure a view object to manage for you an appropriately-sized range of rows. The range facility allows a client to easily display and update a subset of the rows in a row set, as well as easily scroll to subsequent pages, n rows as a time. You call setRangeSize() to define how many rows of data should appear on each page. The default range size is one (1) row. A range size of minus one (-1) indicates the range should include all rows in the row set.

Note:

When using the ADF Model layer's declarative data binding, the iterator binding in the page definition has a RangeSize property. At runtime, the iterator binding invokes the setRangeSize() method on its corresponding row set iterator, passing the value of this RangeSize property. The ADF design time by default sets this RangeSize property to 10 rows for most iterator bindings. An exception is the range size specified for a List binding to supply the set of valid values for a UI component like a dropdown list. In this case, the default range size is minus one (-1) to allow the range to include all rows in the row set.

When you set a range size greater than one, you control the row set paging behavior using the iterator mode. The two iterator mode flags you can pass to the setIterMode() method are:

  • RowIterator.ITER_MODE_LAST_PAGE_PARTIAL

    In this mode, the last page of rows may contain fewer rows than the range size. For example, if you set the range size to 10 and your row set contains 23 rows, the third page of rows will contain only three rows. This is the style that works best for Fusion web applications.

  • RowIterator.ITER_MODE_LAST_PAGE_FULL

    In this mode, the last page of rows is kept full, possibly including rows at the top of the page that had appeared at the bottom of the previous page. For example, if you set the range size to 10 and your row set contains 23 rows, the third page of rows will contain 10 rows, the first seven of which appeared as the last seven rows of page two. This is the style that works best for desktop-fidelity applications using Swing.

39.1.5 Efficiently Scrolling Through Large Result Sets Using Range Paging

As a general rule, for highest performance, Oracle recommends building your application in a way that avoids giving the end user the opportunity to scroll through very large query results. To enforce this recommendation, call the getEstimatedRowCount() method on a view object to determine how many rows would be returned by the user's query before actually executing the query and allowing the user to proceed. If the estimated row count is unreasonably large, your application can demand that the end-user provide additional search criteria.

However, when you must work with very large result sets, typically over 100 rows, you can use the view object's access mode called "range paging" to improve performance. The feature allows your applications to page back and forth through data, a range of rows at a time, in a way that is more efficient for large data sets than the default "scrollable" access mode.

The range paging access mode is typically used for paging through read-only row sets, and often is used with read-only view objects. You allow the user to find the row they are looking for by paging through a large row set with range paging access mode, then you use the Key of that row to find the selected row in a different view object for editing.

Range paging for view objects supports a standard access mode and a variation of the standard access mode that combines the benefits of range paging and result set scrolling with a minimum number of visits to the database. These modes for the view object range paging feature include:

  • RANGE_PAGING, standard access mode fetches the number of rows specified by a range size. In this mode, the number of rows that may be scrolled without requerying the database is determined by a range size that you set. The default is to fetch a single row, but it is expected that you will set a range size equal to the number of rows you want to be able to display to the user before they scroll to the next result set. The application requeries the database each time a row outside of the range is accessed by the end user. Thus, scrolling backward and forward through the row set will requery the database. For clarification about this database-centric paging strategy, see Section 39.1.5.1, "Understanding How to Oracle Supports "TOP-N" Queries."

  • RANGE_PAGING_INCR, incremental access mode gives the UI designer more flexibility for the number of rows to display at a time while keeping database queries to a minimum. In this mode, the UI incrementally displays the result set from the memory cache and thus supports scrolling within a single database query. The number of rows that the end user can scroll though in a single query is determined by the range size and a range paging cache factor that you set. For example, suppose that you set the range size to 4 and the cache factor to 5. Then, the maximum number of rows to cache in memory will be 4*5 = 20. For further explanation of the caching behavior, see Section 39.1.5.4, "What Happens When View Rows are Cached When Using Range Paging."

Caution:

Additionally, the view object supports a RANGE_PAGING_AUTO_POST access mode to accommodate the inserting and deleting of rows from the row set. This mode behaves like the RANGE_PAGING mode, except that it eagerly calls postChanges() on the database transaction whenever any changes are made to the row set. However, this mode is typically not appropriate for use in Fusion web applications unless you can guarantee that the transaction will definitely be committed or rolled-back during the same HTTP request. Failure to heed this advice can lead to strange results in an environment where both application modules and database connections can be pooled and shared serially by multiple different clients.

39.1.5.1 Understanding How to Oracle Supports "TOP-N" Queries

The Oracle database supports a feature called a "Top-N" query to efficiently return the first n ordered rows in a query. For example, if you have a query like:

SELECT EMPNO, ENAME,SAL FROM EMP ORDER BY SAL DESC

If you want to retrieve the top 5 employees by salary, you can write a query like:

SELECT * FROM (
   SELECT X.*,ROWNUM AS RN FROM (
      SELECT EMPNO,ENAME,SAL FROM EMP ORDER BY SAL DESC 
   ) X
) WHERE RN <= 5

which gives you results like:

     EMPNO ENAME       SAL   RN
---------- -------- ------ ----
      7839 KING       5000    1
      7788 SCOTT      3000    2
      7902 FORD       3000    3
      7566 JONES      2975    4
      7698 BLAKE      2850    5

The feature is not only limited to retrieving the first n rows in order. By adjusting the criteria in the outermost WHERE clause you can efficiently retrieve any range of rows in the query's sorted order. For example, to retrieve rows 6 through 10 you could alter the query this way:

SELECT * FROM (
  SELECT X.*,ROWNUM AS RN FROM  (
     SELECT EMPNO,ENAME,SAL FROM EMP ORDER BY SAL DESC
  ) X
) WHERE RN BETWEEN 6 AND 10

Generalizing this idea, if you want to see page number P of the query results, where each page contains R rows, then you would write a query like:

SELECT * FROM (
  SELECT X.*,ROWNUM AS RN FROM (
    SELECT EMPNO,ENAME,SAL FROM EMP ORDER BY SAL DESC
  ) X
) WHERE RN BETWEEN ((:P - 1) * :R) + 1 AND (:P) * :R

As the result set you consider grows larger and larger, it becomes more and more efficient to use this technique to page through the rows. Rather than retrieving hundreds or thousands of rows over the network from the database, only to display ten of them on the page, instead you can produce a clever query to retrieve only the R rows on page number P from the database. No more than a handful of rows at a time needs to be returned over the network when you adopt this strategy.

To implement this database-centric paging strategy in your application, you could handcraft the clever query yourself and write code to manage the appropriate values of the :R and :P bind variables. Alternatively, you can use the view object's range paging access mode, which implements it automatically for you.

39.1.5.2 How to Enable Range Paging for a View Object

You can use the Tuning panel of the overview editor for the view object to set the access mode to either standard range paging or incremental range paging. The Range Paging Cache Factor field is only editable when you select Range Paging Incremental. Figure 39-2 shows the view object's Access Mode set to Range Paging (standard mode) with the default range size of 1. To understand the row set caching behavior of both access modes, see Section 39.1.5.4, "What Happens When View Rows are Cached When Using Range Paging."

Figure 39-2 Access Mode in the Overview Editor for the View Object

Access Mode selection in view object editor

To programmatically enable standard range paging for your view object, first call setRangeSize() to define the number of rows per page, then call the following method with the desired mode:

yourViewObject.setAccessMode(RowSet.RANGE_PAGING | RANGE_PAGING_INCR);

If you set RANGE_PAGING_INCR, then you must also call the following method to set the cache factor for your defined range size:

yourViewObject.setRangePagingCacheFactor(int f);

39.1.5.3 What Happens When You Enable Range Paging

When a view object's access mode is set to RANGE_PAGING, the view object takes its default query like:

SELECT EMPNO, ENAME, SAL FROM EMP ORDER BY SAL DESC

and automatically "wraps" it to produce a Top-N query.

For best performance, the statement uses a combination of greater than and less than conditions instead of the BETWEEN operator, but the logical outcome is the same as the Top-N wrapping query you saw above. The actual query produced to wrap a base query of:

SELECT EMPNO, ENAME, SAL FROM EMP ORDER BY SAL DESC

looks like this:

SELECT * FROM (
  SELECT /*+ FIRST_ROWS */ IQ.*, ROWNUM AS Z_R_N FROM (
    SELECT EMPNO, ENAME, SAL FROM EMP ORDER BY SAL DESC
  ) IQ  WHERE ROWNUM < :0)
WHERE Z_R_N > :1

The two bind variables are bound as follows:

  • :1 index of the first row in the current page

  • :0 is bound to the last row in the current page

39.1.5.4 What Happens When View Rows are Cached When Using Range Paging

When a view object operates in RANGE_PAGING access mode, it only keeps the current range (or "page") of rows in memory in the view row cache at a time. That is, if you are paging through results ten at a time, then on the first page, you'll have rows 1 through 10 in the view row cache. When you navigate to page two, you'll have rows 11 through 20 in the cache. This also can help make sure for large row sets that you don't end up with tons of rows cached just because you want to preserve the ability to scroll backwards and forwards.

When a view object operates in RANGE_PAGING_INCR access mode, the cache factor determines the number of rows to cache in memory for a specific range size. For example, suppose the range size is set to 4 and cache factor to 5. Then, the memory will keep at most 4*5 = 20 rows in its collection. In this example, when the range is refreshed for the first time, the memory will have just four rows even though the range paging query is bound to retrieve rows 0 to 19 (for a total of twenty rows). When the range is scrolled past the forth row, more rows will be read in from the current result set. This will continue until all twenty rows from the query result are read. If the user's action causes the next set of rows to be retrieve, the query will be re-executed with the new row number bind values. The exact row number bind values are determined by the new range-start and the number of rows that can be retained from the cache. For example, suppose all twenty rows have been filled up and the user asks to move the range-start to 18 (0-based). This means that memory can retain row 18 and row 19 and will need two more rows to fill the range. The query is re-executed for rows 20 and 21.

39.1.5.5 How to Scroll to a Given Page Number Using Range Paging

When a view object operates in RANGE_PAGING access mode, to scroll to page number n call its scrollToRangePage() method, passing n as the parameter value.

39.1.5.6 Estimating the Number of Pages in the Row Set Using Range Paging

When a view object operates in RANGE_PAGING access mode, you can access an estimate of the total number of pages the entire query result would produce using the getEstimatedRangePageCount() method.

39.1.5.7 Understanding the Tradeoffs of Using a Range Paging Mode

You might ask yourself, "Why wouldn't I always want to use RANGE_PAGING or RANGE_PAGING_INCR mode?" The answer is that using range paging potentially causes more overall queries to be executed as you are navigating forward and backward through your view object rows. You would want to avoid using RANGE_PAGING mode in these situations:

  • You plan to read all the rows in the row set immediately (for example, to populate a dropdown list).

    In this case your range size would be set to -1 and there really is only a single "page" of all rows, so range paging does not add value.

  • You need to page back and forth through a small-sized row set.

    If you have 100 rows or fewer, and are paging through them 10 at a time, with RANGE_PAGING mode you will execute a query each time you go forward and backward to a new page. Otherwise, in the default scrollable mode, you will cache the view object rows as you read them in, and paging backwards through the previous pages will not re-execute queries to show those already-seen rows. Alternatively, you can use RANGE_PAGING_INCR mode to allow scrolling through in-memory results based on a row set cache factor that you determine.

In the case of a very large (or unpredictably large) row set, the trade off of potentially doing a few more queries — each of which only returns up to the RangeSize number of rows from the database — is more efficient then trying to cache all of the previously-viewed rows. This is especially true if you allow the user to jump to an arbitrary page in the list of results. Doing so in default, scrollable mode requires fetching and caching all of the rows between the current page and the page the users jumps to. In RANGE_PAGING mode, it will ask the database just for the rows on that page. Then, if the user jumps back to a page of rows that they have already visited, in RANGE_PAGING mode, those rows get re-queried again since only the current page of rows is held in memory in this mode. The incremental range paging access mode RANGE_PAGING_INCR combines aspects of both standard range paging and scrollable access mode since it allows the application to cache more rows in memory and permits the user to jump to any combination of those rows without needing to requery.

39.1.6 Setting Up a Data Model with Multiple Masters

When useful, you can set up your data model to have multiple master view object instances for the same detail view object instance. Consider view objects named Customers, Orders, and PaymentOptions with view links defined between:

  • Customers and PaymentOptions

  • Orders and PaymentOptions

Note:

The examples in this section are not based on the Fusion Order Demo application. They currently refer to the MultipleMasters project in the AdvancedViewObjectExamples workspace, available as noted at the beginning of this chapter for download.

Figure 39-3 shows what the data model panel looks like when you've configured both Customers and Orders view object instances to be masters of the same PaymentOptions view object instance.

Figure 39-3 Multiple Master View Object Instances for the Same Detail

Data Model panel

To set up the data model as shown in Figure 39-3 open the overview editor for the application module and follow these steps in the Data Model Components section of the Data Model page:

  1. Add an instance of the Customers view object to the data model.

    Assume you name it Customers.

  2. Add an instance of the Orders view object to the data model

    Assume you name it Orders.

  3. Select the Customers view object instance in the Data Model list

  4. In the Available View Objects list, select the PaymentOptions view object indented beneath the Customers view object and enter the view object instance name of PaymentOptions in the New Instance Name field. Click > to shuttle it into data model as a detail of the existing Customers view object instance.

  5. Select the Orders view object instance in the Data Model list

  6. In the Available View Objects list, select the PaymentOptions view object indented beneath the Orders view object and enter the view object instance name of PaymentOptions in the New Instance Name field. Click > to shuttle it into data model as a detail of the existing Orders view object instance.

    An alert will appear: An instance of a View Object with the name PaymentOptions has already been used in the data model. Would you like to use the same instance?

  7. Click Yes to confirm you want the PaymentOptions view object instance to also be the detail of the Orders view object instance.

39.1.7 Understanding When You Can Use Partial Keys with findByKey()

View objects based on multiple entity usages support the ability to find view rows by specifying a partially populated key. A partial key is a multi-attribute Key object with some of its attributes set to null. However, there are strict rules about what kinds of partial keys can be used to perform the findByKey().

If a view object is based on n entity usages, where n > 1, then the view row key is by default comprised of all of the primary key attributes from all of the participating entity usages. Only the ones from the first entity object are required to participate in the view row key, but by default all of them do.

If you allow the key attributes from some secondary entity usages to remain as key attributes at the view row level, then you should leave all of the attributes that form the primary key for that entity object as part of the view row key. Assuming you have left the one or more key attributes in the view row for m of the n entity usages, where (m <= n), then you can use findByKey() to find rows based on any subset of these m entity usages. Each entity usage for which you provide values in the Key object, requires that you must provide non-null values for all of the attributes in that entity's primary key.

You have to follow this rule because when a view object is based on at least one or more entity usages, its findByKey() method finds rows by delegating to the findByPrimaryKey() method on the entity definition corresponding to the first entity usage whose attributes in the view row key are non-null. The entity definition's findByPrimaryKey() method requires all key attributes for any given entity object to be non-null in order to find the entity row in the cache.

As a concrete example, imagine that you have a OrderInfoVO view object with a OrderEO entity object as its primary entity usage, and an AddressEO entity as secondary reference entity usage. Furthermore, assume that you leave the Key Attribute property of both of the following view row attributes set to true:

  • OrderId — primary key for the OrderEO entity

  • AddressId — primary key for the AddressEO entity

The view row key will therefore be the (OrderId, AddressId) combination. When you do a findByKey(), you can provide a Key object that provides:

  • A completely specified key for the underlying OrderEO entity

    Key k = new Key(new Object[]{new Number(200), null});
    
  • A completely specified key for the underlying AddressEO entity

    Key k = new Key(new Object[]{null, new Number(118)}); 
    
  • A completely specified key for both entities

    Key k = new Key(new Object[]{new Number(200), new Number(118)});
    

When a valid partial key is specified, the findByKey() method can return multiple rows as a result, treating the missing entity usage attributes in the Key object as a wildcard.

39.1.8 Creating Dynamic Attributes to Store UI State

You can add one or more dynamic attributes to a view object at runtime using the addDynamicAttribute() method. Dynamic attributes can hold any serializable object as their value. Typically, you will consider using dynamic attributes when writing generic framework extension code that requires storing some additional per-row transient state to implement a feature you want to add to the framework in a global, generic way.

39.1.9 Working with Multiple Row Sets and Row Set Iterators

While you typically work with a view object's default row set, you can call the createRowSet() method on the ViewObject interface to create secondary, named row sets based on the same view object's query. One situation where this could make sense is when your view object's SQL query contains named bind variables. Since each RowSet object stores its own copy of bind variable values, you could use a single view object to produce and process multiple row sets based on different combinations of bind variable values. You can find a named row set you've created using the findRowSet() method. When you're done using a secondary row set, call its closeRowSet() method.

For any RowSet, while you typically work with its default row set iterator, you can call the createRowSetIterator() method of the RowSet interface to create secondary, named row set iterators. You can find a named row set iterator you've created using the findRowSetIterator() method. When you're done using a secondary row set iterator, call its closeRowSetIterator() method.

Performance Tip:

When you need to perform programmatic iteration over a result set, create a secondary iterator to avoid disturbing the current row of the default row set iterator. For example, through the ADF Model declarative data binding layer, user interface pages in your application work with the default row set iterator of the default row set of view objects in the application module's data model. In this scenario, if you did not create a secondary row set iterator for the business logic you write to iterate over a view object's default row set, you would consequently change the current row of the default row set iterator used by the user interface layer.

39.1.10 Optimizing View Link Accessor Access By Retaining the Row Set

Each time you retrieve a view link accessor row set, by default the view object creates a new RowSet object to allow you to work with the rows. This does not imply re-executing the query to produce the results each time, only creating a new instance of a RowSet object with its default iterator reset to the "slot" before the first row. To force the row set to refresh its rows from the database, you can call its executeQuery() method.

You can enable caching of the view link accessor row set when you do not want the application to incur the small amount of overhead associated with creating new detail row sets. For example, because view accessor row sets remain stable as long as the master row view accessor attribute remains unchanged, it would not be necessary to recreate a new row set for UI components, like the tree control, where data for each master node in a tree needs to retain its distinct set of detail rows. The view link accessor's detail row set can also be accessed programmatically. In this case, if your application makes numerous calls to the same view link accessor attributes, you can consider caching the view link accessor row set. This style of managing master-detail coordination differs from creating view link instances in the data model, as explained in Section 39.1.3, "Understanding View Link Accessors Versus Data Model View Link Instances."

You can enable retention of the view link accessor row set using the overview editor for the view object that is the source for the view link accessor. Select Retain View Link Accessor Row Set in the Tuning section of the General page of the overview editor for the view object.

Alternatively, you can enable a custom Java class for your view object, override the create() method, and add a line after super.create() that calls the setViewLinkAccessorRetained() method passing true as the parameter. It affects all view link accessor attributes for that view object.

When this feature is enabled for a view object, since the view link accessor row set is not recreated each time, the current row of its default row set iterator is also retained as a side-effect. This means that your code will need to explicitly call the reset() method on the row set you retrieve from the view link accessor to reset the current row in its default row set iterator back to the "slot" before the first row.

Note, however, that with accessor retention enabled, your failure to call reset() each time before you iterate through the rows in the accessor row set can result in a subtle, hard-to-detect error in your application. For example, if you iterate over the rows in a view link accessor row set like this, for example to calculate some aggregate total:

RowSet rs = (RowSet)row.getAttribute("OrdersShippedToPurchaser");
while (rs.hasNext()) {
  Row r = rs.next();
  // Do something important with attributes in each row
}

The first time you work with the accessor row set the code will work. However, since the row set (and its default row set iterator) are retained, the second and subsequent times you access the row set the current row will already be at the end of the row set and the while loop will be skipped since rs.hasNext() will be false. Instead, with this feature enabled, write your accessor iteration code like this:

RowSet rs = (RowSet)row.getAttribute("OrdersShippedToPurchaser");
rs.reset(); // Reset default row set iterator to slot before first row!
while (rs.hasNext()) {
  Row r = rs.next();
  // Do something important with attributes in each row
}

Recall that if view link consistency is on, when the accessor is retained the new unposted rows will show up at the end of the row set. This is slightly different from when the accessor is not retained (the default), where new unposted rows will appear at the beginning of the accessor row set.

39.2 Tuning Your View Objects for Best Performance

You can use view objects to read rows of data, create and store rows of transient data, as well as automatically coordinate inserts, updates, and deletes made by end users with your underlying business objects. How you design and use your view objects can definitely affect their performance at runtime. This section provides guidance on configuring your view objects to get the best possible performance.

39.2.1 Use Bind Variables for Parameterized Queries

Whenever the WHERE clause of your query includes values that might change from execution to execution, you should use named bind variables. The Create View Criteria dialog that you display from the Query page of the view object overview editor makes this an easy task. Their use also protects your application against abuse through SQL injection attacks by malicious end-users. For information about defining view criteria with bind variables, see Section 5.11.1, "How to Create Named View Criteria Declaratively."

39.2.1.1 Use Bind Variables to Avoid Re-parsing of Queries

Bind variables are place holders in the SQL string whose value you can easily change at runtime without altering the text of the SQL string itself. Since the query text doesn't change from execution to execution, the database can efficiently reuse the same parsed statement each time. Avoiding re-parsing of your statement alleviates the database from having to continually re-determine its query optimization plan and eliminates contention by multiple end-users on other costly database resources used during this parsing operation. This savings leads to higher runtime performance of your application. See Section 5.10.1, "How to Add Bind Variables to a View Object Definition" for details on how to use named bind variables.

39.2.1.2 Use Bind Variables to Prevent SQL-Injection Attacks

Using bind variables for parameterized WHERE clause values is especially important if their values will be supplied by end-users of your application. Consider the example shown in Example 39-3. It adds a dynamic WHERE clause formed by concatenating a user-supplied parameter value into the statement.

Example 39-3 Using String Concatenation Instead of Bind Variables is Vulnerable to SQL-Injection Attacks

// EXAMPLE OF BAD PRACTICE, Do not follow this approach!
String userSuppliedValue = ... ;
yourViewObject.setWhereClause("BANK_ACCOUNT_ID = "+userSuppliedValue);

A user with malicious intentions — if able to learn any details about your application's underlying database schema — could supply a carefully-constructed "bank account number" as a field value or URL parameter like:

BANK_ACCOUNT_ID

When the code in Example 39-3 concatenates this value into the dynamically-applied where clause, what the database sees is a query predicate like this:

WHERE (BANK_ACCOUNT_ID = BANK_ACCOUNT_ID)

This WHERE clause retrieves all bank accounts instead of just the current user's, perhaps allowing the hacker to view private information of another person's account. This technique of short-circuiting an application's WHERE clause by trying to supply a maliciously-constructed parameter value into a SQL statement is called a SQL injection attack. Using named bind variables instead for these situations as shown in Example 39-4 prevents the vulnerability.

Example 39-4 Use Named Bind Variables Instead of String Concatenation

// Best practice using named bind variables
String userSuppliedValue = ... ;
yourViewObject.setWhereClause("BANK_ACCOUNT_ID = :BankAcccountId");
yourViewObject.defineNamedWhereClauseParam("BankAcccountId", null, null);
yourViewObject.setNamedWhereClauseParam("BankAcccountId",userSuppliedValue);

If a malicious user supplies an illegal value in this case, they receive an error your application can handle instead of obtaining data they are not suppose to see.

39.2.2 Consider Using Entity-Based View Objects for Read-Only Data

Typically view objects used for SQL-based validation purposes, as well as for displaying the list of valid selections in a dropdown list, can be read-only. You need to decide what kind of functionality your application requires and design the view object accordingly.

Best Practice:

When you need to create a read-only view object for data lookup, you should use the entity-based view object and deselect the Updatable option in the Entity Objects page of the view object overview editor. The approach benefits from the design time editors which aid in generating the SQL query. The alternative of creating an expert-mode view object requires writing a SQL query. Expert mode queries are still useful for cases where Unions and Group By queries cannot be expressed using entity objects.

View objects can either be related to underlying entity objects or not. When a view object is related to one or more underlying entity objects the default behavior supports creating new rows and modifying or removing queried rows. However, the update feature can be disabled by deselecting Updatable in the overview editor for the entity-based view object, as shown in Figure 39-4.

Figure 39-4 Deselecting the Updatable Option for an Entity-based View Object

Updatable option deselected in view object editor

The alternative is to create a read-only view object and define the SQL query using Expert Mode in the Edit Query dialog. For the Business Components developer not comfortable with constructing a complex SQL statement, it will always be more convenient to create a non-updatable view object based on an entity object since the editor simplifies the task of creating the query. Entity-based view objects that you set to non-updatable compare favorably to read-only, expert mode-based view objects:

  • There is the ability to optimize the select list at runtime to include only those attributes that are required by the user interface

  • There is no significant performance degradation incurred by using the entity object to create the local cache

  • The data in the view object will reflect the state of the local cache rather than need to return to the database for each read operation

  • The data in the local cache will stay consistent should another view object you define need to perform an update on the non-updatable view object's base entity object.

So, while there is a small amount of runtime overhead associated with the coordination between view object rows and entity object rows (estimates show less than 5% overhead), weigh this against the ability to keep the view object definition entirely declarative and maintain a customizable view object. Expert mode-based view objects are not customizable but they can be used to perform Unions and Group By queries that cannot be expressed in entity objects. Expert mode-based view objects are also useful in SQL-based validation queries used by the view object-based Key Exists validator.

When data is not read-only, the best (and only) choice is to create entity-based view objects. Entity-based view objects that are updatable (default behavior) are the only way to pickup entity-derived attribute default values, reflect pending changes made to relevant entity object attributes through other view objects in the same transaction, and reflect updated reference information when foreign key attribute values are changed is to use an entity-based view object.

39.2.3 Use SQL Tracing to Identify Ill-Performing Queries

After deciding whether your view object should be mapped to entities or not, your attention should turn to the query itself. On the Query page of the view object overview editor, click the Edit SQL Query icon to display the Edit Query dialog. Click the Explain Plan button on the Query page of the Edit Query dialog to see the query plan that the database query optimizer will use. If you see that it is doing a full table scan, you should consider adding indexes or providing a value for the Query Optimizer Hint field on the Tuning section of the overview editor's General page. This will let you explicitly control which query plan will be used. These facilities provide some useful tools to the developer to evaluate the query plans for individual view object SQL statements. However, their use is not a substitute for tracing the SQL of the entire application to identify poorly performing queries in the presence of a production environment's amount of data and number of end users.

You can use the Oracle database's SQL Tracing facilities to produce a complete log of all SQL statements your application performs. The approach that works in all versions of the Oracle database is to issue the command:

ALTER SESSION SET SQL_TRACE TRUE

Specifically in version 10g of Oracle, the DBA would need to grant ALTER SESSION privilege in order to execute this command.

This command enables tracing of the current database session and logs all SQL statements to a server-side trace file until you either enter ALTER SESSION SET SQL_TRACE FALSE or close the connection. To simplify enabling this option to trace your Fusion web applications, override the afterConnect() method of your application module (or custom application module framework extension class) to conditionally perform the ALTER SESSION command to enable SQL tracing based on the presence of a Java system property as shown in Example 39-5.

Example 39-5 Conditionally Enabling SQL Tracing in an Application Module

// In YourCustomApplicationModuleImpl.java
protected void afterConnect() {
  super.afterConnect();
  if (System.getProperty("enableTrace") != null) {
    getDBTransaction().executeCommand("ALTER SESSION SET SQL_TRACE TRUE");
  }
}  

After producing a trace file, you use the TKPROF utility supplied with the database to format the information and to better understand information about each query executed like:

  • The number of times it was (re)parsed

  • The number of times it was executed

  • How many round-trips were made between application server and the database

  • Various quantitative measurements of query execution time

Using these techniques, you can decide which additional indexes might be required to speed up particular queries your application performs, or which queries could be changed to improve their query optimization plan. For details about working with the TKPROF utility, see sections "Understanding SQL Trace and TKPROF" and "Using the SQL Trace Facility and TKPROF" in the Oracle Database Performance Tuning Guide.

Note:

The Oracle database provides the DBMS_MONITOR package that further simplifies SQL tracing and integrates it with Oracle Enterprise Manager for visually monitoring the most frequently performed query statements your applications perform.

39.2.4 Consider the Appropriate Tuning Settings for Every View Object

The Tuning section on the General page of the view object overview editor lets you set various options that can dramatically effect your query's performance. Figure 39-5 shows the default options that the new view object defines.

Figure 39-5 View Object Default Tuning Options

Default tuning options in view object editor

39.2.4.1 Set the Database Retrieval Options Appropriately

The Retrieve from the Database group box, controls how the view object retrieves rows from the database server. The options for the fetch mode are All Rows, Only Up To Row Number, At Most One Row, and No Rows. Most view objects will stick with the default All Rows option, which will be retrieved As Needed (default) or All at Once depending on which option you choose.

Note:

The All at Once option does not enforce a single database round trip to fetch the rows specified by the view object query. The As Needed and All at Once options work in conjunction with the value of in Batches of (also known as fetch size) to determine the number of round trips. For best database access performance, you should consider changing the fetch size as described in Section 39.2.4.2, "Consider Whether Fetching One Row at a Time is Appropriate."

The As Needed option ensures that an executeQuery() operation on the view object initially retrieves only as many rows as necessary to fill the first page of a display, whose number of rows is set based on the view object's range size. If you use As Needed, then you will require only as many database round trips as necessary to deliver the number of rows specified by the initial range size. Whereas, if you use All at Once, then the application will perform as many round trips as necessary to deliver all the rows based on the value of in Batches of (fetch size) and the number of rows identified by the query.

For view objects whose WHERE clause expects to retrieve a single row, set the option to At Most One Row for best performance. This way, the view object knows you don't expect any more rows and will skip its normal test for that situation. Finally, if you use the view object only for creating new rows, set the option to No Rows so no query will ever be performed.

39.2.4.2 Consider Whether Fetching One Row at a Time is Appropriate

The fetch size controls how many rows will be returned in each round trip to the database. By default, the framework will fetch rows in batches of one row at a time. If you are fetching any more than one row, you will gain efficiency by setting this in Batches of value.

However the higher the number, the larger the client-side buffer required, so avoid setting this number arbitrarily high. If you are displaying results n rows at a time in the user interface, it's good to set the fetch size to at least n+1 so that each page of results can be retrieved in a single round trip to the database.

Caution:

Unless your query really fetches just one row, leaving the default fetch size of one (1) in the in Batches of field on the Tuning section of the General page of the view object overview editor is a recipe for bad performance due to many unnecessary round trips between the application server and the database. Oracle strongly recommends considering the appropriate value for each view object's fetch size.

39.2.4.3 Specify a Query Optimizer Hint if Necessary

The Query Optimizer Hint field allows you to specify an optional hint to the Oracle query optimizer to influence what execution plan it will use. You can set this hint in the Tuning page of the overview editor for the view object, as shown in Figure 39-5.

At runtime, the hint you provide is added immediately after the SELECT keyword in the query, wrapped by the special comment syntax /*+ YOUR_HINT */. Two common optimizer hints are:

  • FIRST_ROWS — to hint that you want the first rows as quickly as possible

  • ALL_ROWS — to hint that you want all rows as quickly as possible

There are many other optimizer hints that are beyond the scope of this manual to document. Reference the Oracle database reference manuals for more information on available hints.

39.2.5 Creating View Objects at Design Time

It's important to understand the overhead associated with creating view objects at runtime. Avoid the temptation to do this without a compelling business requirement. For example, if your application issues a query against a table whose name you know at design time and if the list of columns to retrieve is also fixed, then create a view object at design time. When you do this, your SQL statements are neatly encapsulated, can be easily explained and tuned during development, and incur no runtime overhead to discover the structure and data types of the resulting rows.

In contrast, when you use the createViewObjectFromQueryStmt() API on the ApplicationModule interface at runtime, your query is buried in code, it's more complicated to proactively tune your SQL, and you pay a performance penalty each time the view object is created. Since the SQL query statement for a dynamically-created view object could theoretically be different each time a new instance is created using this API, an extra database round trip is required to discover the "shape" of the query results on-the-fly. Only create queries dynamically if you cannot know the name of the table to query until runtime. Most other needs can be addressed using a design-time created view object in combination with runtime API's to set bind variables in a fixed where clause, or to add an additional WHERE clause (with optional bind variables) at runtime.

39.2.6 Use Forward Only Mode to Avoid Caching View Rows

Often you will write code that programmatically iterates through the results of a view object. A typical situation will be custom validation code that must process multiple rows of query results to determine whether an attribute or an entity is valid or not. In these cases, if you intend to read each row in the row set a single time and never require scrolling backward or re-iterating the row set a subsequent time, then you can use "forward only" mode to avoid caching the retrieved rows. To enable forward only mode, call setForwardOnly(true) on the view object.

Note:

Using a read-only view object (with no entity usages) in forward-only mode with an appropriately tuned fetch size is the most efficient way to programmatically read data.

You can also use forward-only mode to avoid caching rows when inserting, updating, or deleting data as long as you never scroll backward through the row set and never call reset() to set the iterator back to the first row. Forward only mode only works with a range size of one (1).

39.3 Generating Custom Java Classes for a View Object

As you've seen, all of the basic querying functionality of a view object can be achieved without using custom Java code. Clients can retrieve and iterate through the data of any SQL query without resorting to any custom code on the view object developer's part. In short, for many read-only view objects, once you have defined the SQL statement, you're done. However, it's important to understand how to enable custom Java generation for a view object when your needs might require it. For example, reasons you might write code in a custom Java class include:

  • To add validation methods (although Groovy Script expressions can provide this support without needing Java)

  • To add custom logic

  • To augment built-in behavior

Appendix E, "Most Commonly Used ADF Business Components Methods" provides a quick reference to the most common code that you will typically write, use, and override in your custom view object and view row classes.

39.3.1 How To Generate Custom Classes

To enable the generation of custom Java classes for a view object, use the Java page of the view object overview editor. As shown in Figure 39-6, there are three optional Java classes that can be related to a view object. The first two in the list are the most commonly used:

  • View object class, which represents the component that performs the query

  • View row class, which represents each row in the query result

Figure 39-6 View Object Custom Java Generation Options

Select Java options dialog

39.3.1.1 Generating Bind Variable Accessors

When you enable the generation of a custom view object class, if you also select the Bind Variable Accessors checkbox, then JDeveloper generates getter and setter methods in your view object class. Since the Users view object had three named bind variables (TheName, LowUserId, and HighUserId), the custom PersonsImpl.java view object class would have corresponding methods like this:

public String getTheName() {...}
public void setTheName(String value){...}
public Number getHighUserId(){...}
public void setHighUserId(Number value) {...}
public Number getLowUserId() {...}
public void setLowUserId(Number value) {...}

These methods allow you to set a bind variable with compile-time type-checking to ensure you are setting a value of the appropriate type. That is, instead of writing a line like this to set the value of the LowUserId:

vo.setNamedWhereClauseParam("LowUserId",new Number(150));

You can write the code like:

vo.setLowUserId(new Number(150));

You can see that with the latter approach, the Java compiler would catch a typographical error had you accidentally typed setLowUserName instead of setLowUserId:

// spelling name wrong gives compile error
vo.setLowUserName(new Number(150));

Or if you were to incorrectly pass a value of the wrong data type, like "ABC" instead of Number value:

// passing String where number expected gives compile error
vo.setLowUserId("ABC");

Without the generated bind variable accessors, an incorrect line of code like the following cannot be caught by the compiler:

// Both variable name and value wrong, but compiler cannot catch it
vo.setNamedWhereClauseParam("LowUserName","ABC");

It contains both an incorrectly spelled bind variable name, as well as a bind variable value of the wrong datatype. If you use the generic APIs on the ViewObject interface, errors of this sort will raise exceptions at runtime instead of being caught at compile time.

39.3.1.2 Generating View Row Attribute Accessors

When you enable the generation of a custom view row class, if you also select the Accessors checkbox, then JDeveloper generates getter and setter methods for each attribute in the view row. For example, for the Persons view object, the corresponding custom PersonsRowImpl.java class might have methods like this generated in it:

public Number getPersonId() {...}
public void setPersonId(Number value) {...}
public String getEmail() {...}
public void setEmail(String value) {...}
public String getFirstName() {...}
public void setFirstName(String value) {...}
public String getLastName() {...}
public void setLastName(String value) {...}

These methods allow you to work with the row data with compile-time checking of the correct datatype usage. That is, instead of writing a line like this one that gets the value of the PersonId attribute:

Number personId = (Number)row.getAttribute("PersonId");

you can write the code like:

Number personId = row.getPersonId();

You can see that with the latter approach, the Java compiler would catch a typographical error had you accidentally typed PersonIdentifier instead of PersonId:

// spelling name wrong gives compile error
Number personId = row.getPersonIdentifier();

Without the generated view row accessor methods, an incorrect line of code like the following cannot be caught by the compiler:

// Both attribute name and type cast are wrong, but compiler cannot catch it
String personId = (String)row.getAttribute("PersonIdentifier");

It contains both an incorrectly spelled attribute name, as well as an incorrectly-typed cast of the getAttribute() return value. Using the generic APIs on the Row interface, errors of this kind will raise exceptions at runtime instead of being caught at compile time.

39.3.1.3 Exposing View Row Accessors to Clients

When enabling the generation of a custom view row class, if you choose to generate the view row attribute accessor, you can also optionally select the Expose Accessor to the Client checkbox. This causes an additional custom row interface to be generated which application clients can use to access custom methods on the row without depending directly on the implementation class.

Best Practice:

When you create client code for business components, you should use business service interfaces rather than concrete classes. Using the interface instead of the implementation class, ensures that client code does not need to change when your server-side implementation does. For more details working with client code, see in Section 3.5.9, "Custom Interface Support for Client-Accessible Components."

For example, in the case of the Persons view object, exposing the accessors to the client will generate a custom row interface named PersonsRow. This interface is created in the common subpackage of the package in which the view object resides. Having the row interface allows clients to write code that accesses the attributes of query results in a strongly typed manner. Example 39-6 shows a TestClient3 sample client program that casts the results of the next() method to the PersonsRow interface so that it can call accessors like getPersonId() and getEmail().

Example 39-6 Simple Example of Using Client Row Interface with Accessors

package devguide.examples.readonlyvo.client;

import devguide.examples.readonlyvo.queries.common.PersonsRow;
import oracle.jbo.*;
import oracle.jbo.client.Configuration;
import oracle.jbo.domain.Number;

public class TestClient3 {
  public static void main(String[] args) {
    String amDef = "devguide.examples.PersonService";
    String config = "PersonServiceLocal";
    ApplicationModule am =
      Configuration.createRootApplicationModule(amDef, config);
    ViewObject vo = am.findViewObject("PersonList");
    vo.executeQuery();
    while (vo.hasNext()) {
     // Cast next() to a strongly-typed PersonsRow interface 
      PersonsRow curPerson = (PersonsRow)vo.next();
      Number personId = curPerson.getPersonId();
      String email  = curPerson.getEmail();
      System.out.println(personId+ " " + email);
    }
    Configuration.releaseRootApplicationModule(am, true);
  }
}

39.3.1.4 Configuring Default Java Generation Preferences

You've seen how to generate custom Java classes for your view objects when you need to customize their runtime behavior, or if you simply prefer to have strongly typed access to bind variables or view row attributes.

To change the default settings that control how JDeveloper generates Java classes, choose Tools | Preferences and open the Business Components page. The settings you choose will apply to all future business components you create.

Oracle recommends that developers getting started with ADF Business Components set their preference to generate no custom Java classes by default. As you run into specific needs, you can enable just the bit of custom Java you need for that one component. Over time, you'll discover which set of defaults works best for you.

39.3.2 What Happens When You Generate Custom Classes

When you choose to generate one or more custom Java classes, JDeveloper creates the Java file(s) you've indicated.

For example, in the case of a view object named devguide.examples.Persons, the default names for its custom Java files will be PersonsImpl.java for the view object class and PersonsRowImpl.java for the view row class. Both files get created in the same ./devguide/examples directory as the component's XML component definition file.

The Java generation options for the view object continue to be reflected on the Java page on subsequent visits to the view object overview editor. Just as with the XML definition file, JDeveloper keeps the generated code in your custom Java classes up to date with any changes you make in the editor. If later you decide you didn't require a custom Java file for any reason, unchecking the relevant options in the Java page causes the custom Java files to be removed.

39.3.2.1 Seeing and Navigating to Custom Java Files

As shown in Figure 39-7, when you've enabled generation of custom Java classes, they also appear under the node for the view object. When you need to see or work with the source code for a custom Java file, there are two ways to open the file in the source editor:

  • Choose Open in the context menu as shown in Figure 39-7

  • With the Java file node selected in the Application Navigator, double-click a node in the Structure window

Figure 39-7 Seeing and Navigating to Custom Java Classes for a View Object

Custom java file in Application Navigator

39.3.3 What You May Need to Know About Custom Classes

This section provides additional information to help you use custom Java classes.

39.3.3.1 About the Framework Base Classes for a View Object

When you use an "XML-only" view object, at runtime its functionality is provided by the default ADF Business Components implementation classes. Each custom Java class that gets generated will automatically extend the appropriate ADF Business Components base class so that your code inherits the default behavior and can easily add or customize it. A view object class will extend ViewObjectImpl, while the view row class will extend ViewRowImpl (both in the oracle.jbo.server package).

39.3.3.2 You Can Safely Add Code to the Custom Component File

Based perhaps on previous negative experiences, some developers are hesitant to add their own code to generated Java source files. Each custom Java source code file that JDeveloper creates and maintains for you includes the following comment at the top of the file to clarify that it is safe to add your own custom code to this file:

// ---------------------------------------------------------------------
// ---    File generated by Oracle ADF Business Components Design Time.
// ---    Custom code may be added to this class.
// ---    Warning: Do not modify method signatures of generated methods.
// ---------------------------------------------------------------------

JDeveloper does not blindly regenerate the file when you click the OK or Apply button in the component dialogs. Instead, it performs a smart update to the methods that it needs to maintain, leaving your own custom code intact.

39.3.3.3 Attribute Indexes and InvokeAccessor Generated Code

The view object is designed to function either in an XML-only mode or using a combination of an XML component definition and a custom Java class. Since attribute values are not stored in private member fields of a view row class, such a class is not present in the XML-only situation. Instead, in addition to a name, attributes are also assigned a numerical index in the view object's XML component definition, on a zero-based, sequential order of the ViewAttribute and association-related ViewLinkAccessor tags in that file. At runtime, the attribute values in an view row are stored in a structure that is managed by the base ViewRowImpl class, indexed by the attribute's numerical position in the view object's attribute list.

For the most part this private implementation detail is unimportant. However, when you enable a custom Java class for your view row, this implementation detail is related to some of the generated code that JDeveloper automatically maintains in your view row class, and you may want to understand what that code is used for. For example, in the custom Java class for the Users view row, Example 39-7 shows that each attribute or view link accessor attribute has a corresponding generated integer constant. JDeveloper ensures that the values of these constants correctly reflect the ordering of the attributes in the XML component definition.

Example 39-7 Attribute Constants Are Automatically Maintained in the Custom View Row Java Class

public class PersonsRowImpl extends ViewRowImpl implements PersonsRow {
  public static final int PERSONID = 0;
  public static final int EMAIL = 1;
  public static final int FIRSTNAME = 2;
  public static final int LASTNAME = 3;
  public static final int PERSONTYPECODE = 4;
  public static final int PRIMARYADDRESSID = 5;
  // etc.

You'll also notice that the automatically maintained, strongly typed getter and setter methods in the view row class use these attribute constants like this:

// In devguide.examples.PersonsRowImpl class
public String getEmail() {
  return (String) getAttributeInternal(EMAIL); // <-- Attribute constant
}
public void setEmail(String value) {
  setAttributeInternal(EMAIL, value);// <-- Attribute constant
}

The last two aspects of the automatically maintained code related to view row attribute constants are the getAttrInvokeAccessor() and setAttrInvokeAccessor() methods. These methods optimize the performance of attribute access by numerical index, which is how generic code in the ViewRowImpl base class typically accesses attribute values. An example of the getAttrInvokeAccessor() method looks like the following from the PersonsRowImpl.java class. The companion setAttrInvokeAccessor() method looks similar.

// In devguide.examples.PersonsRowImpl class
protected Object getAttrInvokeAccessor(int index,AttributeDefImpl attrDef)
throws Exception {
  switch (index) {
  case PERSONID:         return getPersonId();
  case EMAIL:            return getEmail();
  case FIRSTNAME:        return getFirstName();
  case LASTNAME:         return getLastName();
  case PERSONTYPECODE:   return getPersonTypeCode();
  case PRIMARYADDRESSID: return getPrimaryAddressId();
  default:
    return super.getAttrInvokeAccessor(index, attrDef);
  }
}

The rules of thumb to remember about this generated attribute-index related code are the following.

The Do's
  • Add custom code if needed inside the strongly typed attribute getter and setter methods

  • Use the view object overview editor to change the order or type of view object attributes

    JDeveloper will change the Java signature of getter and setter methods, as well as the related XML component definition for you.

The Don'ts
  • Don't modify the getAttrInvokeAccessor() and setAttrInvokeAccessor() methods

  • Don't change the values of the attribute index numbers by hand

Note:

If you need to manually edit the generated attribute constants, perhaps due to source control merge conflicts, you must ensure that the zero-based ordering reflects the sequential ordering of the ViewAttribute and ViewLinkAccessor tags in the corresponding view object XML component definition.

39.4 Working Programmatically with Multiple Named View Criteria

You can define multiple named view criteria in the overview editor for a view object and then selectively apply any combination of them to your view object at runtime as needed. For information about working with named view criteria at design time, see Section 5.11.1, "How to Create Named View Criteria Declaratively."

Note:

The example in this section refers to the MultipleViewCriteria project in the AdvancedViewObjectsExamples application workspace in the StandaloneExamples module of the Fusion Order Demo application.

39.4.1 Applying One or More Named View Criteria

To apply one or more named view criteria, use the setApplyViewCriteriaNames() method. This method accepts a String array of the names of the criteria you want to apply. If you apply more than one named criteria, they are AND-ed together in the WHERE clause produced at runtime. New view criteria that you apply with the setApplyViewCriteriaNames() method will not overwrite those that were previously applied.

When you need to apply more than one named view criteria, you can expose custom methods on the client interface of the view object to encapsulate applying combinations of the named view criteria. For example, Example 39-8 shows custom methods showMaleCustomers(), showFemaleStaff(), and showFemaleCustomers(), each of which uses the setApplyViewCriteriaNames() method to apply an appropriate combination of named view criteria. Once these methods are exposed on the view object's client interface, at runtime clients can invoke these methods as needed to change the information displayed by the view object.

Example 39-8 Exposing Client Methods to Enable Appropriate Named Criterias

// In PersonsViewImpl.java
    public void showMaleCustomers() {
        ViewCriteriaManager vcm = getViewCriteriaManager();
        ViewCriteria vc_gender = vcm.getViewCriteria("GenderIsNotFCriteria");
        ViewCriteria vc_type = vcm.getViewCriteria("IsCustomerCriteria");
        VariableValueManager vvm_gender = vc_gender.ensureVariableManager();
        VariableValueManager vvm_type = vc_type.ensureVariableManager();
        vvm_gender.setVariableValue("bv_Gender","F");
        vvm_type.setVariableValue("bv_PersonTypeCode", "CUST");
        setApplyViewCriteriaNames(new String[]{"GenderIsNotFCriteria",
                                                        "IsCustomerCriteria"});
    }

    public void showFemaleStaff() {
        ViewCriteriaManager vcm = getViewCriteriaManager();
        ViewCriteria vc_gender = vcm.getViewCriteria("GenderIsFCriteria");
        ViewCriteria vc_type = vcm.getViewCriteria("IsStaffSupplierCriteria");
        VariableValueManager vvm_gender = vc_gender.ensureVariableManager();
        VariableValueManager vvm_type = vc_type.ensureVariableManager();
        vvm_gender.setVariableValue("bv_Gender","F");
        vvm_type.setVariableValue("bv_PersonTypeCode", "CUST");
        setApplyViewCriteriaNames(new String[]{"GenderIsFCriteria",
                                                     "IsStaffSupplierCriteria"});
        executeQuery();
    }

    public void showFemaleCustomers() {
        ViewCriteriaManager vcm = getViewCriteriaManager();
        ViewCriteria vc_gender = vcm.getViewCriteria("GenderIsFCriteria");
        ViewCriteria vc_type = vcm.getViewCriteria("IsCustomerCriteria");
        VariableValueManager vvm_gender = vc_gender.ensureVariableManager();
        VariableValueManager vvm_type = vc_type.ensureVariableManager();
        vvm_gender.setVariableValue("bv_Gender","F");
        vvm_type.setVariableValue("bv_PersonTypeCode", "CUST");
        setApplyViewCriteriaNames(new String[]{"GenderIsFCriteria",
                                                   "IsCustomerCriteria"});
        executeQuery();

39.4.2 Removing All Applied Named View Criteria

To remove any currently applied named view criteria, use setApplyViewCriteriaNames(null). For example, you could add the showAll() method in Example 39-9 to the Users view object and expose it on the client interface. This would allow clients to return to an unfiltered view of the data when needed.

Do not remove any design time view criteria because the row level bind variable values may already be applied on the row set. To help ensure this, named view criteria that get defined for a view accessor in the design time, will be applied as "required" view criteria on the view object instance so that it does not get removed by the view criteria's life cycle methods.

Example 39-9 Removing All Applied Named View Criteria

// In UsersImpl.java
public void showAll() {
  setApplyViewCriteriaNames(null);
  executeQuery();
}

Note:

The setApplyViewCriterias(null) removes all applied view criteria, but allows you to later reapply any combination of them. In contrast, the clearViewCriterias() method deletes all named view criteria. After calling clearViewCriterias() you would have to use putViewCriteria() again to define new named criteria before you could apply them.

39.4.3 Using the Named Criteria at Runtime

At runtime, your application can invoke different client methods on a single view object interface to return different filtered sets of data. Example 39-10 shows the interesting lines of a TestClient class that works with the Persons view object described above. The showResults() method is a helper method that iterates over the rows in the view object to display some attributes.

Example 39-10 Test Client Code Working with Named View Criterias

// In TestClientMultipleViewCriterias.java
PersonsView vo = (PersonsView)am.findViewObject("PersonsView");
vo.showMaleCustomers();
showResults(vo,"After applying view criterias for male customers");
vo.applyViewCriteria(null);
vo.showFemaleStaff();
showResults(vo,"After applying view criterias for female staff");
vo.showFemaleCustomers();
showResults(vo,"After applying view criterias for female customers");
vo.showAll();
vo.showResults(vo,"After clearing all view criterias");

Running the TestClient program produces output as follows:

---After applying view criterias for male customers ---
Daniel Faviet [CUST, M]
John Chen [CUST, M]
Ismael Sciarra [CUST, M]
Jose Manuel Urman [CUST, M]
Luis Popp [CUST, M]
Den Raphaely [CUST, M]
Alexander Khoo [CUST, M]
Sigal Tobias [CUST, M]
Guy Himuro [CUST, M]
Matthew Weiss [CUST, M]
Adam Fripp [CUST, M]
Payam Kaufling [CUST, M]
Kevin Mourgos [CUST, M]
James Landry [CUST, M]
Steven Markle [CUST, M]
...
---After applying view criterias for female staff ---
Neena Kochhar [STAFF, F]
Valli Pataballa [STAFF, F]
Diana Lorentz [STAFF, F]
Terra Bralick [SUPP, F]
Rachel Berman [SUPP, F]
Claudia Benghiat [SUPP, F]
Sharon Hemant [SUPP, F]
Alison Chen [SUPP, F]
Alex Duckers [SUPP, F]
Katrina Han [SUPP, F]
---After applying view criterias for female customers ---
Nancy Greenberg [CUST, F]
Shelli Baida [CUST, F]
Karen Colmenares [CUST, F]
Shanta Vollman [CUST, F]
Julia Nayer [CUST, F]
Irene Mikkilineni [CUST, F]
Laura Bissot [CUST, F]
---After clearing all view criterias ---
Steven King [STAFF, M]
Neena Kochhar [STAFF, F]
Lex De Haan [STAFF, M]
Alexander Hunold [STAFF, M]
Bruce Ernst [STAFF, M]
David Austin [STAFF, M]
Valli Pataballa [STAFF, F]
Diana Lorentz [STAFF, F]
Nancy Greenberg [CUST, F]
...

39.5 Performing In-Memory Sorting and Filtering of Row Sets

By default a view object performs its query against the database to retrieve the rows in its resulting row set. However, you can also use view objects to perform in-memory searches and sorting to avoid unnecessary trips to the database.

Note:

The example in this section refers to the InMemoryOperations project in the AdvancedViewObjectsExamples application workspace in the StandaloneExamples module of the Fusion Order Demo application.

39.5.1 Understanding the View Object's SQL Mode

The view object's SQL mode controls the source used to retrieve rows to populate its row set. The setQueryMode() allows you to control which mode, or combination of modes, are used:

  • ViewObject.QUERY_MODE_SCAN_DATABASE_TABLES

    This is the default mode that retrieves results from the database.

  • ViewObject.QUERY_MODE_SCAN_VIEW_ROWS

    This mode uses rows already in the row set as the source, allowing you to progressively refine the row set's contents through in-memory filtering.

  • ViewObject.QUERY_MODE_SCAN_ENTITY_ROWS

    This mode, valid only for entity-based view objects, uses the entity rows presently in the entity cache as the source to produce results based on the contents of the cache.

You can use the modes individually, or combine them using Java's logical OR operator (X | Y). For example, to create a view object that queries the entity cache for unposted new entity rows, as well as the database for existing rows, you could write code like:

setQueryMode(ViewObject.QUERY_MODE_SCAN_DATABASE_TABLES |
             ViewObject.QUERY_MODE_SCAN_ENTITY_ROWS)

If you combine the SQL modes, the view object automatically handles skipping of duplicate rows. In addition, there is an implied order to the results that are found:

  1. Scan view rows (if specified)

  2. Scan entity cache (if specified)

  3. Scan database tables (if specified) by issuing a SQL query

If you call the setQueryMode() method to change the SQL mode, your new setting takes effect the next time you call the executeQuery() method.

39.5.2 Sorting View Object Rows In Memory

To sort the rows in a view object at runtime, use the setSortBy() method. You pass a sort expression that looks like a SQL ORDER BY clause. However, instead of referencing the column names of the table, you use the view object's attribute names. For example, for a view object containing attributes named Customer and DaysOpen, you could sort the view object first by Customer descending, then by DaysOpen by calling:

setSortBy("Customer desc, DaysOpen");

Alternatively, you can use the zero-based attribute index position in the sorting clause like this:

setSortBy("3 desc, 2");

After calling the setSortBy() method, the rows will be sorted the next time you call the executeQuery() method. The view object translates this sorting clause into an appropriate format to use for ordering the rows depending on the SQL mode of the view object. If you use the default SQL mode, the SortBy clause is translated into an appropriate ORDER BY clause and used as part of the SQL statement sent to the database. If you use either of the in-memory SQL modes, then the SortBy by clause is translated into one or more SortCriteria objects for use in performing the in-memory sort.

Note:

While SQL ORDER BY expressions treat column names in a case-insensitive way, the attribute names in a SortBy expression are case-sensitive.

39.5.2.1 Combining setSortBy and setQueryMode for In-Memory Sorting

You can perform an in-memory sort on the rows produced by a read-only view object using the setSortBy() and setQueryMode() methods. Example 39-11 shows the interesting lines of code from the TestClientSetSortBy class that uses setSortBy() and setQueryMode() to perform an in-memory sort on the rows produced by a read-only view object ClosedOrders.

Example 39-11 Combining setSortBy and setQueryMode for In-Memory Sorting

// In TestClientSetSortBy.java
am.getTransaction().executeCommand("ALTER SESSION SET SQL_TRACE TRUE");
ViewObject vo = am.findViewObject("ClosedOrders");
vo.executeQuery();
showRows(vo,"Initial database results");
vo.setSortBy("Customer desc");
vo.setQueryMode(ViewObject.QUERY_MODE_SCAN_VIEW_ROWS);
vo.executeQuery();
showRows(vo,"After in-memory sorting by Customer desc");
vo.setSortBy("Customer desc, DaysOpen");
vo.executeQuery();
showRows(vo,"After in-memory sorting by Customer desc, DaysOpen");

Running the example produces the results:

--- Initial database results ---
106,Ice machine not working,1,mhartste
103,Washing machine leaks,4,ngreenbe
105,Air in dryer not hot,4,jmurman
109,Freezer is not cold,4,jwhalen
:
--- After in-memory sorting by Customer desc ---
100,I have noticed that every time I do a...,9,dfaviet
101,Agitator does not work,8,sbaida
103,Washing machine leaks,4,ngreenbe
105,Air in dryer not hot,4,jmurman
:
--- After in-memory sorting by Customer desc, DaysOpen ---
100,I have noticed that every time I do a...,9,dfaviet
101,Agitator does not work,8,sbaida
105,Air in dryer not hot,4,jmurman
109,Freezer is not cold,4,jwhalen
:

The first line in Example 39-11 containing the executeCommand() call issues the ALTER SESSION SET SQL TRACE command to enable SQL tracing for the current database session. This causes the Oracle database to log every SQL statement performed to a server-side trace file. It records information about the text of each SQL statement, including how many times the database parsed the statement and how many round-trips the client made to fetch batches of rows while retrieving the query result.

Note:

You might need a DBA to grant permission to the FOD account to perform the ALTER SESSION command to do the tracing of SQL output.

Once you've produced a trace file, you can use the TKPROF utility that comes with the database to format the file:

tkprof xe_ora_3916.trc trace.prf

For details about working with the TKPROF utility, see sections "Understanding SQL Trace and TKPROF" and "Using the SQL Trace Facility and TKPROF" in the Oracle Database Performance Tuning Guide.

This will produces a trace.prf file containing the interesting information shown in Example 39-12 about the SQL statement performed by the ClosedOrders view object. You can see that after initially querying six rows of data in a single execute and fetch from the database, the two subsequent sorts of those results did not cause any further executions. Since the code set the SQL mode to ViewObject.QUERY_MODE_SCAN_VIEW_ROWS the setSortBy() followed by the executeQuery() performed the sort in memory.

Example 39-12 TKPROF Output of a Trace File Confirming Sort Was Done In Memory

*************************************************************
SELECT * FROM (select o.order_id,
       case 
         when length(o.giftwrap_message) > 5 then
         rtrim(substr(o.giftwrap_message,1,5))||'...'
         else o.giftwrap_messagen 
       end as giftwrap_message, 
       ceil(
         (select trunc(max(creation_date))
            from order_histories
           where order_id = or.order_id)
         - trunc(o.order_date)
       ) as days_open,
       p.email as customer
from orders o, persons p
 where o.customer_id = p.person_id
   and order status code = 'COMPLETE')

call    count     cpu  elapsed disk  query  current     rows
------- -----  ------ -------- ---- ------ --------  -------
Parse       1    0.00     0.00    0      0        0        0
Execute     1    0.00     0.00    0      0        0        0
Fetch       1    0.00     0.00    0     22        0        6
------- -----  ------ -------- ---- ------ --------  -------
total       3    0.00     0.00    0     22        0        6
*************************************************************

39.5.2.2 Extensibility Points for In-Memory Sorting

Should you need to customize the way that rows are sorted in memory, you have the following two extensibility points:

  1. You can override the method:

    public void sortRows(Row[] rows)
    

    This method performs the actual in-memory sorting of rows. By overriding this method you can plug in an alternative sorting approach if needed.

  2. You can override the method:

    public Comparator getRowComparator()
    

    The default implementation of this method returns an oracle.jbo.RowComparator. RowComparator invokes the compareTo() method to compare two data values. These methods/objects can be overridden to provide custom compare routines.

39.5.3 Performing In-Memory Filtering with View Criteria

To filter the contents of a row set using ViewCriteria, you can call:

  • applyViewCriteria() or setApplyViewCriteriaNames() followed by executeQuery() to produce a new, filtered row set.

  • findByViewCriteria() to retrieve a new row set to process programmatically without changing the contents of the original row set.

Both of these approaches can be used against the database or to perform in-memory filtering, or both, depending on the view criteria mode. You set the criteria mode using the setCriteriaMode() method on the ViewCriteria object, to which you can pass either of the following integer flags, or the logical OR of both:

  • ViewCriteria.CRITERIA_MODE_QUERY

  • ViewCriteria.CRITERIA_MODE_CACHE

When used for in-memory filtering with view criteria, the operators supported are shown in Table 39-0. You can group subexpressions with parenthesis and use the AND and OR operators between subexpressions.

Table 39-1 SQL Operators Supported By In-Memory Filtering with View Criteria

Operator Operation

=, >, <, <=, >=, <>, LIKE, BETWEEN

Comparison

NOT

Logical negation

AND

Conjunction

OR

Disjunction


Example 39-13 shows the interesting lines from a TestClientFindByViewCriteria class that uses the two features described above both against the database and in-memory. It uses a CustomerList view object instance and performs the following basic steps:

  1. Queries customers from the database with a last name starting with a 'C', producing the output:

    --- Initial database results with applied view criteria ---
    John Chen
    Emerson Clabe
    Karen Colmenares
    
  2. Subsets the results from step 1 in memory to only those with a first name starting with 'J'. It does this by adding a second view criteria row to the view criteria and setting the conjunction to use "AND". This produces the output:

    --- After augmenting view criteria and applying in-memory ---
    John Chen
    
  3. Sets the conjunction back to OR and re-applies the criteria to the database to query customers with last name like 'J%' or first name like 'C%'. This produces the output:

    --- After changing view criteria and applying to database again ---
    John Chen
    Jose Manuel Urman
    Emerson Clabe
    Karen Colmenares
    Jennifer Whalen
    
  4. Defines a new criteria to find customers in-memory with first or last name that contain a letter 'o'

  5. Uses findByViewCriteria() to produce new row set instead of subsetting, producing the output:

    --- Rows returned from in-memory findByViewCriteria ---
    John Chen
    Jose Manuel Urman
    Emerson Clabe
    Karen Colmenares
    
  6. Shows that original row set hasn't changed when findByViewCriteria() was used, producing the output:

    --- Note findByViewCriteria didn't change rows in the view ---
    John Chen
    Jose Manuel Urman
    Emerson Clabe
    Karen Colmenares
    Jennifer Whalen
    

Example 39-13 Performing Database and In-Memory Filtering with View Criteria

// In TestClientFindByViewCriteria.java
ViewObject vo = am.findViewObject("CustomerList");
// 1. Show customers with a last name starting with a 'M'
ViewCriteria vc = vo.createViewCriteria();
ViewCriteriaRow vcr1 = vc.createViewCriteriaRow();
vcr1.setAttribute("LastName","LIKE 'M%'");
vo.applyViewCriteria(vc);
vo.executeQuery();
vc.add(vcr1);
vo.executeQuery();
showRows(vo, "Initial database results with applied view criteria");
// 2. Subset results in memory to those with first name starting with 'S'
vo.setQueryMode(ViewObject.QUERY_MODE_SCAN_VIEW_ROWS);
ViewCriteriaRow vcr2 = vc.createViewCriteriaRow();
vcr2.setAttribute("FirstName","LIKE 'S%'");
vcr2.setConjunction(ViewCriteriaRow.VCROW_CONJ_AND);
vc.setCriteriaMode(ViewCriteria.CRITERIA_MODE_CACHE);
vc.add(vcr2);
vo.executeQuery();
showRows(vo,"After augmenting view criteria and applying in-memory");
// 3. Set conjuction back to OR and re-apply to database query to find
// customers with last name like 'H%' or first name like 'S%'
vc.setCriteriaMode(ViewCriteria.CRITERIA_MODE_QUERY);
vo.setQueryMode(ViewObject.QUERY_MODE_SCAN_DATABASE_TABLES);
vcr2.setConjunction(ViewCriteriaRow.VCROW_CONJ_OR);
vo.executeQuery();
showRows(vo,"After changing view criteria and applying to database again");
// 4. Define new critera to find customers with first or last name like '%o%'
ViewCriteria nameContainsO = vo.createViewCriteria();
ViewCriteriaRow lastContainsO = nameContainsO.createViewCriteriaRow();
lastContainsO.setAttribute("LastName","LIKE '%o%'");
ViewCriteriaRow firstContainsO = nameContainsO.createViewCriteriaRow();
firstContainsO.setAttribute("FirstName","LIKE '%o%'");
nameContainsO.add(firstContainsO);
nameContainsO.add(lastContainsO);
// 5. Use findByViewCriteria() to produce new rowset instead of subsetting
nameContainsO.setCriteriaMode(ViewCriteria.CRITERIA_MODE_CACHE);
RowSet rs = (RowSet)vo.findByViewCriteria(nameContainsO,
                         -1,ViewObject.QUERY_MODE_SCAN_VIEW_ROWS);
showRows(rs,"Rows returned from in-memory findByViewCriteria");
// 6. Show that original rowset hasn't changed
showRows(vo,"Note findByViewCriteria didn't change rows in the view");

39.5.4 Performing In-Memory Filtering with RowMatch

The RowMatch object provides an even more convenient way to express in-memory filtering conditions. You create a RowMatch object by passing a query predicate expression to the constructor like this:

RowMatch rm = 
 new RowMatch("LastName = 'Popp' or (FirstName like 'A%' and LastName like 'K%')");

As you do with the SortBy clause, you phrase the RowMatch expression in terms of the view object attribute names, using the supported operators shown in Table 39-2. You can group subexpressions with parenthesis and use the and and or operators between subexpressions.

Table 39-2 SQL Operators Supported By In-Memory Filtering with RowMatch

Operator Operation

=, >, <, <=, >=, <>, LIKE, BETWEEN

Comparison

NOT

Logical negation

AND

Conjunction

OR

Disjunction


You can also use a limited set of SQL functions in the RowMatch expression, as shown in Table 39-3.

Table 39-3 SQL Functions Supported By In-Memory Filtering with RowMatch

Operator Operation

UPPER

Converts all letters in a string to uppercase.

TO_CHAR

Converts a number or date to a string.

TO_DATE

Converts a character string to a date format.

TO_TIMESTAMP

Converts a string to timestamp.


Note:

While SQL query predicates treat column names in a case-insensitive way, the attribute names in a RowMatch expression are case-sensitive.

39.5.4.1 Applying a RowMatch to a View Object

To apply a RowMatch to your view object, call the setRowMatch() method. In contrast to a ViewCriteria, the RowMatch is only used for in-memory filtering, so there is no "match mode" to set. You can use a RowMatch on view objects in any supported SQL mode, and you will see the results of applying it the next time you call the executeQuery() method.

When you apply a RowMatch to a view object, the RowMatch expression can reference the view object's named bind variables using the same :VarName notation that you would use in a SQL statement. For example, if a view object had a named bind variable named StatusCode, you could apply a RowMatch to it with an expression like:

Status = :StatusCode or :StatusCode = '%'

Example 39-14 shows the interesting lines of a TestClientRowMatch class that illustrate the RowMatch in action. The CustomerList view object used in the example has a transient Boolean attribute named Selected. The code performs the following basic steps:

  1. Queries the full customer list, producing the output:

    --- Initial database results ---
    Neena Kochhar [null]
    Lex De Haan [null]
    Nancy Greenberg [null]
    :
    
  2. Marks odd-numbered rows selected by setting the Selected attribute of odd rows to Boolean.TRUE, producing the output:

    --- After marking odd rows selected ---
    Neena Kochhar [null]
    Lex De Haan [true]
    Nancy Greenberg [null]
    Daniel Faviet [true]
    John Chen [null]
    Ismael Sciarra [true]
    :
    
  3. Uses a RowMatch to subset the row set to contain only the select rows, that is, those with Selected = true. This produces the output:

    --- After in-memory filtering on only selected rows ---
    Lex De Haan [true]
    Daniel Faviet [true]
    Ismael Sciarra [true]
    Luis Popp [true]
    :
    
  4. Further subsets the row set using a more complicated RowMatch expression, producing the output:

    --- After in-memory filtering with more complex expression ---
    Lex De Haan [true]
    Luis Popp [true]
    

Example 39-14 Performing In-Memory Filtering with RowMatch

// In TestClientRowMatch.java
// 1. Query the full customer list
ViewObject vo = am.findViewObject("CustomerList");
vo.executeQuery();
showRows(vo,"Initial database results");
// 2. Mark odd-numbered rows selected by setting Selected = Boolean.TRUE
markOddRowsAsSelected(vo);
showRows(vo,"After marking odd rows selected");
// 3. Use a RowMatch to subset row set to only those with Selected = true
RowMatch rm = new RowMatch("Selected = true");
vo.setRowMatch(rm);
// Note: Only need to set SQL mode when not defined at design time
vo.setQueryMode(ViewObject.QUERY_MODE_SCAN_VIEW_ROWS);
vo.executeQuery();
showRows(vo, "After in-memory filtering on only selected rows");
// 5. Further subset rowset using more complicated RowMatch expression
rm = new RowMatch("LastName = 'Popp' "+
                  "or (FirstName like 'A%' and LastName like 'K%')");
vo.setRowMatch(rm);
vo.executeQuery();
showRows(vo,"After in-memory filtering with more complex expression");
// 5. Remove RowMatch, set query mode back to database, requery to see full list
vo.setRowMatch(null);
vo.setQueryMode(ViewObject.QUERY_MODE_SCAN_DATABASE_TABLES);
vo.executeQuery();
showRows(vo,"After re-querying to see a full list again");

39.5.4.2 Using RowMatch to Test an Individual Row

In addition to using a RowMatch to filter a row set, you can also use its rowQualifies() method to test whether any individual row matches the criteria it encapsulates. For example:

RowMatch rowMatch = new RowMatch("CountryId = 'US'");
if (rowMatch.rowQualifies(row)) {
  System.out.println("Customer is from the United States ");
}

39.5.4.3 How a RowMatch Affects Rows Fetched from the Database

Once you apply a RowMatch, if the view object's SQL mode is set to retrieve rows from the database, when you call executeQuery() the RowMatch is applied to rows as they are fetched. If a fetched row does not qualify, it is not added to the rowset.

Unlike a SQL WHERE clause, a RowMatch can evaluate expressions involving transient view object attributes and not-yet-posted attribute values. This can be useful to filter queried rows based on RowMatch expressions involving transient view row attributes whose values are calculated in Java. This interesting aspect should be used with care, however, if your application needs to process a large rowset. Oracle recommends using database-level filtering to retrieve the smallest-possible rowset first, and then using RowMatch as appropriate to subset that list in memory.

39.6 Using View Objects to Work with Multiple Row Types

Sometimes you will create a view object to work with entity rows of a single type like Supplier, which perhaps includes Supplier-specific attributes. At other times you may want to query and update rows based on an entity object inheritance hierarchy in the same row set. For example, you might work, in the same row set, with attributes that are common to the inheritance hierarchy of Persons, Supplier, and Staff entity objects.

Note:

To experiment with the example described in this section, use the same InheritanceAndPolymorphicQueries project in the AdvancedEntityExamples workspace used in Section 38.7, "Using Inheritance in Your Business Domain Layer."

39.6.1 Working with Polymorphic Entity Usages

A polymorphic entity usage is one that references a base entity object in an inheritance hierarchy and is configured to handle subtypes of that entity as well. Figure 39-8 shows the results of using a view object with a polymorphic entity usage. The entity-based PersonList view object has the Person entity object as its primary entity usage. The view object partitions each row retrieved from the database into an entity row part of the appropriate entity object subtype of Person. It creates the appropriate entity row subtype based on consulting the value of the discriminator attribute. For example, if the PersonList query retrieves one row for person ngreenbe, one row for staff sking, and one row for supplier ahunold, the underlying entity row parts would be as shown in the figure.

Figure 39-8 View Object with a Polymorphic Entity Usage Handles Entity Subtypes

Flow of entity subtypes

39.6.2 How To Create a View Object with a Polymorphic Entity Usage

To create a view object with a polymorphic entity usage, follow these steps:

  1. Identify the entity object that represents the base type in the entity inheritance hierarchy you want to work with.

    For example, the data model project might define a base Persons entity object in order to support the creation of view objects with polymorphic entity usages for the Supplier and Staff entity objects.

  2. Create an entity-based view object with that base entity as its entity usage.

    For example, you might create the SupplierList view object based on the Persons entity object.

    Note:

    When an entity-based view object references an entity object with a discriminator attribute, then JDeveloper enforces that the discriminator attribute is included in the query as well (in addition to the primary key attribute).
  3. On the Entity Objects page of the view object overview editor, select the entity usage in the Selected list and click Subtypes.

  4. In the Subtypes dialog, shuttle the desired entity subtypes you want to allow from the Available to the Selected list, and click OK.

    For example, for the StaffList view object you would select the entity subtype Staff, as shown in Figure 39-9.

    Figure 39-9 View Object with a Entity Subtype Selection

    Select Subtypes dialog
  5. Click OK.

    The Entity Objects page of the overview editor identifies the selected entity object with the entity subtype override. For example, overview editor for the StaffList view object identifies the overridden entity object ThePerson (Staff): overridden with the subtype in parenthesis, as shown in Figure 39-10.

    Figure 39-10 View Object Editor Shows Entity Subtype is Overridden

    Overridden subtype in view object editor

39.6.3 What Happens When You Create a View Object with a Polymorphic Entity Usage

When you create an entity-based view object with a polymorphic entity usage, JDeveloper adds information about the allowed entity subtypes to the view object's XML component definition. For example, when creating the PersonList view object above, the names of the allowed subtype entity objects are recorded in an AttrArray tag like this:

<ViewObject Name="PersonList" ... >
   <EntityUsage Name="ThePerson"
                Entity="devguide.advanced.inheritance.Persons" >
   </EntityUsage>
...
   <AttrArray Name="EntityImports">
      <Item Value="devguide.advanced.inheritance.Staff" />
      <Item Value="devguide.advanced.inheritance.Supplier" />
   </AttrArray>
   <!-- etc. -->
</ViewObject>

39.6.4 What You May Need to Know About Entity Usages

This section provides additional information to help you work with polymorphic entity usages.

39.6.4.1 Your Query Must Limit Rows to Expected Entity Subtypes

If your view object expects to work with only a subset of the available entity subtypes in a hierarchy, you need to include an appropriate WHERE clause that limits the query to only return rows whose discriminator column matches the expected entity types.

39.6.4.2 Exposing Selected Entity Methods in View Rows Using Delegation

By design, clients do not work directly with entity objects. Instead, they work indirectly with entity objects through the view rows of an appropriate view object that presents a relevant set of information related to the task as hand. Just as a view object can expose a particular set of the underlying attributes of one or more entity objects related to the task at hand, it can also expose a selected set of methods from those entities. You accomplish this by enabling a custom view row Java class and writing a method in the view row class that:

  • Accesses the appropriate underlying entity row using the generated entity accessor in the view row, and

  • Invokes a method on it

For example, assume that the Persons entity object contains a performPersonFeature() method in its PersonsImpl class. To expose this method to clients on the PersonsList view row, you can enable a custom view row Java class and write the method shown in Example 39-15. JDeveloper generates an entity accessor method in the view row class for each participating entity usage based on the entity usage alias name. Since the alias for the Persons entity in the PersonsList view object is "ThePerson", it generates a getThePerson() method to return the entity row part related to that entity usage.

Example 39-15 Exposing Selected Entity Object Methods on View Rows Through Delegation

// In PersonListRowImpl.java
public void performPersonFeature() {
  getThePerson().performPersonFeature();
}

The code in the view row's performPersonFeature() method uses this getThePerson() method to access the underlying PersonImpl entity row class and then invokes its performPersonFeature() method. This style of coding is known as delegation, where a view row method delegates the implementation of one of its methods to a corresponding method on an underlying entity object. When delegation is used in a view row with a polymorphic entity usage, the delegated method call is handled by appropriate underlying entity row subtype. This means that if the PersonsImpl, StaffImpl, and SupplierImpl classes implement the performPersonFeature() method in a different way, the appropriate implementation is used depending on the entity subtype for the current row.

After exposing this method on the client row interface, client programs can use the custom row interface to invoke custom business functionality on a particular view row. Example 39-16 shows the interesting lines of code from a TestEntityPolymorphism class. It iterates over all the rows in the PersonList view object instance, casts each one to the custom PersonListRow interface, and invokes the performPersonFeature() method.

Example 39-16 Invoking a View Row Method That Delegates to an Entity Object

PersonList personlist = (PersonList)am.findViewObject("PersonList");
personlist.executeQuery();
while (personlist.hasNext()) {
  PersonListRow person = (PersonListRow)personlist.next();
  System.out.print(person.getEmail()+"->");
  person.performPersonFeature();
}

Running the client code in Example 39-16 produces the following output:

austin->## performPersonFeature as Supplier
hbaer->## performPersonFeature as Person
:
sking->## performPersonFeature as Staff
:

Rows related to Persons entities display a message confirming that the performPersonFeature() method in the PersonsImpl class was used. Rows related to Supplier and Staff entities display a different message, highlighting the different implementations that the respective SupplierImpl and StaffImpl classes have for the inherited performPersonFeature() method.

39.6.4.3 Creating New Rows With the Desired Entity Subtype

In a view object with a polymorphic entity usage, when you create a new view row it contains a new entity row part whose type matches the base entity usage. To create a new view row with one of the entity subtypes instead, use the createAndInitRow() method. Example 39-17 shows two custom methods in the PersonList view object's Java class that use createAndInitRow() to allow a client to create new rows having entity rows either of Staff or Supplier subtypes. To use the createAndInitRow(), as shown in the example, create an instance of the NameValuePairs object and set it to have an appropriate value for the discriminator attribute. Then, pass that NameValuePairs to the createAndInitRow() method to create a new view row with the appropriate entity row subtype, based on the value of the discriminator attribute you passed in.

Example 39-17 Exposing Custom Methods to Create New Rows with Entity Subtypes

// In PersonListImpl.java
public PersonListRow createStaffRow() {
  NameValuePairs nvp = new NameValuePairs();
  nvp.setAttribute("PersonTypeCode","STAFF");
  return (PersonListRow)createAndInitRow(nvp);
}
public PersonListRow createSupplierRow() {
  NameValuePairs nvp = new NameValuePairs();
  nvp.setAttribute("PersonTypeCode","SUPP");
  return (PersonListRow)createAndInitRow(nvp);
} 

If you expose methods like this on the view object's custom interface, then at runtime, a client can call them to create new view rows with appropriate entity subtypes. Example 39-18 shows the interesting lines relevant to this functionality from a TestEntityPolymorphism class. First, it uses the createRow(), createStaffRow(), and createSupplierRow() methods to create three new view rows. Then, it invokes the performPersonFeature() method from the PersonListRow custom interface on each of the new rows.

As expected, each row handles the method in a way that is specific to the subtype of entity row related to it, producing the results:

## performPersonFeature as Person
## performPersonFeature as Staff
## performPersonFeature as Supplier

Example 39-18 Creating New View Rows with Different Entity Subtypes

// In TestEntityPolymorphism.java
PersonListRow newPerson = (PersonListRow)Personlist.createRow();
PersonListRow newStaff  = Personlist.createStaffRow();
PersonListRow newSupplier = Personlist.createSupplierRow();
newPerson.performPersonFeature();
newStaff.performPersonFeature();
newSupplier.performPersonFeature();

39.6.5 Working with Polymorphic View Rows

In the example shown in Section 39.6, "Using View Objects to Work with Multiple Row Types," the polymorphism occurs "behind the scenes" at the entity object level. Since the client code works with all view rows using the same PersonListRow interface, it cannot distinguish between rows based on a Staff entity object from those based on a Persons entity object. The code works with all view rows using the same set of view row attributes and methods common to all types of underlying entity subtypes.

If you configure a view object to support polymorphic view rows, then the client can work with different types of view rows using a view row interface specific to the type of row it is. By doing this, the client can access view attributes or invoke view row methods that are specific to a given subtype as needed. Figure 39-11 illustrates the hierarchy of view objects that enables this feature for the PersonList example considered above. SupplierList and StaffList are view objects that extend the base PersonList view object. Notice that each one includes an additional attribute specific to the subtype of Person they have as their entity usage. SupplierList includes an additional ContractExpires attribute, while StaffList includes the additional DiscountEligible attribute. When configured for view row polymorphism as described in the next section, a client can work with the results of the PersonList view object using:

  • PersonListRow interface for view rows related to persons

  • SupplierListRow interface for view rows related to suppliers

  • StaffListRow interface for view rows related to staff

As you'll see, this allows the client to access the additional attributes and view row methods that are specific to a given subtype of view row.

Figure 39-11 Hierarchy of View Object Subtypes Enables View Row Polymorphism

Subtype hierarchy enables row polymorphism

39.6.6 How to Create a View Object with Polymorphic View Rows

To create a view object with polymorphic view rows, follow these steps:

  1. In the Application Navigator, double-click the view object that you want to be the base.

    In the example above, the PersonList view object is the base.

  2. In the overview editor, click the Attributes navigation tab and select a discriminator attribute for the view row, and click the Edit selected attribute(s) button.

  3. In the Edit Attribute dialog, give the discriminator attribute a default value and and check the Discriminator checkbox to mark the attribute as the one that distinguishes which view row interface to use.

    You must supply a value for the Value field that matches the attribute value for which you expect the base view object's view row interface to be used. For example, in the PersonList view object, you would mark the PersonTypeCode attribute as the discriminator attribute and supply a default value of "person".

  4. Enable a custom view row class for the base view object, and expose at least one method on the client row interface. This can be one or all of the view row attribute accessor methods, as well as any custom view row methods.

  5. Create a new view object that extends the base view object

    In the example above, SupplierList extends the base PersonList view object.

  6. Enable a custom view row class for the extended view object.

    If appropriate, add additional custom view row methods or override custom view row methods inherited from the parent view object's row class.

  7. Supply a distinct value for the discriminator attribute in the extended view object.

    The SupplierList view object provides the value of "SUPP" for the PersonTypeCode discriminator attribute.

  8. Repeat steps 4-6 to add additional extended view objects as needed.

    For example, the StaffList view object is a second one that extends PersonList. It supplies the value "STAFF" for the PersonTypeCode discriminator attribute.

After setting up the view object hierarchy, you need to define the list of view object subtypes that participate in the view row polymorphism. To accomplish this, do the following:

  1. Add an instance of each type of view object in the hierarchy to the data model of an application module.

    For example, the PersonModule application module in the example has instances of PersonList, SupplierList, and StaffList view objects.

  2. In the overview editor for the application module, click the Data Model navigation tab and click the Subtypes button.

  3. In the Subtypes dialog that appears, shuttle the desired view object subtypes that you want to participate in view row polymorphism from the Available to the Selected list, and click OK

39.6.7 What You May Need to Know About Polymorphic View Rows

This section provides additional information to help you work with polymorphic view rows.

39.6.7.1 Selecting Subtype-Specific Attributes in Extended View Objects

When you create an extended view object, it inherits the entity usage of its parent. If the parent view object's entity usage is based on an entity object with subtypes in your domain layer, you may want your extended view object to work with one of these subtypes instead of the inherited parent entity usage type. Two reasons you might want to do this are:

  1. To select attributes that are specific to the entity subtype

  2. To be able to write view row methods that delegate to methods specific to the entity subtype

In order to do this, you need to override the inherited entity usage to refer to the desired entity subtype. To do this, perform these steps in the overview editor for your extended view object:

  1. In the Application Navigator, double-click the view object.

  2. In the overview editor, click the Entity Objects navigation tab and verify that you are working with an extended entity usage.

    For example, when creating the SupplierList view object that extends the PersonList view object, the entity usage with the alias ThePerson will initially display in the Selected list as: ThePerson(Person): extended. The type of the entity usage is in parenthesis, and the "extended" label confirms that the entity usage is currently inherited from its parent.

  3. Select the desired entity subtype in the Available list that you want to override the inherited one. It must be a subtype entity of the existing entity usage's type.

    For example, you would select the Supplier entity object in the Available list to overridden the inherited entity usage based on the Persons entity type.

  4. Click > to shuttle it to the Selected list

  5. Acknowledge the alert that appears, confirming that you want to override the existing, inherited entity usage.

When you have performed these steps, the Selected list updates to reflect the overridden entity usage. For example, for the SupplierList view object, after overriding the Persons-based entity usage with the Supplier entity subtype, it updates to show: ThePerson (Supplier): overridden.

After overriding the entity usage to be related to an entity subtype, you can then use the Attributes tab of the editor to select additional attributes that are specific to the subtype. For example, the SupplierList view object includes the additional attribute named ContractExpires that is specific to the Supplier entity object.

39.6.7.2 Delegating to Subtype-Specific Methods After Overriding the Entity Usage

After overriding the entity usage in an extended view object to reference a subtype entity, you can write view row methods that delegate to methods specific to the subtype entity class. Example 39-19 shows the code for a performSupplierFeature() method in the custom view row class for the SupplierList view object. It casts the return value from the getThePerson() entity row accessor to the subtype SupplierImpl, and then invokes the performSupplierFeature() method that is specific to Supplier entity objects.

Example 39-19 View Row Method Delegating to Method in Subtype Entity

// In SupplierListRowImpl.java
public void performSupplierFeature() {
   SupplierImpl supplier = (SupplierImpl)getThePerson();
   supplier.performSupplierFeature();
}

Note:

You need to perform the explicit cast to the entity subtype here because JDeveloper does not yet take advantage of the JDK feature called covariant return types that would allow a subclass like SupplierListRowImpl to override a method like getThePerson() and change its return type.

39.6.7.3 Working with Different View Row Interface Types in Client Code

Example 39-20 shows the interesting lines of code from a TestViewRowPolymorphism class that performs the following steps:

  1. Iterates over the rows in the PersonList view object.

    For each row in the loop, it uses Java's instanceof operator to test whether the current row is an instance of the StaffListRow or the SupplierListRow.

  2. If the row is a StaffListRow, then cast it to this more specific type and:

    • Call the performStaffFeature() method specific to the StaffListRow interface, and

    • Access the value of the DiscountEligible attribute that is specific to the StaffList view object.

  3. If the row is a SupplierListRow, then cast it to this more specific type and:

    • Call the performSupplierFeature() method specific to the SupplierListRow interface, and

    • Access the value of the ContractExpires attribute that is specific to the SupplierList view object.

  4. Otherwise, just call a method on the PersonListRow

Example 39-20 Using View Row Polymorphism in Client Code

// In TestViewRowPolymorphism.java
ViewObject vo = am.findViewObject("PersonList");
vo.executeQuery();
// 1. Iterate over the rows in the PersonList view object
while (vo.hasNext()) {
  PersonListRow Person = (PersonListRow)vo.next();
  System.out.print(Person.getEmail()+"->");
  if (Person instanceof StaffListRow) {
    // 2. If the row is a StaffListRow, cast it
    StaffListRow mgr = (StaffListRow)Person;
    mgr.performStaffFeature();       
    System.out.println("Discount Status: "+staff.getDiscountEligible());
  }
  else if (Person instanceof SupplieristRow) {
    // 3. If the row is a StaffListRow, cast it
    SupplierListRow tech = (SupplierListRow)Person;
    supplier.performSupplierFeature();       
    System.out.println("Contract expires: "+tech.getContractExpires());        
  }
  else {
    // 4. Otherwise, just call a method on the PersonListRow 
    Person.performPersonFeature();
  }
}

Running the code in Example 39-20 produces the following output:

daustin->## performSupplierFeature called
Contract expires: 2006-05-09
hbaer->## performPersonFeature as Person
:
sking->## performStaffFeature called
Discount Status: Y
:

This illustrates that by using the view row polymorphism feature the client was able to distinguish between view rows of different types and access methods and attributes specific to each subtype of view row.

39.6.7.4 View Row Polymorphism and Polymorphic Entity Usage are Orthogonal

While often even more useful when used together, the view row polymorphism and the polymorphic entity usage features are distinct and can be used separately. In particular, the view row polymorphism feature can be used for read-only view objects, as well as for entity-based view objects. When you combine both mechanisms, you can have both the entity row part being polymorphic, as well as the view row type.

Note to use view row polymorphism with either view objects or entity objects, you must configure the discriminator attribute property separately for each. This is necessary because read-only view objects contain no related entity usages from which to infer the discriminator information.

In summary, to use view row polymorphism:

  1. Configure an attribute to be the discriminator at the view object level in the root view object in an inheritance hierarchy.

  2. Have a hierarchy of inherited view objects each of which provides a distinct value for the "Default Value" property of that view object level discriminator attribute.

  3. List the subclassed view objects in this hierarchy in the application module's list of Subtypes.

Whereas, to create a view object with a polymorphic entity usage:

  1. Configure an attribute to be the discriminator at the entity object level in the root entity object in an inheritance hierarchy.

  2. Have a hierarchy of inherited entity objects, each of which overrides and provides a distinct value for the "Default Value" property of that entity object level discriminator attribute.

  3. List the subclassed entity objects in a view object's list of Subtypes.

39.7 Reading and Writing XML

The Extensible Markup Language (XML) standard from the Worldwide Web Consortium (W3C) defines a language-neutral approach for electronic data exchange. Its rigorous set of rules enables the structure inherent in data to be easily encoded and unambiguously interpreted using human-readable text documents.

View objects support the ability to write these XML documents based on their queried data. View objects also support the ability to read XML documents in order to apply changes to data including inserts, updates, and deletes. When you've introduced view links, this XML capability supports reading and writing multi-level nested information for master-detail hierarchies of any complexity. While the XML produced and consumed by view objects follows a canonical format, you can combine the view object's XML features with XML Stylesheet Language Transformations (XSLT) to easily convert between this canonical XML format and any format you need to work with.

Note:

The example in this section refers to the ReadingAndWritingXML project in the AdvancedViewObjectsExamples application workspace in the StandaloneExamples module of the Fusion Order Demo application.

39.7.1 How to Produce XML for Queried Data

To produce XML from a view object, use the writeXML() method. If offers two ways to control the XML produced:

  1. For precise control over the XML produced, you can specify a view object attribute map indicating which attributes should appear, including which view link accessor attributes should be accessed for nested, detail information:

    Node writeXML(long options, HashMap voAttrMap)
    
  2. To producing XML that includes all attributes, you can simply specify a depth-level that indicates how many levels of view link accessor attributes should be traversed to produce the result:

    Node writeXML(int depthCount, long options)
    

The options parameter is a integer flag field that can be set to one of the following bit flags:

  • XMLInterface.XML_OPT_ALL_ROWS

    Includes all rows in the view object's row set in the XML.

  • XMLInterface.XML_OPT_LIMIT_RANGE

    Includes only the rows in the current range in the XML.

Using the logical OR operation, you can combine either of the above flags with the XMLInterface.XML_OPT_ASSOC_CONSISTENT flag when you want to include new, unposted rows in the current transaction in the XML output.

Both versions of the writeXML() method accept an optional third argument which is an XSLT stylesheet that, if supplied, is used to transform the XML output before returning it.

39.7.2 What Happens When You Produce XML

When you produce XML using writeXML(), the view object begins by creating a wrapping XML element whose default name matches the name of the view object definition. For example, for a Persons view object in the devguide.advanced.xml.queries package, the XML produces will be wrapped in an outermost Persons tag.

Then, it converts the attribute data for the appropriate rows into XML elements. By default, each row's data is wrapped in an row element whose name is the name of the view object with the Row suffix. For example, each row of data from a view object named Persons is wrapped in an PersonsRow element. The elements representing the attribute data for each row appear as nested children inside this row element.

If any of the attributes is a view link accessor attribute, and if the parameters passed to writeXML() enable it, the view object will include the data for the detail rowset returned by the view link accessor. This nested data is wrapped by an element whose name is determined by the name of the view link accessor attribute. The return value of the writeXML() method is an object that implements the standard W3C Node interface, representing the root element of the generated XML.

Note:

The writeXML() method uses view link accessor attributes to programmatically access detail collections. It does not require adding view link instances in the data model.

For example, to produce an XML element for all rows of a Persons view object instance, and following view link accessors as many levels deep as exists, Example 39-21 shows the code required.

Example 39-21 Generating XML for All Rows of a View Object to All View Link Levels

ViewObject vo = am.findViewObject("PersonsView");
printXML(vo.writeXML(-1,XMLInterface.XML_OPT_ALL_ROWS));

The Persons view object is linked to a Orders view object showing the orders created by that person. In turn, the Orders view object is linked to a OrderItems view object providing details on the items ordered by customers. Running the code in Example 39-21 produces the XML shown in Example 39-22, reflecting the nested structure defined by the view links.

Example 39-22 XML from a Persons View Object with Two Levels of View Linked Details

 ... 
 <PersonsViewRow>
      <PersonId>111</PersonId>
      <PrincipalName>ISCIARRA</PrincipalName>
      <FirstName>Ismael</FirstName>
      <LastName>Sciarra</LastName>
      <PersonTypeCode>CUST</PersonTypeCode>
      <ProvisionedFlag>N</ProvisionedFlag>
      <PrimaryAddressId>42</PrimaryAddressId>
      <MembershipId>2</MembershipId>
      <Email>ISCIARRA</Email>
      <ConfirmedEmail>ISCIARRA</ConfirmedEmail>
      <PhoneNumber>228.555.0126</PhoneNumber>
      <DateOfBirth>1971-09-30</DateOfBirth>
      <MaritalStatusCode>SING</MaritalStatusCode>
      <Gender>M</Gender>
      <ContactableFlag>Y</ContactableFlag>
      <ContactByAffilliatesFlag>Y</ContactByAffilliatesFlag>
      <CreatedBy>SEED_DATA</CreatedBy>
      <CreationDate>2008-08-15 11:26:36.0</CreationDate>
      <LastUpdatedBy>SEED_DATA</LastUpdatedBy>
      <LastUpdateDate>2008-08-15 11:26:36.0</LastUpdateDate>
      <ObjectVersionId>1</ObjectVersionId>
      <OrdersView>
         <OrdersViewRow>
            <OrderId>1017</OrderId>
            <OrderDate>2008-08-06 11:28:26.0</OrderDate>
            <OrderStatusCode>STOCK</OrderStatusCode>
            <OrderTotal>1649.92</OrderTotal>
            <CustomerId>111</CustomerId>
            <ShipToAddressId>8</ShipToAddressId>
            <ShippingOptionId>2</ShippingOptionId>
            <PaymentOptionId>1006</PaymentOptionId>
            <DiscountId>3</DiscountId>
            <FreeShippingFlag>Y</FreeShippingFlag>
            <CustomerCollectFlag>Y</CustomerCollectFlag>
            <CollectionWarehouseId>102</CollectionWarehouseId>
            <GiftwrapFlag>N</GiftwrapFlag>
            <CreatedBy>0</CreatedBy>
            <CreationDate>2008-08-15 11:28:26.0</CreationDate>
            <LastUpdatedBy>0</LastUpdatedBy>
            <LastUpdateDate>2008-08-15 11:28:26.0</LastUpdateDate>
            <ObjectVersionId>0</ObjectVersionId>
            <OrderItemsView>
               <OrderItemsViewRow>
                  <OrderId>1017</OrderId>
                  <LineItemId>1</LineItemId>
                  <ProductId>22</ProductId>
                  <Quantity>1</Quantity>
                  <UnitPrice>199.95</UnitPrice>
                  <CreatedBy>0</CreatedBy>
                  <CreationDate>2008-08-15 11:32:26.0</CreationDate>
                  <LastUpdatedBy>0</LastUpdatedBy>
                  <LastUpdateDate>2008-08-15 11:32:26.0</LastUpdateDate>
                  <ObjectVersionId>0</ObjectVersionId>
               </OrderItemsViewRow>
               <OrderItemsViewRow>
                  <OrderId>1017</OrderId>
                  <LineItemId>2</LineItemId>
                  <ProductId>9</ProductId>
                  <Quantity>1</Quantity>
                  <UnitPrice>129.99</UnitPrice>
                  <CreatedBy>0</CreatedBy>
                  <CreationDate>2008-08-15 11:32:27.0</CreationDate>
                  <LastUpdatedBy>0</LastUpdatedBy>
                  <LastUpdateDate>2008-08-15 11:32:27.0</LastUpdateDate>
                  <ObjectVersionId>0</ObjectVersionId>
               </OrderItemsViewRow>
               <OrderItemsViewRow>
                  <OrderId>1017</OrderId>
                  <LineItemId>3</LineItemId>
                  <ProductId>36</ProductId>
                  <Quantity>2</Quantity>
                  <UnitPrice>659.99</UnitPrice>
                  <CreatedBy>0</CreatedBy>
                  <CreationDate>2008-08-15 11:32:27.0</CreationDate>
                  <LastUpdatedBy>0</LastUpdatedBy>
                  <LastUpdateDate>2008-08-15 11:32:27.0</LastUpdateDate>
                  <ObjectVersionId>0</ObjectVersionId>
               </OrderItemsViewRow>
            </OrderItemsView>
         </OrdersViewRow>
      </OrdersView>
   </PersonsViewRow>
...

39.7.3 What You May Need to Know About Reading and Writing XML

This section provides additional information to help you work with XML.

39.7.3.1 Controlling XML Element Names

You can use the Property Inspector to change the default XML element names used in the view object's canonical XML format by setting several properties. To accomplish this, open the overview editor for the view object, then:

  • Select the attribute on the Attributes page and in the Property Inspector, select the Custom Properties navigation tab and set the custom attribute-level property named Xml Element to a value SomeOtherName to change the XML element name used for that attribute to <SomeOtherName>

    For example, the Email attribute in the Persons view object defines this property to change the XML element you see in Example 39-22 to be <EmailAddress> instead of <Email>.

  • Select the General navigation tab in the Property Inspector and set the custom view object-level property named Xml Row Element to a value SomeOtherRowName to change the XML element name used for that view object to <SomeOtherRowName>.

    For example, the Persons view object defines this property to change the XML element name for the rows you see in Example 39-22 to be <Person> instead of <PersonsRow>.

  • To change the name of the element names that wrapper nested row set data from view link attribute accessors, use the View Link Properties dialog. To open the dialog, in the view link overview editor, click the Edit accessors icon on the Accessors section of Relationship page. Enter the desired name of the view link accessor attribute in the Accessor Name field.

39.7.3.2 Controlling Element Suppression for Null-Valued Attributes

By default, if a view row attribute is null, then its corresponding element is omitted from the generated XML. Select the attribute on the Attributes page of the overview editor and in the Property Inspector, select the Custom Properties navigation tab and set the custom attribute-level property named Xml Explicit Null to any value (e.g. "true" or "yes") to cause an element to be included for the attribute if its value is null. For example, if an attribute named AssignedDate has this property set, then a row containing a null assigned date will contain a corresponding AssignedDate null="true"/ element. If you want this behavior for all attributes of a view object, you can define the Xml Explicit Null custom property at the view object level as a shortcut for defining it on each attribute.

39.7.3.3 Printing or Searching the Generated XML Using XPath

Two of the most common things you might want to do with the XML Node object returned from writeXML() are:

  1. Printing the node to its serialized text representation — to send across the network or save in a file, for example

  2. Searching the generated XML using W3C XPath expressions

Unfortunately, the standard W3C Document Object Model (DOM) API does not include methods for doing either of these useful operations. But there is hope. Since ADF Business Components uses the Oracle XML parser's implementation of the DOM, you can cast the Node return value from writeXML() to the Oracle specific classes XMLNode or XMLElement (in the oracle.xml.parser.v2 package) to access additional useful functionality like:

  • Printing the XML element to its serialized text format using the print() method

  • Searching the XML element in memory with XPath expressions using the selectNodes() method

  • Finding the value of an XPath expression related to the XML element using the valueOf() method.

Example 39-23 shows the printXML() method in the TestClientWriteXML. It casts the Node parameter to an XMLNode and calls the print() method to dump the XML to the console.

Example 39-23 Using the XMLNode's print() Method to Serialize XML

// In TestClientWriteXML.java
private static void printXML(Node n) throws IOException {
  ((XMLNode)n).print(System.out);
}

39.7.3.4 Using the Attribute Map For Fine Control Over Generated XML

When you need fine control over which attributes appear in the generated XML, use the version of the writeXML() method that accepts a HashMap. Example 39-24 shows the interesting lines from a TestClientWriteXML class that use this technique. After creating the HashMap, you put String[]-valued entries into it containing the names of the attributes you want to include in the XML, keyed by the fully-qualified name of the view definition those attributes belong to. The example includes the PersonId, Email, PersonTypeCode, and OrdersView attributes from the Persons view object, and the OrderId, OrderStatusCode, and OrderTotal attributes from the OrdersView view object.

Note:

For upward compatibility reasons with earlier versions of ADF Business Components the HashMap expected by the writeXML() method is the one in the com.sun.java.util.collections package.

While processing the view rows for a given view object instance:

  • If an entry exists in the attribute map with a key matching the fully-qualified view definition name for that view object, then only the attributes named in the corresponding String array are included in the XML.

    Furthermore, if the string array includes the name of a view link accessor attribute, then the nested contents of its detail row set are included in the XML. If a view link accessor attribute name does not appear in the string array, then the contents of its detail row set are not included.

  • If no such entry exists in the map, then all attributes for that row are included in the XML.

Example 39-24 Using a View Definition Attribute Map for Fine Control Over Generated XML

HashMap viewDefMap = new HashMap();
viewDefMap.put("devguide.advanced.xml.queries.PersonsView",
        new String[]{"PersonId","Email", "PersonTypeCode",
                     "OrdersView" /* View link accessor attribute */
                     });
viewDefMap.put("devguide.advanced.xml.queries.OrdersView",
        new String[]{"OrderId","OrderStatusCode","OrderTotal"});  
printXML(vo.writeXML(XMLInterface.XML_OPT_ALL_ROWS,viewDefMap)); 

Running the example produces the XML shown in Example 39-25, including only the exact attributes and view link accessors indicated by the supplied attribute map.

Example 39-25 XML from a Users View Object Produced Using an Attribute Map

<OrdersViewRow>
            <OrderId>1033</OrderId>
            <OrderDate>2009-01-30 13:59:39.0</OrderDate>
            <OrderShippedDate>2009-02-02 13:59:39.0</OrderShippedDate>
            <OrderStatusCode>COMPLETE</OrderStatusCode>
            <OrderTotal>2677.96</OrderTotal>
            <CustomerId>108</CustomerId>
            <ShipToAddressId>20</ShipToAddressId>
            <ShippingOptionId>1</ShippingOptionId>
            <PaymentOptionId>1016</PaymentOptionId>
            <DiscountId>3</DiscountId>
            <FreeShippingFlag>Y</FreeShippingFlag>
            <CustomerCollectFlag>Y</CustomerCollectFlag>
            <CollectionWarehouseId>101</CollectionWarehouseId>
            <GiftwrapFlag>N</GiftwrapFlag>
            <CreatedBy>0</CreatedBy>
            <CreationDate>2009-02-23 13:59:39.0</CreationDate>
            <LastUpdatedBy>0</LastUpdatedBy>
            <LastUpdateDate>2009-02-23 13:59:39.0</LastUpdateDate>
            <ObjectVersionId>0</ObjectVersionId>
            <OrderItemsView>
               <OrderItemsViewRow>
                  <OrderId>1033</OrderId>
                  <LineItemId>1</LineItemId>
                  <ProductId>10</ProductId>
                  <Quantity>3</Quantity>
                  <UnitPrice>225.99</UnitPrice>
                  <CreatedBy>0</CreatedBy>
                  <CreationDate>2009-02-23 13:59:40.0</CreationDate>
                  <LastUpdatedBy>0</LastUpdatedBy>
                  <LastUpdateDate>2009-02-23 13:59:40.0</LastUpdateDate>
                  <ObjectVersionId>0</ObjectVersionId>
               </OrderItemsViewRow>
               <OrderItemsViewRow>
                  <OrderId>1033</OrderId>
                  <LineItemId>2</LineItemId>
                  <ProductId>1</ProductId>
                  <Quantity>1</Quantity>
                  <UnitPrice>1999.99</UnitPrice>
                  <CreatedBy>0</CreatedBy>
                  <CreationDate>2009-02-23 13:59:40.0</CreationDate>
                  <LastUpdatedBy>0</LastUpdatedBy>
                  <LastUpdateDate>2009-02-23 13:59:40.0</LastUpdateDate>
                  <ObjectVersionId>0</ObjectVersionId>
               </OrderItemsViewRow>
            </OrderItemsView>
</OrdersViewRow>
...

39.7.3.5 Use the Attribute Map Approach with Bi-Directional View Links

If your view objects are related through a view link that you have configured to be bi-directional, then you must use the writeXML() approach that uses the attribute map. If you were to use the writeXML() approach in the presence of bi-directional view links and were to supply a maximum depth of -1 to include all levels of view links that exist, the writeXML() method will go into an infinite loop as it follows the bi-directional view links back and forth, generating deeply nested XML containing duplicate data until it runs out of memory. Use writeXML() with an attribute map instead in this situation. Only by using this approach can you control which view link accessors are included in the XML and which are not to avoid infinite recursion while generating the XML.

39.7.3.6 Transforming Generated XML Using an XSLT Stylesheet

When the canonical XML format produced by writeXML() does not meet your needs, you can supply an XSLT stylesheet as an optional argument. It will produce the XML as it would normally, but then transform that result using the supplied stylesheet before returning the final XML to the caller.

Consider the XSLT stylesheet shown in Example 39-26. It is a simple transformation with a single template that matches the root element of the generated XML from Example 39-25 to create a new CustomerEmailAddresses element in the result. The template uses the xsl:for-each instruction to process all PersonsView elements that contain more than one OrdersViewRow child element inside a nested OrdersViews element. For each PersonsView element that qualifies, it creates a Customer element in the result whose Contact attribute is populated from the value of the Email child element of the PersonsView.

Example 39-26 XSLT Stylesheet to Transform Generated XML Into Another Format

<?xml version="1.0" encoding="windows-1252" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <CustomerEmailAddresses>
      <xsl:for-each
           select="/PersonsView/PersonsViewRow[count(OrdersView/OrdersViewRow) >
                                                       1]">
        <xsl:sort select="Email"/>
        <Customer Contact="{Email}"/>
      </xsl:for-each>
    </CustomerEmailAddresses>
  </xsl:template>
</xsl:stylesheet>

Example 39-27 shows the interesting lines from a TestClientWriteXML class that put this XSLT stylesheet into action when calling writeXML().

Example 39-27 Passing an XSLT Stylesheet to writeXML() to Transform the Resulting XML

// In TestClientWriteXML.java
XSLStylesheet xsl = getXSLStylesheet();
printXML(vo.writeXML(XMLInterface.XML_OPT_ALL_ROWS,viewDefMap,xsl));

Running the code in Example 39-27 produces the transformed XML shown here:

<CustomerEmailAddresses>
   <Customer Contact="dfaviet"/>
   <Customer Contact="jchen"/>
   <Customer Contact="ngreenbe"/>
</CustomerEmailAddresses>

The getXSLStylesheet() helper method shown in Example 39-28 is also interesting to study since it illustrates how to read a resource like an XSLT stylesheet from the classpath at runtime. The code expects the Example.xsl stylesheet to be in the same directory as the TestClientWriteXML class. By referencing the Class object for the TestClientWriteXML class using the .class operator, the code uses the getResource() method to get a URL to the resource. Then, it passes the URL to the newXSLStylesheet() method of the XSLProcessor class to create a new XSLStylesheet object to return. That object represents the compiled version of the XSLT stylesheet read in from the *.xslfile.

Example 39-28 Reading an XSLT Stylesheet as a Resource from the Classpath

private static XSLStylesheet getXSLStylesheet()
        throws XMLParseException, SAXException,IOException,XSLException {
  String xslurl = "Example.xsl";
  URL xslURL = TestClientWriteXML.class.getResource(xslurl);
  XSLProcessor xslProc = new XSLProcessor();
  return xslProc.newXSLStylesheet(xslURL);
}

Note:

When working with resources like XSLT stylesheets that you want to be included in the output directory along with your compiled Java classes and XML metadata, you can use the Compiler page of the Project Properties dialog to update the Copy File Types to Output Directory field to include .xsl in the semicolon-separated list.

39.7.3.7 Generating XML for a Single Row

In addition to calling writeXML() on a view object, you can call the same method with the same parameters and options on any Row as well. If the Row object on which you call writeXML() is a entity row, you can bitwise-OR the additional XMLInterface.XML_OPT_CHANGES_ONLY flag if you only want the changed entity attributes to appear in the XML.

39.7.4 How to Consume XML Documents to Apply Changes

To have a view object consume an XML document to process inserts, updates, and deletes, use the readXML() method:

void readXML(Element elem, int depthcount)

The canonical format expected by readXML() is the same as what would be produced by a call to the writeXML() method on the same view object. If the XML document to process does not correspond to this canonical format, you can supply an XSLT stylesheet as an optional third argument to readXML() to transform the incoming XML document into the canonical format before it is read for processing.

39.7.5 What Happens When You Consume XML Documents

When a view object consumes an XML document in canonical format, it processes the document to recognize row elements, their attribute element children, and any nested elements representing view link accessor attributes. It processes the document recursively to a maximum level indicated by the depthcount parameter. Passing -1 for the depthcount to request that it process all levels of the XML document.

39.7.5.1 How ViewObject.readXML() Processes an XML Document

For each row element it recognizes, the readXML() method does the following:

  • Identifies the related view object to process the row.

  • Reads the children attribute elements to get the values of the primary key attributes for the row.

  • Performs a findByKey() using the primary key attributes to detect whether the row already exists or not.

  • If the row exists:

    • If the row element contains the marker attribute bc4j-action="remove", then the existing row is deleted.

    • Otherwise, the row's attributes are updated using the values in any attribute element children of the current row element in the XML

  • If the row does not exist, then a new row is created, inserted into the view object's rowset. Its attributes are populated using the values in any attribute element children of the current row element in the XML.

39.7.5.2 Using readXML() to Processes XML for a Single Row

The same readXML() method is also supported on any Row object. The canonical XML format it expects is the same format produced by a call to writeXML() on the same row. You can invoke readXML() method on a row to:

  • Update its attribute values from XML

  • Remove the row, if the bc4j-action="remove" marker attribute is present on the corresponding row element.

  • Insert, update, or delete any nested rows via view link accessors

Consider the XML document shown in Example 39-29. It is in the canonical format expected by a single row in the PersonsView view object. Nested inside the root PersonsViewRow element, the ConfirmedEmail attribute represents the customer's email address. The nested OrdersView element corresponds to the Orders view link accessor attribute and contains three OrdersViewRow elements. Each of these includes OrderId elements representing the primary key of a OrdersView row.

Example 39-29 XML Document in Canonical Format to Insert, Update, and Delete Rows

<PersonsViewRow>
   <!-- This will update Person's ConfirmedEmail attribute -->
   <PersonId>110</PersonId>
   <ConfirmedEmail>NewConfirmed</ConfirmedEmail>
   <OrdersView>
      <!-- This will be an update since it does exist -->
      <OrdersViewRow>
         <OrderId>1011</OrderId>
         <OrderStatusCode>SHIP</OrderStatusCode>
      </OrdersViewRow>
      <!-- This will be an insert since it doesn't exist -->
      <OrdersViewRow>
         <OrderId>1070</OrderId>
         <OrderStatusCode>PENDING</OrderStatusCode>
      </OrdersViewRow>
      <!-- This will be deleted -->
      <OrdersViewRow bc4j-action="remove">
         <OrderId>1026</OrderId>
      </OrdersViewRow>
   </OrdersView>
</PersonsViewRow>

Example 39-30 shows the interesting lines of code from a TestClientReadXML class that applies this XML datagram to a particular row in the PersonsView view object. TestClientReadXML class performs the following basic steps:

  1. Finds a target row by key (e.g. for customer "jchen").

  2. Shows the XML produced for the row before changes are applied.

  3. Obtains the parsed XML document with changes to apply using a helper method.

  4. Reads the XML document to apply changes to the row.

  5. Shows the XML with the pending changes applied.

    TestClientReadXML class is using the XMLInterface.XML_OPT_ASSOC_CONSISTENT flag described in Section 39.7.1, "How to Produce XML for Queried Data" to ensure that new, unposted rows are included in the XML.

Example 39-30 Applying Changes to an Existing Row with readXML()

ViewObject vo = am.findViewObject("CustomersView");
Key k = new Key(new Object[] { 110 });
// 1. Find a target row by key (e.g. for customer "jchen")
Row jchen = vo.findByKey(k, 1)[0];
// 2. Show the XML produced for the row before changes are applied
printXML(jchen.writeXML(-1, XMLInterface.XML_OPT_ALL_ROWS));
// 3. Obtain parsed XML document with changes to apply using helper method
Element xmlToRead = getInsertUpdateDeleteXMLGram();
printXML(xmlToRead);
// 4. Read the XML document to apply changes to the row
jchen.readXML(getInsertUpdateDeleteXMLGram(), -1);
// 5. Show the XML with the pending changes applied
printXML(jchen.writeXML(-1, XMLInterface.XML_OPT_ALL_ROWS | 
                              XMLInterface.XML_OPT_ASSOC_CONSISTENT));

Running the code in Example 39-30 initially displays the "before" version of John Chen's information. Notice that:

  • The ConfirmedEmail attribute has the value "JCHEN"

  • The status code for order 1011 has a level of "CANCEL"

  • There is an orders row for order 1026, and

  • There is no orders row related to order 1070.

<PersonsViewRow>
   <PersonId>110</PersonId>
   <PrincipalName>JCHEN</PrincipalName>
   <FirstName>John</FirstName>
   <LastName>Chen</LastName>
   <PersonTypeCode>CUST</PersonTypeCode>
   <ProvisionedFlag>N</ProvisionedFlag>
   <PrimaryAddressId>37</PrimaryAddressId>
   <MembershipId>1</MembershipId>
   <Email>JCHEN</Email>
   <ConfirmedEmail>JCHEN</ConfirmedEmail>
   <PhoneNumber>706.555.0103</PhoneNumber>
   <DateOfBirth>1967-09-28</DateOfBirth>
   <MaritalStatusCode>MARR</MaritalStatusCode>
   <Gender>M</Gender>
   <ContactableFlag>Y</ContactableFlag>
   <ContactByAffilliatesFlag>Y</ContactByAffilliatesFlag>
   <CreatedBy>SEED_DATA</CreatedBy>
   <CreationDate>2009-02-23 13:59:38.0</CreationDate>
   <LastUpdatedBy>SEED_DATA</LastUpdatedBy>
   <LastUpdateDate>2009-02-23 13:59:38.0</LastUpdateDate>
   <ObjectVersionId>1</ObjectVersionId>
   <OrdersView>
      <OrdersViewRow>
         <OrderId>1011</OrderId>
         <OrderDate>2009-02-17 13:59:38.0</OrderDate>
         <OrderStatusCode>CANCEL</OrderStatusCode>
         <OrderTotal>99.99</OrderTotal>
         <CustomerId>110</CustomerId>
         <ShipToAddressId>9</ShipToAddressId>
         <ShippingOptionId>2</ShippingOptionId>
         <PaymentOptionId>1005</PaymentOptionId>
         <DiscountId>5</DiscountId>
         <FreeShippingFlag>N</FreeShippingFlag>
         <CustomerCollectFlag>N</CustomerCollectFlag>
         <GiftwrapFlag>N</GiftwrapFlag>
         <CreatedBy>0</CreatedBy>
         <CreationDate>2009-02-23 13:59:38.0</CreationDate>
         <LastUpdatedBy>anonymous</LastUpdatedBy>
         <LastUpdateDate>2009-02-23 13:59:38.0</LastUpdateDate>
         <ObjectVersionId>8</ObjectVersionId>
         <OrderItemsView>
            <OrderItemsViewRow>
               <OrderId>1011</OrderId>
               <LineItemId>1</LineItemId>
               <ProductId>18</ProductId>
               <Quantity>1</Quantity>
               <UnitPrice>99.99</UnitPrice>
               <CreatedBy>0</CreatedBy>
               <CreationDate>2009-02-23 13:59:39.0</CreationDate>
               <LastUpdatedBy>0</LastUpdatedBy>
               <LastUpdateDate>2009-02-23 13:59:39.0</LastUpdateDate>
               <ObjectVersionId>0</ObjectVersionId>
            </OrderItemsViewRow>
         </OrderItemsView>
      </OrdersViewRow>
...

After applying the changes from the XML document using readXML() to the row and printing its XML again using writeXML() you see that:

  • The ConfirmedEmail is now "NewConfirmed"

  • A new orders row for order 1070 got created.

  • The status code for order 1011 has a level of "SHIP", and

  • The orders row for order 1026 is removed

<PersonsViewRow>
   <PersonId>110</PersonId>
   <PrincipalName>JCHEN</PrincipalName>
   <FirstName>John</FirstName>
   <LastName>Chen</LastName>
   <PersonTypeCode>CUST</PersonTypeCode>
   <ProvisionedFlag>N</ProvisionedFlag>
   <PrimaryAddressId>37</PrimaryAddressId>
   <MembershipId>1</MembershipId>
   <Email>JCHEN</Email>
   <ConfirmedEmail>NewConfirmed</ConfirmedEmail>
   <PhoneNumber>706.555.0103</PhoneNumber>
   <DateOfBirth>1967-09-28</DateOfBirth>
   <MaritalStatusCode>MARR</MaritalStatusCode>
   <Gender>M</Gender>
   <ContactableFlag>Y</ContactableFlag>
   <ContactByAffilliatesFlag>Y</ContactByAffilliatesFlag>
   <CreatedBy>0</CreatedBy>
   <CreationDate>2009-02-23 13:59:38.0</CreationDate>
   <LastUpdatedBy>0</LastUpdatedBy>
   <LastUpdateDate>2009-02-23 13:59:38.0</LastUpdateDate>
   <ObjectVersionId>1</ObjectVersionId>
   <OrdersView>
      <OrdersViewRow>
         <OrderId>1070</OrderId>
         <OrderDate>2009-06-22</OrderDate>
         <OrderStatusCode>PENDING</OrderStatusCode>
         ...
      </OrdersViewRow>
      <OrdersViewRow>
         <OrderId>1011</OrderId>
         <OrderDate>2009-02-17 13:59:38.0</OrderDate>
         <OrderStatusCode>SHIP</OrderStatusCode>
         <OrderTotal>99.99</OrderTotal>
         <CustomerId>110</CustomerId>
         <ShipToAddressId>9</ShipToAddressId>
         <ShippingOptionId>2</ShippingOptionId>
         <PaymentOptionId>1005</PaymentOptionId>
         <DiscountId>5</DiscountId>
         <FreeShippingFlag>N</FreeShippingFlag>
         <CustomerCollectFlag>N</CustomerCollectFlag>
         <GiftwrapFlag>N</GiftwrapFlag>
         <CreatedBy>0</CreatedBy>
         <CreationDate>2009-02-23 13:59:38.0</CreationDate>
         <LastUpdatedBy>0</LastUpdatedBy>
         <LastUpdateDate>2009-02-23 13:59:38.0</LastUpdateDate>
         <ObjectVersionId>0</ObjectVersionId>
         <OrderItemsView>
            <OrderItemsViewRow>
               <OrderId>1011</OrderId>
               <LineItemId>1</LineItemId>
               <ProductId>18</ProductId>
               <Quantity>1</Quantity>
               <UnitPrice>99.99</UnitPrice>
               <CreatedBy>0</CreatedBy>
               <CreationDate>2009-02-01 13:59:39.0</CreationDate>
               <LastUpdatedBy>0</LastUpdatedBy>
               <LastUpdateDate>2009-02-01 13:59:39.0</LastUpdateDate>
               <ObjectVersionId>0</ObjectVersionId>
            </OrderItemsViewRow>
         </OrderItemsView>
      </OrdersViewRow>
...

Note:

The example illustrated using readXML() to apply changes to a single row. If the XML document contained a wrapping PersonsView row, including the primary key attribute in each of its one or more nested PersonsViewRow elements, then that document could be processed using the readXML() method on the PersonsView view object for handling operations for multiple PersonsView rows.

39.8 Using Programmatic View Objects for Alternative Data Sources

By default view objects read their data from the database and automate the task of working with the Java Database Connectivity (JDBC) layer to process the database result sets. However, by overriding appropriate methods in its custom Java class, you can create a view object that programmatically retrieves data from alterative data sources like a REF CURSOR, an in-memory array, or a Java *.properties file, to name a few.

39.8.1 How to Create a Read-Only Programmatic View Object

To create a read-only programmatic view object, you use the Create View Object wizard.

To create the read-only programmatic view object:

  1. In the Application Navigator, right-click the project in which you want to create the view object and choose New.

  2. In the New Gallery, expand Business Tier, select ADF Business Components and then View Object, and click OK.

  3. In the Create View Object wizard, in the Name page, provide a name and package for the view object. For the data source, select Rows populated programmatically, not based on a query.

  4. In the Attributes page, click New one or more times to define the view object attributes your programmatic view object requires.

  5. In the Attribute Settings page, adjust any setting you may need to for the attributes you defined.

  6. In the Java page, select Generate View Object Class to enable a custom view object class (ViewObjImpl) to contain your code.

  7. Click Finish to create the view object.

In your view object's custom Java class, override the methods described in Section 39.8.3, "Key Framework Methods to Override for Programmatic View Objects" to implement your custom data retrieval strategy.

39.8.2 How to Create an Entity-Based Programmatic View Object

To create a entity-based view object with programmatic data retrieval, create the view object in the normal way, enable a custom Java class for it, and override the methods described in the next section to implement your custom data retrieval strategy.

39.8.3 Key Framework Methods to Override for Programmatic View Objects

A programmatic view object typically overrides all of the following methods of the base ViewObjectImpl class to implement its custom strategy for retrieving data:

  • create()

    This method is called when the view object instance is created and can be used to initialize any state required by the programmatic view object. At a minimum, this overridden method will contain the following lines to ensure the programmatic view object has no trace of a SQL query related to it:

    // Wipe out all traces of a query for this VO
    getViewDef().setQuery(null);
    getViewDef().setSelectClause(null);
    setQuery(null);
    
  • executeQueryForCollection()

    This method is called whenever the view object's query needs to be executed (or re-executed).

  • hasNextForCollection()

    This method is called to support the hasNext() method on the row set iterator for a row set created from this view object. Your implementation returns true if you have not yet exhausted the rows to retrieve from your programmatic data source.

  • createRowFromResultSet()

    This method is called to populate each row of "fetched" data. Your implementation will call createNewRowForCollection() to create a new blank row and then populateAttributeForRow() to populate each attribute of data for the row.

  • getQueryHitCount()

    This method is called to support the getEstimatedRowCount() method. Your implementation returns a count, or estimated count, of the number of rows that will be retrieved by the programmatic view object's query.

  • protected void releaseUserDataForCollection()

    Your code can store and retrieve a user data context object with each row set. This method is called to allow you to release any resources that may be associated with a row set that is being closed.

Since the view object component can be related to several active row sets at runtime, many of the above framework methods receive an Object parameter named qc in which the framework will pass the collection of rows in question that your code is supposed to be filling, as well as the array of bind variable values that might affect which rows get populated into the specific collection.

You can store a user-data object with each collection of rows so your custom datasource implementation can associate any needed datasource context information. The framework provides the setUserDataForCollection() and getUserDataForCollection() methods to get and set this per-collection context information. Each time one of the overridden framework methods is called, you can use the getUserDataForCollection() method to retrieve the correct ResultSet object associated with the collection of rows the framework wants you to populate.

The examples in the following sections each override these methods to implement different kinds of programmatic view objects.

39.8.4 How to Create a View Object on a REF CURSOR

Sometimes your application might need to work with the results of a query that is encapsulated within a stored procedure. PL/SQL allows you to open a cursor to iterate through the results of a query, and then return a reference to this cursor to the client. This so-called REF CURSOR is a handle with which the client can then iterate the results of the query. This is possible even though the client never actually issued the original SQL SELECT statement.

Note:

The example in this section refers to the ViewObjectOnRefCursor project in the AdvancedViewObjectsExamples application workspace in the StandaloneExamples module of the Fusion Order Demo application.

Declaring a PL/SQL package with a function that returns a REF CURSOR is straightforward. For example, your package might look like this:

CREATE OR REPLACE PACKAGE RefCursorExample IS
  TYPE ref_cursor IS REF CURSOR;
  FUNCTION get_orders_for_customer(p_email VARCHAR2) RETURN ref_cursor;
  FUNCTION count_orders_for_customer(p_email VARCHAR2) RETURN NUMBER;
END RefCursorExample;

After defining an entity-based OrdersForCustomer view object with an entity usage for a Order entity object, go to its custom Java class OrdersForCustomerImpl.java. At the top of the view object class, define some constant Strings to hold the anonymous blocks of PL/SQL that you'll execute using JDBC CallableStatement objects to invoke the stored functions:

/*
 * Execute this block to retrieve the REF CURSOR
 */
 private static final String SQL = 
           "begin ? := RefCursorSample.get_orders_for_customer(?);end;";
/*
 * Execute this block to retrieve the count of orders that
 * would be returned if you executed the statement above.
 */
private static final String COUNTSQL =
           "begin ? := RefCursorSample.count_orders_for_customer(?);end;";

Then, override the methods of the view object as described in the following sections.

39.8.4.1 The Overridden create() Method

The create() method removes all traces of a SQL query for this view object.

protected void create() {
  getViewDef().setQuery(null);
  getViewDef().setSelectClause(null);
  setQuery(null); 
}

39.8.4.2 The Overridden executeQueryForCollection() Method

The executeQueryForCollection() method calls a helper method retrieveRefCursor() to execute the stored function and return the REF CURSOR return value, cast as a JDBC ResultSet. Then, it calls the helper method storeNewResultSet() that uses the setUserDataForCollection() method to store this ResultSet with the collection of rows for which the framework is asking to execute the query.

protected void executeQueryForCollection(Object qc,Object[] params,
                                         int numUserParams) { 
  storeNewResultSet(qc,retrieveRefCursor(qc,params));
  super.executeQueryForCollection(qc, params, numUserParams); 
}

The retrieveRefCursor() uses the helper method described in Section 37.5, "Invoking Stored Procedures and Functions" to invoke the stored function and return the REF CURSOR:

private ResultSet retrieveRefCursor(Object qc, Object[] params) {
  ResultSet rs = (ResultSet)callStoredFunction(OracleTypes.CURSOR,
                   "RefCursorExample.get_requests_for_customer(?)",
                   new Object[]{getNamedBindParamValue("CustEmail",params)});
  return rs ;
}

39.8.4.3 The Overridden createRowFromResultSet() Method

For each row that the framework needs fetched from the datasource, it will invoke your overridden createRowFromResultSet() method. The implementation retrieves the collection-specific ResultSet object from the user-data context, uses the createNewRowForCollection() method to create a new blank row in the collection, and then use the populateAttributeForRow() method to populate the attribute values for each attribute defined at design time in the view object overview editor.

protected ViewRowImpl createRowFromResultSet(Object qc, ResultSet rs) { 
  /*
   * We ignore the JDBC ResultSet passed by the framework (null anyway) and
   * use the resultset that we've stored in the query-collection-private
   * user data storage
   */
  rs = getResultSet(qc);
  
  /*
   * Create a new row to populate
   */
  ViewRowImpl r = createNewRowForCollection(qc);
  try {
    /*
     * Populate new row by attribute slot number for current row in Result Set
     */
    populateAttributeForRow(r,0, rs.getLong(1));
    populateAttributeForRow(r,1, rs.getString(2));
    populateAttributeForRow(r,2, rs.getString(3));
  }
  catch (SQLException s) {
    throw new JboException(s);
  }
  return r;
}

39.8.4.4 The Overridden hasNextForCollectionMethod()

The overridden implementation of the framework method hasNextForCollection() has the responsibility to return true or false based on whether there are more rows to fetch. When you've hit the end, you call the setFetchCompleteForCollection() to tell view object that this collection is done being populated.

protected boolean hasNextForCollection(Object qc) {
  ResultSet rs = getResultSet(qc);
  boolean nextOne = false;
  try {
    nextOne = rs.next();
    /*
     * When were at the end of the result set, mark the query collection
     * as "FetchComplete".
     */
    if (!nextOne) {
      setFetchCompleteForCollection(qc, true); 
      /*
       * Close the result set, we're done with it
       */
      rs.close();
    }
  }
  catch (SQLException s) {
   throw new JboException(s);
  }
  return nextOne;
}

39.8.4.5 The Overridden releaseUserDataForCollection() Method

Once the collection is done with its fetch-processing, the overridden releaseUserDataForCollection() method gets invoked and closes the ResultSet cleanly so no database cursors are left open.

  protected void releaseUserDataForCollection(Object qc, Object rs) {
     ResultSet userDataRS = getResultSet(qc);
     if (userDataRS != null) {
      try {
        userDataRS.close();
      } 
      catch (SQLException s) {
        /* Ignore */
      }   
    }
    super.releaseUserDataForCollection(qc, rs);
  }

39.8.4.6 The Overridden getQueryHitCount() Method

Lastly, in order to properly support the view object's getEstimatedRowCount() method, the overridden getQueryHitCount() method returns a count of the rows that would be retrieved if all rows were fetched from the row set. Here the code uses a CallableStatement to get the job done. Since the query is completely encapsulated behind the stored function API, the code also relies on the PL/SQL package to provide an implementation of the count logic as well to support this functionality.

public long getQueryHitCount(ViewRowSetImpl viewRowSet) {
  Object[] params = viewRowSet.getParameters(true);
  BigDecimal id = (BigDecimal)params[0];
  CallableStatement st = null;
  try {
    st = getDBTransaction().createCallableStatement(COUNTSQL,
                            DBTransaction.DEFAULT);
    /*
     * Register the first bind parameter as our return value of type CURSOR
     */
    st.registerOutParameter(1,Types.NUMERIC);
    /* 
     * Set the value of the 2nd bind variable to pass id as argument
     */ 
    if (id == null) st.setNull(2,Types.NUMERIC);
    else            st.setBigDecimal(2,id);
    st.execute();
    return st.getLong(1);
  }
  catch (SQLException s) {
    throw new JboException(s);
  }
  finally {try {st.close();} catch (SQLException s) {}}
}

39.9 Creating a View Object with Multiple Updatable Entities

Note:

The example in this section refers to the MultipleMasters project in the AdvancedViewObjectsExamples application workspace in the StandaloneExamples module of the Fusion Order Demo application.

When you create a view object with multiple entity usages, you can enable a secondary entity usage to be updatable by selecting it in the Selected list of the Entity Objects page of the view object overview editor and:

  • Deselecting the Reference checkbox

  • Selecting the Updatable checkbox

If you only plan to use the view object to update or delete existing data, then this is the only step required. The user can update attributes related to any of the non-reference, updatable entity usages and the view row will delegate the changes to the appropriate underlying entity rows.

However, if you need a view object with multiple updatable entities to support creating new rows and the association between the entity objects is not a composition, then you need to write a bit of code to enable that to work correctly.

Note:

You only need to write code to handle creating new rows when the association between the updatable entities is not a composition. If the association is a composition, then ADF Business Components handles this automatically.

When you call createRow() on a view object with multiple update entities, it creates new entity row parts for each updatable entity usage. Since the multiple entities in this scenario are related by an association, there are three pieces of code you might need to implement to ensure the new, associated entity rows can be saved without errors:

  1. You may need to override the postChanges() method on entity objects involved to control the correct posting order.

  2. If the primary key of the associated entity is populated by a database sequence using DBSequence, and if the multiple entity objects are associated but not composed, then you need to override the postChanges() and refreshFKInNewContainees() method to handle cascading the refreshed primary key value to the associated rows that were referencing the temporary value.

  3. You need to override the create() method of the view object's custom view row class to modify the default row creation behavior to pass the context of the parent entity object to the newly-created child entity.

In Section 38.8, "Controlling Entity Posting Order to Avoid Constraint Violations," you've already seen the code required for 1 and 2 above in an example with associated Suppliers and Products entity objects. The only thing remaining is the overridden create() method on the view row. Consider a ProductAndSupplier view object with a primary entity usage of Product and secondary entity usages of Supplier and User. Assume the Product entity usage is marked as updatable and non-reference, while the User entity usage is a reference entity usage.

Example 39-31 shows the commented code required to correctly sequence the creation of the multiple, updatable entity row parts during a view row create operation.

Example 39-31 Overriding View Row create() Method for Multiple Updatable Entities

  /**
   * By default, the framework will automatically create the new
   * underlying entity object instances that are related to this
   * view object row being created.
   *
   * We override this default view object row creation to explicitly
   * pre-populate the new (detail) ProductsImpl instance using
   * the new (master) SuppliersImpl instance. Since all entity objects
   * implement the AttributeList interface, we can directly pass the
   * new SuppliersImpl instance to the ProductsImpl create()
   * method that accepts an AttributeList.
   */
  protected void create(AttributeList attributeList) {
    // The view row will already have created "blank" entity instances
    SuppliersImpl newSupplier = getSupplier();
    ProductsImpl newProduct = getProduct();
     try {
        // Let product "blank" entity instance to do programmatic defaulting
        newSupplier.create(attributeList);
        // Let product "blank" entity instance to do programmatic
        // defaulting passing in new SuppliersImpl instance so its attributes
        // are available to the EmployeeImpl's create method.
        newProduct.create(newSupplier);
     }
     catch (JboException ex) {
       newSupplier.revert();
       newProduct.revert();      
       throw ex;
     }
     catch (Exception otherEx) {
       newSupplier.revert();
       newProduct.revert();      
       throw new RowCreateException(true      /* EO Row? */,
                                    "Product" /* EO Name */,
                                    otherEx   /* Details */);
     }
  }

In order for this view row class to be able to invoke the protected create() method on the Suppliers and Products entity objects, they need to override the create() method. If the view object and entity objects are in the same package, the overridden create() method can have protected access. Otherwise, it requires public access.

/**
 * Overridding this method in this class allows friendly access
 * to the create() method by other classes in this same package, like the
 * ProductsAndSuppliers view object implementation class, whose overridden
 * create() method needs to call this.
 * @param nameValuePair
 */
  protected void create(AttributeList nameValuePair) {
    super.create(nameValuePair);
  }

39.10 Declaratively Preventing Insert, Update, and Delete

Some 4GL tools like Oracle Forms provide declarative properties that control whether a given data collection allows inserts, updates, or deletes. While the view object does not yet support this as a built-in feature in the current release, it's easy to add this facility using a framework extension class that exploits custom metadata properties as the developer-supplied flags to control insert, update, or delete on a view object.

Note:

The example in this section refers to the DeclarativeBlockOperations project in the AdvancedViewObjectsExamples application workspace in the StandaloneExamples module of the Fusion Order Demo application.

To allow developers to have control over individual view object instances, you could adopt the convention of using application module custom properties by the same name as the view object instance. For example, if an application module has view object instances named ProductsInsertOnly, ProductsUpdateOnly, ProductsNoDelete, and Products, your generic code might look for application module custom properties by these same names. If the property value contains Insert, then insert is enabled for that view object instance. If the property contains Update, then update allowed. And, similarly, if the property value contains Delete, then delete is allowed. You could use helper methods like this to test for these application module properties and determine whether insert, update, and delete are allowed for a given view object:

private boolean isInsertAllowed() {
  return isStringInAppModulePropertyNamedAfterVOInstance("Insert");
}
private boolean isUpdateAllowed() {
  return isStringInAppModulePropertyNamedAfterVOInstance("Update");
}
private boolean isDeleteAllowed() {
  return isStringInAppModulePropertyNamedAfterVOInstance("Delete");
}
private boolean isStringInAppModulePropertyNamedAfterVOInstance(String s) {
  String voInstName = getViewObject().getName();
  String propVal = (String)getApplicationModule().getProperty(voInstName);
  return propVal != null ? propVal.indexOf(s) >= 0 : true;
}

Example 39-32 shows the other code required in a custom framework extension class for view rows to complete the implementation. It overrides the following methods:

  • isAttributeUpdateable()

    To enable the user interface to disable fields in a new row if insert is not allowed or to disable fields in an existing row if update is not allowed.

  • setAttributeInternal()

    To prevent setting attribute values in a new row if insert is not allowed or to prevent setting attributes in an existing row if update is not allowed.

  • remove()

    To prevent remove if delete is not allowed.

  • create()

    To prevent create if insert is not allowed.

Example 39-32 Preventing Insert, Update, or Delete Based on Custom Properties

public class CustomViewRowImpl extends ViewRowImpl {
  public boolean isAttributeUpdateable(int index) {
    if (hasEntities() && 
        ((isNewOrInitialized() && !isInsertAllowed()) ||
         (isModifiedOrUnmodified() && !isUpdateAllowed()))) {
      return false;
    }
    return super.isAttributeUpdateable(index);
  }
  protected void setAttributeInternal(int index, Object val) {
    if (hasEntities()) {
      if (isNewOrInitialized() && !isInsertAllowed())
        throw new JboException("No inserts allowed in this view");
      else if (isModifiedOrUnmodified() && !isUpdateAllowed())
        throw new JboException("No updates allowed in this view");
    }
    super.setAttributeInternal(index, val);
  }
  public void remove() {
    if (!hasEntities() || isDeleteAllowed() || isNewOrInitialized())
      super.remove();
    else
        throw new JboException("Delete not allowed in this view");
  }
  protected void create(AttributeList nvp) {
    if (isInsertAllowed()) {
      super.create(nvp);
    } else {
      throw new JboException("Insert not allowed in this view");
    }
  }
  // private helper methods omitted from this example
}