Fusion Middleware Documentation
Advanced Search


Developing Fusion Web Applications with Oracle Application Development Framework
Close Window

Table of Contents

Show All | Collapse

10 Working Programmatically with View Objects

This chapter describes programmatic uses of the ADF Business Components API that you can use while designing and working with ADF view objects in an Oracle ADF application.

This chapter includes the following sections:

10.1 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 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 D, "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.

10.1.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 10-1, 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 and controls the execution lifecycle

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

Figure 10-1 View Object Custom Java Generation Options

Select Java options dialog

10.1.1.1 Generating Bind Variable Accessors

When you enable the generation of a custom view object class, if you also select the Include Bind Variable Accessors checkbox, as shown in Figure 10-1, then JDeveloper generates getter and setter methods in your view object class. Since the ProductView view object has a named bind variables (bv_ProductName), the custom ProductViewImpl.java view object class would have corresponding methods like this:

public String getbv_ProductName() {...}
public void setbv_ProductName(String 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 bv_ProductName:

vo.setNamedWhereClauseParam("bv_ProductName","ball");

You can write the code like:

vo.setbv_ProductName("ball");

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

// spelling name wrong gives compile error
vo.setbv_Name("ball");

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

// passing Integer where String expected gives compile error
vo.setbv_ProductName(150);

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.
// Note the method setNamedWhereClauseParam() sets the value only on the efault  
// RowSet and not on the secondary RowSet.
vo.setNamedWhereClauseParam("bv_Name",150);

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.

10.1.1.2 Generating View Row Attribute Accessors

When you enable the generation of a custom view row class, if you also select the Include Accessors checkbox, as shown in Figure 10-1, then JDeveloper generates getter and setter methods for each attribute in the view row. For example, for the ProductView view object, the corresponding custom ProductViewRowImpl.java class might have methods like this generated in it:

public String getName() {...}
public void setName(String value) {...}
public String getShortDesc() {...}
public void setShortDesc(String value) {...}
public String getImageId() {...}
public void setImageId(Integer value) {...}
public String getSuggestedWhlslPrice() {...}
public void setSuggestedWhlslPrice(BigDecimal 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 Name attribute:

String name = row.getAttribute("Name");

you can write the code like:

String name = row.getName();

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

// spelling name wrong gives compile error
String name = row.getFullName();

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
Integer name = (Integer)row.getAttribute("FullName");

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.

10.1.1.3 Exposing View Row Accessors to Clients

When enabling the generation of a custom view row class, if you would like to generate a view row attribute accessor, 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.4, "What You May Need to Know About Custom Interface Support."

For example, in the case of the ProductView view object, exposing the accessors to the client will generate a custom row interface named ProductViewRow. 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 10-1 shows a TestClient sample client program that casts the results of the productByName.first() method to the ProductViewRow interface so that it can make method calls like printAllAttributes() and testSomethingOnProductRows().

Example 10-1 Simple Example of Using Client Row Interface with Accessors

package oracle.summit.model.extend;

import oracle.jbo.*;
import oracle.jbo.client.Configuration;

import oracle.summit.model.extend.common.ProductView;
import oracle.summit.model.extend.common.ProductViewEx;
import oracle.summit.model.extend.common.ProductViewExRow;
import oracle.summit.model.extend.common.ProductViewRow;

public class TestClient {
  public static void main(String[] args) {
    String amDef = "oracle.summit.model.extend.AppModule";
    String config = "AppModuleLocal";
    ApplicationModule am = 
                      Configuration.createRootApplicationModule(amDef,config);
    ProductView products = (ProductView)am.findViewObject("ProductView1");
    products.executeQuery();
    ProductViewRow product = (ProductViewRow)products.first();
    printAllAttributes(products,product);
    testSomethingOnProductsRow(product);
    products = (ProductView)am.findViewObject("ProductViewEx1");
    ProductViewEx productsByName = (ProductViewEx)products;
    productsByName.setbv_ProductName("bunny");
    productsByName.executeQuery();
    product = (ProductViewRow)productsByName.first();
    printAllAttributes(productsByName,product);
    testSomethingOnProductsRow(product);
    am.getTransaction().rollback();
    Configuration.releaseRootApplicationModule(am,true);
  }
  private static void testSomethingOnProductsRow(ProductViewRow product) {
    try {
      if (product instanceof ProductViewExRow) {
        ProductViewExRow productByName = (ProductViewExRow)product;
        productByName.someExtraFeature("Test");
      }
      product.setName("Q");
      System.out.println("Setting the Name attribute to 'Q' succeeded.");
    }
    catch (ValidationException v) {
      System.out.println(v.getLocalizedMessage());
    }
  }
  private static void printAllAttributes(ViewObject vo, Row r) {
    String viewObjName = vo.getName();
    System.out.println("Printing attribute for a row in VO '"+ viewObjName+"'");
    StructureDef def = r.getStructureDef();
    StringBuilder sb = new StringBuilder();
    int numAttrs = def.getAttributeCount();
    AttributeDef[] attrDefs = def.getAttributeDefs();
    for (int z = 0; z < numAttrs; z++) {
      Object value = r.getAttribute(z);
      sb.append(z > 0 ? "  " : "")
        .append(attrDefs[z].getName())
        .append("=")
        .append(value == null ? "<null>" : value)
        .append(z < numAttrs - 1 ? "\n" : "");
    }
    System.out.println(sb.toString());
  }
}

10.1.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 ADF 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.

10.1.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 oracle.summit.model.extend.ProductView, the default names for its custom Java files will be ProductViewImpl.java for the view object class and ProductViewRowImpl.java for the view row class. Both files get created in the same ./model/extend directory as the component's XML document 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.

10.1.2.1 Seeing and Navigating to Custom Java Files

As shown in Figure 10-2, 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 10-2

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

Figure 10-2 Viewing and Navigating to Custom Java Classes for a View Object

Custom java file in Application Navigator

10.1.3 What You May Need to Know About Custom Classes

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

10.1.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).

10.1.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 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.

10.1.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 document 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, attributes are defined as an AttributesEnum type, which specifies attribute names (and accessors for each attribute) based on the view object's XML document, in sequential order of the <ViewAttribute> tag, the association-related <ViewLinkAccessor> tag, and the <ViewAccessor> tag 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 10-2 shows that each attribute, view link accessor attribute, or view accessor attribute has a corresponding generated AttributesEnum enum. JDeveloper defines enums instead of constants in order to prevent merge conflicts that could result when multiple developers add new attributes to the XML document.

Example 10-2 Attribute Constants Are Automatically Maintained in the Custom View Row Java Class

public class RowImpl extends SummitViewRowImpl implements ProductViewRow {
/**
 * AttributesEnum: generated enum for identifying attributes and accessors. 
 * Do not modify.
 */
public enum AttributesEnum {...}
  public static final int ID = AttributesEnum.Id.index();
  public static final int NAME = AttributesEnum.Name.index();
  public static final int SHORTDESC = AttributesEnum.ShortDesc.index();
  public static final int LONGTEXTID = AttributesEnum.LongtextId.index();
  public static final int IMAGEID = AttributesEnum.ImageId.index();
  public static final int SUGGESTEDWHLSLPRICE =
                              AttributesEnum.SuggestedWhlslPrice.index();
  public static final int WHLSLUNITS = AttributesEnum.WhlslUnits.index();
  public static final int SOMEVALUE = AttributesEnum.SomeValue.index();
  public static final int SFADSF = AttributesEnum.sfadsf.index();
  ...

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:

public Integer getId() {
  return (Integer) getAttributeInternal(ID); // <-- Attribute constant
}
public void setId(Integer value) {
  setAttributeInternal(ID, value);// <-- Attribute constant
}

The last two aspects of the automatically maintained code related to view row attribute constants defined by the AttributesEnum type 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 ProductViewRowImpl.java class. The companion setAttrInvokeAccessor() method looks similar.

protected Object getAttrInvokeAccessor(int index, AttributeDefImpl attrDef) throws Exception {
    if ((index >= AttributesEnum.firstIndex()) && (index < AttributesEnum.count())) {
        return AttributesEnum.staticValues()[index - AttributesEnum.firstIndex()].get(this);
    }
    return super.getAttrInvokeAccessor(index, attrDef);
}

The rules of thumb to remember about this generated attribute-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 document for you.

The Don'ts
  • Don't modify the list of enums in the generated AttributesEnum enum

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

10.1.3.4 Avoid Creating Dependencies on Parent Application Module Types

Custom methods that you implement in the view object class or view object row class must not be dependent on the return type of an application module. At runtime, in specific cases, methods that execute with such a dependency may throw a ClassCastException because the returned application module does not match the expected type. It is therefore recommended that custom methods that you implement should not have code to get a specific application module implementation or view object implementation as shown below.

((MyAM)getRootApplicationModule()).getMyVO 

Specifically, the above code fails with a ClassCastException in the following scenarios:

  • When the ADF Business Components framework instantiates the view object in a different container application module than its defining container. While view objects are typically instantiated in the container application module that declares their view usage (XML definition), the ADF Business Components runtime does not guarantee that the containers associated with each application module will remain fixed. Thus, if you implement methods with dependencies on the parent application module type, your methods may not execute consistently.

  • When you manually nest an application module under a root application module. In this case, the nested application modules share the same Transaction object and there is no guarantee that the expected application module type is returned with the above code.

  • When the ADF Business Components framework implementation changes with releases. For example, in previous releases, the framework created an internal root application module in order to control declarative transactions that the application defined using ADF task flows.

10.2 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.9.1, "How to Create Named View Criteria Declaratively."

Note:

The example in this section refers to the oracle.summit.model.multinamedviewcriteria package in the SummitADF_Examples application workspace.

10.2.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 overwrite all previously applied view criteria. Alternatively, you can use the setApplyViewCriteriaName() method when you want to append a single view criteria to 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 10-3 shows custom methods showCustomersForSalesRep(), showCustomersForCreditRating(), and showCustomersForSalesRepForCreditRating(), 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 10-3 Exposing Client Methods to Enable Appropriate Named Criteria

// In CustomerViewImpl.java
   public void showCustomersForSalesRep() {
   // reset the view criteria
      setApplyViewCriteriaNames(null);
   // set the view criteria to show all the customers for the given sales rep
      setApplyViewCriteriaName("SalesRepIsCriteria");
      executeQuery();
   }

   public void showCustomersForCreditRating() {
      setApplyViewCriteriaNames(null);
      setApplyViewCriteriaName("CreditRatingIsCriteria");
      executeQuery();
   }
 
   public void showCustomersForSalesRepForCreditRating() {
   // reset the view criteria
      setApplyViewCriteriaNames(null);
   // apply both view criteria to show all the customers that match both
      setApplyViewCriteriaNames(new String[]{"SalesRepIsCriteria",
                                                "CreditRatingIsCriteria"});
      executeQuery();
   }

10.2.2 Removing All Applied Named View Criteria

To remove any currently applied named view criteria, use setApplyViewCriteriaNames(null). For example, you could add the showAllCustomers() method in Example 10-4 to the CustomersView 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 10-4 Removing All Applied Named View Criteria

// In CustomerViewImpl.java
public void showAllCustomers() {
  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.

10.2.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 10-5 shows the interesting lines of a test client class that works with the CustomerView 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 10-5 Test Client Code Working with Named View Criterias

// In MultiNamedViewCriteriaTestClient.java
   CustomerView vo = (CustomerView) am.findViewObject("CustomerView");

// Show list of all rows in the CustomerView, without applying any view criteria.
   showResults(vo,"All Customers");

// Use  type safe set method to set the value of the bind variable used by the
// view criteria
   vo.setbv_SalesRepId(12);
   vo.showCustomersForSalesRep();
   showResults(vo, "All Customers with SalesRepId = 12");
 
// Set the sales rep id to 11 and show the results.
   vo.setbv_SalesRepId(11);
   vo.showCustomersForSalesRep();
   showResults(vo, "All Customers with SalesRepId = 11");
 
// use type safe method to set value of bind variable used by the view criteria
   vo.setbv_CreditRatingId(2);

// This method applies both view criteria to the VO so the results match 
// both criteria, but we do not set the bind variable for sales rep as it
// was already set. The results will show all the customers with a sales 
// rep of 11 and a credit rating of 2.
   vo.showCustomersForSalesRepForCreditRating();
   showResults(vo, "Customers with SalesRepId = 11 and CreditRating = 2");
 
// Now show all the customers again. The showAllCustomers method resets
// the view criteria.
   vo.showAllCustomers();
   showResults(vo, "All Customers");

Running MultiNamedViewCriteriaTestClient.java produces output as follows:

---All Customers ---

Unisports Sao Paulo [12, 1]
Simms Athletics Osaka [14, 4]
Delhi Sports New Delhi [14, 2]
...
---All Customers with SalesRepId = 12 ---

Unisports Sao Paulo [12, 1]
Futbol Sonora Nogales [12, 1]
Stuffz Sporting Goods Madison [12, 3]
...
---All Customers with SalesRepId = 11 ---
 
Womansport Seattle [11, 3]
Beisbol Si! San Pedro de Macon's [11, 1]
Big John's Sports Emporium San Francisco [11, 1]
Ojibway Retail Buffalo [11, 3]
...
---Customers with SalesRepId = 11 and CreditRating = 2 ---
 
Max Gear New York [11, 2]
MoreAndMoreStuffz Dallas [11, 2]
Schindler's Sports St Louis [11, 2]
...
---All Customers ---
 
Unisports Sao Paulo [12, 1]
Simms Athletics Osaka [14, 4]
Delhi Sports New Delhi [14, 2]
...

10.3 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. This type of operation is ideal for sorting and filtering the new, as yet unposted rows of the view object row set; otherwise, unposted rows are added to the top of the row set.

Note:

The example in this section refers to to the oracle.summit.model.inMemoryOperations package in the SummitADF_Examples application workspace.

10.3.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.

10.3.2 Sorting View Object Rows In Memory

To sort the rows in a view object at runtime, use the setSortBy() method to control the sort order or you can set the view object property PROP_ALWAYS_USE_SORT to true to allow the system to handle in-memory sorting by default.

When you want to control the sort order, 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 CreditRatingId and ZipCode, you could sort the view object first by Customer descending, then by DaysOpen by calling:

setSortBy("CreditRatingId desc, ZipCode");

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.

10.3.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 10-6 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 CustomersInTx.

Example 10-6 Combining setSortBy and setQueryMode for In-Memory Sorting

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

Running the example produces the results:

--- Initial database results ---
 
36,Great Gear,1500 Barton Springs Rd,78704, 2, TX
37,Acme Outfitters,3500 Guadalupe St,78705, 3, TX
38,Athena's Closet,1209 Red River St,78701, 1, TX
39,BuyMyJunk,5610 E Mockingbird Ln,75206, 3, TX
...
 
--- After in-memory sorting by Customer Name  ---
 
37,Acme Outfitters,3500 Guadalupe St,78705, 3, TX
38,Athena's Closet,1209 Red River St,78701, 1, TX
43,Big Bad Sports,2920 Hillcroft St,77057, 1, TX
39,BuyMyJunk,5610 E Mockingbird Ln,75206, 3, TX
...
 
--- After in-memory sorting by CreditRating desc, and ZipCode ---
 
42,Field Importers,2111 Norfolk St,77098, 4, TX
39,BuyMyJunk,5610 E Mockingbird Ln,75206, 3, TX
37,Acme Outfitters,3500 Guadalupe St,78705, 3, TX
41,MoreAndMoreStuffz,3501 McKinney Ave,75204, 2, TX
...

The first line in Example 10-6 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 Summit 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 "Using Application Tracing Tools" chapter of the Oracle Database Performance Tuning Guide.

This will produces a trace.prf file containing the interesting information shown in Example 10-7 about the SQL statement performed by the CustomersInTx 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 10-7 TKPROF Output of a Trace File Confirming Sort Was Done In Memory

SELECT   
    S_CUSTOMER.ID ID,   
    S_CUSTOMER.NAME NAME,   
    S_CUSTOMER.PHONE PHONE,   
    S_CUSTOMER.ADDRESS ADDRESS,   
    S_CUSTOMER.CITY CITY,   
    S_CUSTOMER.STATE STATE,   
    S_CUSTOMER.ZIP_CODE ZIP_CODE,   
    S_CUSTOMER.CREDIT_RATING_ID CREDIT_RATING_ID   
FROM   
    S_CUSTOMER   
WHERE   
    S_CUSTOMER.STATE = 'TX'
 
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.02          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        9      0.00       0.00          0         14          0           8
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       11      0.00       0.02          0         14          0           8

10.3.2.2 Simplified In-Memory Sorting

Should you not require control over the way that rows are sorted in memory, the view object interface provides the PROP_ALWAYS_USE_SORT property that enforces sorting when you change the view object query mode to use in-memory rows as the source. You can use this property when a view object has a small row set and the client needs to have in-memory sorting performed. Set the view object property to specify default in-memory sorting after you set the query mode:

vo.setQueryMode(ViewObject.QUERY_MODE_SCAN_ENTITY_ROWS);
vo.setProperty(ViewObject.PROP_ALWAYS_USE_SORT, "true");

10.3.2.3 Extensibility Points for In-Memory Sorting

Should you need to customize the way that rows are sorted in memory, you have the following means at your disposal:

  • 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.

  • 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.

10.3.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 10-1. You can group subexpressions with parenthesis and use the AND and OR operators between subexpressions.

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

Operator Operation

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

Comparison

NOT

Logical negation

AND

Conjunction

OR

Disjunction


Example 10-8 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 reapplies 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 10-8 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 reapply 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");

10.3.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 10-2. You can group subexpressions with parenthesis and use the AND and OR operators between subexpressions.

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

Operator Operation

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

Comparison

NOT

Logical negation

Note that logical negation operations NOT IN are not supported by the RowMatch expression.

To negate the IN operator, use this construction instead (note the use of brackets):

NOT (EmpID IN ( 'VP','PU'))

AND

Conjunction

OR

Disjunction


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

Table 10-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.

10.3.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 10-9 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 10-9 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 requerying to see a full list again");

10.3.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 ");
}

10.3.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.

10.4 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 oracle.summit.model.readandwrite package in the SummitADF_Examples application workspace.

10.4.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 an 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.

Additionally, both versions of the writeXML() method allow you to set the argument depthCount=ignore to indicate to ignore the depth count and just render what is in the data model based on the specified options parameter flags.

10.4.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 CustomersView view object, the XML produced will be wrapped in an outermost CustomersView 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 CustomersView is wrapped in an CustomersViewRow 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 CustomersView view object instance, and following view link accessors as many levels deep as exists, Example 10-10 shows the code required.

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

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

The CustomersView 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 10-10 produces the XML shown in Example 10-11, reflecting the nested structure defined by the view links.

Example 10-11 XML from a CustomersView View Object with Two Levels of View Linked Details

... 
   <CustomersViewRow>
      <Id>211</Id>
      <Name>Kuhn's Sports</Name>
      <Phone>42-111292</Phone>
      <Address>7 Modrany</Address>
      <City>Prague</City>
      <CountryId>11</CountryId>
      <CreditRatingId>2</CreditRatingId>
      <SalesRepId>15</SalesRepId>
      <OrdersView>
         <OrdersViewRow>
            <Id>107</Id>
            <CustomerId>211</CustomerId>
            <DateOrdered>2012-12-13</DateOrdered>
            <DateShipped>2012-12-14</DateShipped>
            <SalesRepId>15</SalesRepId>
            <Total>142171</Total>
            <PaymentTypeId>2</PaymentTypeId>
            <PaymentOptionId>1082</PaymentOptionId>
            <OrderFilled>Y</OrderFilled>
         </OrdersViewRow>
         <OrdersViewRow>
            <Id>183</Id>
            <CustomerId>211</CustomerId>
            <DateOrdered>2013-02-24</DateOrdered>
            <DateShipped>2013-02-27</DateShipped>
            <SalesRepId>13</SalesRepId>
            <Total>3742</Total>
            <PaymentTypeId>1</PaymentTypeId>
            <OrderFilled>Y</OrderFilled>
         </OrdersViewRow>
         <OrdersViewRow>
            <Id>184</Id>
            <CustomerId>211</CustomerId>
            <DateOrdered>2012-11-19</DateOrdered>
            <DateShipped>2012-11-22</DateShipped>
            <SalesRepId>12</SalesRepId>
            <Total>987</Total>
            <PaymentTypeId>1</PaymentTypeId>
            <OrderFilled>Y</OrderFilled>
         </OrdersViewRow>
         <OrdersViewRow>
            <Id>185</Id>
            <CustomerId>211</CustomerId>
            <DateOrdered>2012-08-08</DateOrdered>
            <DateShipped>2012-08-12</DateShipped>
            <SalesRepId>12</SalesRepId>
            <Total>2525.25</Total>
            <PaymentTypeId>1</PaymentTypeId>
            <OrderFilled>Y</OrderFilled>
         </OrdersViewRow>
         <OrdersViewRow>
            <Id>186</Id>
            <CustomerId>211</CustomerId>
            <DateOrdered>2012-04-26</DateOrdered>
            <DateShipped>2012-04-30</DateShipped>
            <SalesRepId>12</SalesRepId>
            <Total>307.25</Total>
            <PaymentTypeId>1</PaymentTypeId>
            <OrderFilled>Y</OrderFilled>
         </OrdersViewRow>
         <OrdersViewRow>
            <Id>187</Id>
            <CustomerId>211</CustomerId>
            <DateOrdered>2012-12-23</DateOrdered>
            <DateShipped>2012-12-30</DateShipped>
            <SalesRepId>13</SalesRepId>
            <Total>3872.9</Total>
            <PaymentTypeId>1</PaymentTypeId>
            <OrderFilled>Y</OrderFilled>
         </OrdersViewRow>
         <OrdersViewRow>
            <Id>188</Id>
            <CustomerId>211</CustomerId>
            <DateOrdered>2013-01-14</DateOrdered>
            <DateShipped>2013-01-15</DateShipped>
            <SalesRepId>12</SalesRepId>
            <Total>12492</Total>
            <PaymentTypeId>1</PaymentTypeId>
            <OrderFilled>Y</OrderFilled>
         </OrdersViewRow>
         ...
      </OrdersView>
   </CustomersViewRow>
...

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

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

10.4.3.1 Controlling XML Element Names

You can use the Properties window 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 Properties window, 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 CustomersView view object defines this property to change the XML element you see in Example 10-11 to be <EmailAddress> instead of <Email>.

  • Select the General navigation tab in the Properties window 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 CustomersView view object defines this property to change the XML element name for the rows you see in Example 10-11 to be <Customer> instead of <CustomersViewRow>.

  • 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 in the Accessors section of the Relationship page. Enter the desired name of the view link accessor attribute in the Accessor Name field.

10.4.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 Properties window, 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.

10.4.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 10-12 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 10-12 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);
}

10.4.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 10-13 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 Name, City, and OrdersView attributes from the CustomersView view object, and the Id, DateOrdered, and Total 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 10-13 Using a View Definition Attribute Map for Fine Control Over Generated XML

HashMap viewDefMap = new HashMap();
viewDefMap.put("oracle.summit.model.readandwritexml.queries.CustomersView",
        new String[]{"Name","City", 
                     "OrdersView" /* View link accessor attribute */
                     });
viewDefMap.put("oracle.summit.model.readandwritexml.queries.OrdersView",
        new String[]{"Id","DateOrdered","Total"});  
printXML(vo.writeXML(XMLInterface.XML_OPT_ALL_ROWS,viewDefMap)); 

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

Example 10-14 XML from a Users View Object Produced Using an Attribute Map

<CustomersViewRow>
      <Id>211</Id>
      <Name>Kuhn's Sports</Name>
      <City>Prague</City>
      <OrdersView>
         <OrdersViewRow>
            <Id>107</Id>
            <DateOrdered>2012-12-13</DateOrdered>
            <Total>142171</Total>
         </OrdersViewRow>
         <OrdersViewRow>
            <Id>183</Id>
            <DateOrdered>2013-02-24</DateOrdered>
            <Total>3742</Total>
         </OrdersViewRow>
         <OrdersViewRow>
            <Id>184</Id>
            <DateOrdered>2012-11-19</DateOrdered>
            <Total>987</Total>
         </OrdersViewRow>
         <OrdersViewRow>
            <Id>185</Id>
            <DateOrdered>2012-08-08</DateOrdered>
            <Total>2525.25</Total>
         </OrdersViewRow>
         <OrdersViewRow>
            <Id>186</Id>
            <DateOrdered>2012-04-26</DateOrdered>
            <Total>307.25</Total>
         </OrdersViewRow>
         <OrdersViewRow>
            <Id>187</Id>
            <DateOrdered>2012-12-23</DateOrdered>
            <Total>3872.9</Total>
         </OrdersViewRow>
         <OrdersViewRow>
            <Id>188</Id>
            <DateOrdered>2013-01-14</DateOrdered>
            <Total>12492</Total>
         </OrdersViewRow>
         ...
      </OrdersView>
   </CustomersViewRow>
...

10.4.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.

10.4.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 10-15. It is a simple transformation with a single template that matches the root element of the generated XML from Example 10-14 to create a new CustomerNames element in the result. The template uses the xsl:for-each instruction to process all CustomersView elements. For each CustomersView element that qualifies, it creates a CustomerNames element in the result whose Contact attribute is populated from the value of the Name child element of the CustomersView.

Example 10-15 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="/">
    <CustomerNames>
      <xsl:for-each
           select="/CustomersView/CustomersViewRow)">
        <xsl:sort select="Name"/>
        <Customer Contact="{Name}"/>
      </xsl:for-each>
    </CustomerNames>
  </xsl:template>
</xsl:stylesheet>

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

Example 10-16 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 10-16 produces the transformed XML shown here:

<CustomerNames>
   <Customer Contact="ABC Company"/>
   <Customer Contact="Acme Outfitters"/>
   <Customer Contact="Acme Sporting Goods"/>
   <Customer Contact="All Baseball"/>
   ...
   <Customer Contact="The Sports Emporium"/>
   <Customer Contact="Unisports"/>
   <Customer Contact="Value Valley"/>
   <Customer Contact="Wally's Mart"/>
   <Customer Contact="Wally's Weights"/>
   <Customer Contact="Westwind Sports"/>
   <Customer Contact="Womansport"/>
   <Customer Contact="Your Choice Sporting Goods"/>
   <Customer Contact="Z-Mart"/>
   <Customer Contact="Zibbers"/>
   <Customer Contact="Zip City"/>
</CustomerNames>

The getXSLStylesheet() helper method shown in Example 10-17 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 10-17 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.

10.4.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.

10.4.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.

10.4.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.

10.4.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.

10.4.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 10-18. It is in the canonical format expected by a single row in the CustomersView view object. Nested inside the root CustomersViewRow element, the City attribute represents the customer's city. The nested OrdersView element corresponds to the OrdersView view link accessor attribute and contains three OrdersViewRow elements. Each of these includes Id elements representing the primary key of a OrdersView row.

Example 10-18 XML Document in Canonical Format to Insert, Update, and Delete Rows

<CustomersViewRow>
   <Id>16</Id>
   <!-- This will update Customer's ConfirmedEmail attribute -->
   <City>Houston</City>
   <OrdersView>
      <!-- This will be an update since it does exist -->
      <OrdersViewRow>
         <Id>1018</Id>
         <DateShipped>2013-03-13</DateShipped>
      </OrdersViewRow>
      <!-- This will be an insert since it doesn't exist -->
      <OrdersViewRow>
         <Id>9999</Id>
         <CustomerId>16</CustomerId>
         <Total>9999.00</Total>
      </OrdersViewRow>
      <!-- This will be deleted -->
      <OrdersViewRow bc4j-action="remove">
         <Id>1008</Id>
      </OrdersViewRow>        
   </OrdersView>
</CustomersViewRow>

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

  1. Finds a target row by key (e.g. for customer "The Sports Emporium").

  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 10.4.1, "How to Produce XML for Queried Data" to ensure that new, unposted rows are included in the XML.

Example 10-19 Applying Changes to an Existing Row with readXML()

ViewObject vo = am.findViewObject("CustomersView");
Key k = new Key(new Object[] { 16 });
// 1. Find a target row by key (e.g. for customer "The Sports Emporium")
Row sports = vo.findByKey(k, 1)[0];
// 2. Show the XML produced for the row before changes are applied
printXML(sports.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
sports.readXML(getInsertUpdateDeleteXMLGram(), -1);
// 5. Show the XML with the pending changes applied
printXML(sports.writeXML(-1, XMLInterface.XML_OPT_ALL_ROWS | 
                              XMLInterface.XML_OPT_ASSOC_CONSISTENT));

Running the code in Example 10-19 initially displays the "before" version of The Sports Emporium information. Notice that:

  • The City attribute has a value of "Lapeer"

  • There is no ShippedDate attribute for order ID 1018

  • There is an orders row for order ID 1008, and

  • There is no orders row related to order ID 9999.

<CustomersViewRow>
   <Id>16</Id>
   <Name>The Sports Emporium</Name>
   <Address>222 E Nepessing St</Address>
   <City>Lapeer</City>
   <State>MI</State>
   <CountryId>4</CountryId>
   <ZipCode>48446</ZipCode>
   <CreditRatingId>1</CreditRatingId>
   <SalesRepId>13</SalesRepId>
   <OrdersView>
      <OrdersViewRow>
         <Id>1018</Id>
         <CustomerId>16</CustomerId>
         <DateOrdered>2013-02-25</DateOrdered>
         <SalesRepId>11</SalesRepId>
         <Total>195.99</Total>
         <PaymentTypeId>2</PaymentTypeId>
         <PaymentOptionId>1009</PaymentOptionId>
         <OrderFilled>Y</OrderFilled>
      </OrdersViewRow>
      <OrdersViewRow>
         <Id>1008</Id>
         <CustomerId>16</CustomerId>
         <DateOrdered>2013-02-26</DateOrdered>
         <SalesRepId>14</SalesRepId>
         <Total>100.97</Total>
         <PaymentTypeId>2</PaymentTypeId>
         <PaymentOptionId>1009</PaymentOptionId>
         <OrderFilled>N</OrderFilled>
      </OrdersViewRow>
      <OrdersViewRow>
         <Id>1023</Id>
         <CustomerId>16</CustomerId>
         <DateOrdered>2013-01-11</DateOrdered>
         <DateShipped>2013-01-15</DateShipped>
         <SalesRepId>11</SalesRepId>
         <Total>2451.97</Total>
         <PaymentTypeId>2</PaymentTypeId>
         <PaymentOptionId>1009</PaymentOptionId>
         <OrderFilled>Y</OrderFilled>
      </OrdersViewRow>
   </OrdersView>
</CustomersViewRow>

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

  • The City attribute is now Houston.

  • A new orders row for order 9999 got created.

  • The date shipped for orders row 1018 has been set to 2013-03-13, and

  • The orders row for order 1008 is removed

<CustomersViewRow>
   <Id>16</Id>
   <Name>The Sports Emporium</Name>
   <Address>222 E Nepessing St</Address>
   <City>Houston</City>
   <State>MI</State>
   <CountryId>4</CountryId>
   <ZipCode>48446</ZipCode>
   <CreditRatingId>1</CreditRatingId>
   <SalesRepId>13</SalesRepId>
   <OrdersView>
      <OrdersViewRow>
         <Id>9999</Id>
         <CustomerId>16</CustomerId>
         <Total>9999.00</Total>
      </OrdersViewRow>
      <OrdersViewRow>
         <Id>1018</Id>
         <CustomerId>16</CustomerId>
         <DateOrdered>2013-02-25</DateOrdered>
         <DateShipped>2013-03-13</DateShipped>
         <SalesRepId>11</SalesRepId>
         <Total>195.99</Total>
         <PaymentTypeId>2</PaymentTypeId>
         <PaymentOptionId>1009</PaymentOptionId>
         <OrderFilled>Y</OrderFilled>
      </OrdersViewRow>
      <OrdersViewRow>
         <Id>1023</Id>
         <CustomerId>16</CustomerId>
         <DateOrdered>2013-01-11</DateOrdered>
         <DateShipped>2013-01-15</DateShipped>
         <SalesRepId>11</SalesRepId>
         <Total>2451.97</Total>
         <PaymentTypeId>2</PaymentTypeId>
         <PaymentOptionId>1009</PaymentOptionId>
         <OrderFilled>Y</OrderFilled>
      </OrdersViewRow>
   </OrdersView>
</CustomersViewRow>

Note:

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

10.5 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.

10.5.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 Applications window, 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 10.5.3, "Key Framework Methods to Override for Programmatic View Objects" to implement your custom data retrieval strategy.

10.5.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.

10.5.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 reexecuted). Your implementation must not override this method and completely change the query or change the list of parameters. Doing so . For implementation best practices, see Section 10.5.4.2, "The Overridden executeQueryForCollection() Method."

  • 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.

  • getCappedQueryHitCount()

    The row count query is optimized to determine whether the query would return more rows than the capped value specified by a global row fetch limit. If the number of potential rows is greater, the method returns a negative number. Although the scroll bar in the user interface may not be able to use the row count query to show an accurate scroll position, your row count query will perform well even with large tables. Your implementation can check the Cap value and return the actual row count. The framework obtains the Cap value from the row fetch limit specified in the adf-config.xml file. Note that oldCap is not used

    The method returns a row count when the value is less than or equal to the Cap; otherwise, returns a negative number. Your implementation can use this information to build a performing row count query. If performance is not important, you may just return the same value as getQueryHitCount(), but the getCappedQueryHitCount() now provides additional information about the number of rows it is looking for. For example, assume the DEPT table has one million rows. When getQueryHitCount() is called, the framework will execute SELECT COUNT(*) FROM DEPT against the database and return all one million rows. However, when you intend to show at most 500 rows, you can set a global row fetch limit and call getCappedQueryHitCount(). In this case, the framework will execute SELECT COUNT(*) FROM DEPT WHERE ROWNUM <= 500. The query will complete much faster and also provides a performance improvements. The method wraps the view object query in a nested clause like SELECT COUNT(*) FROM (SELECT DEPTNO, LOC FROM DEPT) WHERE ROWNUM <= :cap (assuming that SELECT DEPTNO, LOC FROM DEPT is the view object query statement).

  • 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.

10.5.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 oracle.summit.model.viewobjectonrefcursor package in the SummitADF_Examples application workspace.

Declaring a PL/SQL package with a function that returns a REF CURSOR is straightforward. Example 10-20 shows how your package might look.

Example 10-20 Function that Returns a REF_CURSOR

CREATE OR REPLACE PACKAGE RefCursorExample IS
  TYPE ref_cursor IS REF CURSOR;
  FUNCTION get_orders_for_customer(p_custId NUMBER) RETURN ref_cursor;
  FUNCTION count_orders_for_customer(p_custId NUMBER) RETURN NUMBER;
END RefCursorExample;
.
/
show errors
CREATE OR REPLACE PACKAGE BODY RefCursorExample IS
  FUNCTION get_orders_for_customer(p_custIdl NUMBER) RETURN ref_cursor IS
    the_cursor ref_cursor;
  BEGIN
    OPEN the_cursor FOR
      SELECT o.id, o.date_ordered, o.total
        FROM s_ord o, s_customer c
        WHERE o.customer_id = c.id
        AND c.id = p_custId;
      RETURN the_cursor;
  END get_orders_for_customer;

  FUNCTION count_orders_for_customer(p_custId NUMBER) RETURN NUMBER IS
    the_count NUMBER;
  BEGIN
    SELECT COUNT(*)
      INTO the_count
      FROM s_ord o, s_customer c
      WHERE o.customer_id = c.id
      AND c.id = p_cust_id;
    RETURN the_count;
  END count_orders_for_customer;
END RefCursorExample;
.
/
show errors

After defining an entity-based OrdersForCustomer view object with an entity usage for an Order entity object, go to its custom Java class OrdersForCustomerImpl.java. Then, override the methods of the view object as described in the following sections.

10.5.4.1 The Overridden create() Method

The create() method removes all traces of a SQL query for this view object. Example 10-21 shows how to override the create() method for this purpose.

Example 10-21 Create Method Removes SQL Query Traces

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

10.5.4.2 The Overridden executeQueryForCollection() Method

The executeQueryForCollection() method is executed when the framework needs to issue the database query for the query collection based on this view object. One view object can produce many related result sets, each potentially the result of different bind variable values. If the row set in query is involved in a framework-coordinated master/detail view link, then the params array will contain one or more framework-supplied name-value pairs of bind parameters from the source view object. If there are any user-supplied bind parameter values, they will precede the framework-supplied bind variable values in the params array, and the number of user parameters will be indicated by the value of the numUserParams argument.

The method calls a helper method retrieveRefCursor() to execute the stored function and return the REF CURSOR return value, cast as a JDBC ResultSet. Example 10-22 shows how to override the executeQueryForCollection() method for this purpose.

Example 10-22 Executing the Stored Function

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

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. Example 10-23 shows how to define the storeNewResultSet() method for this purpose.

Example 10-23 Storing the Result Set

private void storeNewResultSet(Object qc, ResultSet rs) {
  ResultSet existingRs = getResultSet(qc);
  // If this query collection is getting reused, close out any previous rowset
  if (existingRs != null) {
     try {existingRs.close();} catch (SQLException s) {}
  }
  setUserDataForCollection(qc,rs);
  hasNextForCollection(qc); // Prime the pump with the first row.
}

The retrieveRefCursor() method uses the helper method described in Section 16.5, "Invoking Stored Procedures and Functions" to invoke the stored function and return the REF CURSOR. Example 10-24 shows how to define the retrieveRefCursor() method for this purpose.

Example 10-24 Retrieving the Reference Cursor

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

10.5.4.3 The Overridden createRowFromResultSet() Method

For each row that the framework needs fetched from the data source, it will invoke your overridden createRowFromResultSet() method. The implementation retrieves the collection-specific ResultSet object from the user-data context. It uses the getResultSet() method to retrieve the result set wrapper from the query-collection user data, and the createNewRowForCollection() method to create a new blank row in the collection, and then uses the populateAttributeForRow() method to populate the attribute values for each attribute defined at design time in the view object overview editor. Example 10-25 shows how to override the createRowFromResultSet() method for this purpose.

Example 10-25 Creating the Row from the Result Set

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;
}

10.5.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. Example 10-26 shows how to override the hasNextForCollection() method for this purpose.

Example 10-26 Testing for More Rows to Fetch

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;
}

10.5.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. Example 10-27 shows how to override the releaseUserDataForCollection() method for this purpose.

Example 10-27 Releasing the Result Set

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);
  }

10.5.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 stored function 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. Example 10-28 shows how to override the getQueryHitCount() method for this purpose.

Example 10-28 Supporting the Estimated Row Count for the View Object

public long getQueryHitCount(ViewRowSetImpl viewRowSet) {
  Long result = (Long)callStoredFunction(NUMBER,
                   "RefCursorExample.count_orders_for_customer(?)",
                   viewRowSet.getParameters(true));
  return result.longValue();
}

10.6 Creating a View Object with Multiple Updatable Entities

By default, when you create a view object with multiple entity usages, each secondary entity usage that you add to a view object in the overview editor is configured with these settings:

  • The Updatable checkbox is deselected

  • The Reference checkbox is selected

You can change the default behavior to enable a secondary entity usage to be updatable by selecting the usage in the Selected list of the Entity Objects page of the view object overview editor and selecting the Updatable checkbox.

Additionally, for each secondary entity usage, you can decide whether to leave Reference select to control whether or not to refresh the attributes of the secondary entity when the entity lookup information changes. By default, Reference is selected to ensure attributes of each secondary entity objects will be refreshed. For details about this setting when you allow row inserts with multiple entity usages, see Section 10.6.2, "What Happens at Runtime: View Row Creation."

Table 10-4 summarizes the combinations you can select when you define secondary entity usages for a view object.

Table 10-4 View Object Properties to Control View Row Creation Behavior

Updatable Reference View Row Behavior

true

true

This combination allows the entity usage's attributes to be updated and keeps its attributes synchronized with the value of the primary key. Since this combination works fine with the view link consistency feature, you can use it to make sure your view object only has one entity object usage that will participate in inserts.

true

false

This combination allows the entity usage's attributes to be updated but prevents its attributes from being changed by the a primary key lookup. This is a rather rare combination, and works best in situations where you only plan to use the view object to update or delete existing data. With this combination, the user can update attributes related to any of the nonreference, updatable entity usages and the view row will delegate the changes to the appropriate underlying entity rows.

Note: The combination of the view link consistency feature with a view object having some of its secondary entity usages set as Updatable=true, Reference=false can end up creating unwanted extra new entities in your application.

false

true

This is the default behavior, described in Section 5.6.1, "How to Create Joins for Entity-Based View Objects." This combination assumes you do not want the entity usage to be updatable.


If you need a view object with multiple updatable entities to support creating new rows (Updatable=true, Reference=false) and the association between the entity objects is not a composition, then you need to write a bit of code, as described in Section 10.6.1, "How to Programmatically Create New Rows With Multiple Updatable Entity Usages."

10.6.1 How to Programmatically Create New Rows With Multiple Updatable Entity Usages

If you need a view object with multiple updatable entities to support creating new rows (Updatable=true, Reference=false) and the association between the entity objects is not a composition, then you need to override the create() method of the view object's custom view row class 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.

To understand the code for steps 1 and 2, see the example with associated entity objects described in Section 4.14.7, "How to Control Entity Posting Order to Prevent Constraint Violations." The last thing you need to understand is how to override create() method on the view row. Consider a ProductInventoryVO view object with a primary entity usage of Product and secondary entity usage of Inventory. Assume the Product entity usage is marked as updatable and nonreference, while the Inventory entity usage is a reference entity usage.

Note:

The example in this section refers to the oracle.summit.model.multieoupdate package in the SummitADF_Examples application workspace.

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

Example 10-29 Overriding View Row create() Method for Multiple Updatable Entities

/**
   * By default, the framework will automatically create the new
   * underlying entity object instances that make up this
   * view object row being created.
   *
   * We override this default view object row creation to explicitly
   * pre-populate the new (detail) InventoryImpl instance using
   * the new (master) ProductImpl instance. Since all entity objects
   * implement the AttributeList interface, we can directly pass the
   * new ProductImpl instance to the InventoryImpl create()
   * method that accepts an AttributeList.
   */
  protected void create(AttributeList attributeList) {
    // The view row will already have created "blank" entity instances
    // so be sure to use the respective names of the entity instance.
    ProductImpl newProduct = getProduct();
    InventoryImpl newInventory = getInventory();
     try {
        // Let inventory "blank" entity instance to do programmatic defaulting
        newProduct.create(attributeList);
        // Let inventory "blank" entity instance to do programmatic
        // defaulting passing in new ProductImpl instance so its attributes
        // are available to the ProductInventoryVORowImpl's create method.
        newInventory.create(newProduct);
     }
     catch (JboException ex) {
       newProduct.revert();
       newInventory.revert();      
       throw ex;
     }
     catch (Exception otherEx) {
       newProduct.revert();
       newInventory.revert();      
       throw new RowCreateException(true      /* EO Row? */,
                                    "Inventory" /* EO Name */,
                                    otherEx   /* Details */);
     }
  }

In order for this ProductInventoryVO view object's view row class (ProductInventoryVORowImpl class) to be able to invoke the protected create() method on the Product and Inventory entity objects, the entity object classes need to override their create() methods for method accessibility:

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

When overriding the create() method, the declaration of the method will depend on the following conditions:

  • If the view object and entity objects are in the same package, the overridden create() method can have protected access and the ProductInventoryVORowImpl class will have access to them.

  • If either entity object is in a different package, then ProductImpl.create() and InventoryImpl.create() (whichever is in a different package) have to be declared public in order for the ProductInventoryVORowImpl class to be able to invoke them.

10.6.2 What Happens at Runtime: View Row Creation

If you need a view object with multiple updatable entities to support creating new rows, you will want to understand that the Reference flag controls behavior related to view row creation, as well as automatic association-driven lookup of information. If you disable the Reference flag for a given entity usage, then:

  • Each time a new view row is created, a new entity instance will be created for that entity usage.

  • The entity row to which the view row points for its storage of view row attributes related to that entity usage is never changed automatically by the framework.

Conversely, if you leave the Reference flag enabled (default) for an entity usage then:

  • No new entity instance will be created for that entity usage when a new view row is created.

  • The entity row to which the view row points for storage of view row attributes related to that entity usage will automatically be kept in sync with changes made to attributes in the view row that participate in an association with said entity.

Consider a ProductInventoryVO view object that joins information from the PRODUCT and INVENTORY tables to show ProductId, PName, ProductInventoryId (a name chosen in the view object editor to identify this as an attribute of ProductInventoryVO), InventoryInventoryId (a name chosen in the view object editor to identify this as an attribute of ProductInventoryVO) and AmountInStock attributes.

Now, consider what happens at runtime for the default case where you set up the secondary entity object marked as both updatable and reference:

  • The Product entity object is the primary entity usage.

  • The Inventory entity object is a secondary entity usage and is marked as a Reference.

When the user creates a new row in the ProductInventoryVO, ADF Business Components only creates a new Product entity instance (since the Reference flag for the Inventory entity usage is enabled). If the user changes the product's InventoryInventoryId attribute to 10, then ADF Business Components will automatically look up the Inventory entity with primary key 10 in the entity cache (reading it in from the database if not already in the cache) and make this new view row's Inventory entity usage point to this inventory 10 entity. That has the result of automatically reflecting the right AmountInStock value for inventory 10.

In the default scenario, the reference lookup occurs both because the entity usage for Inventory is marked as a reference, as well as the fact that an association exists between the Product entity and the Inventory entity. Through the association definition, ADF Business Components knows which attributes are involved on each side of this association. When any of the attributes on the Product side of the ProductToInventory association are modified, if the Inventory entity usage is marked as a Reference, ADF Business Components will perform that automatic reference lookup. If the user changes value of the AmountInStock to 20 in this new view row, after committing the change, the database will have a new product for inventory 10 and have updated the amount of inventory 10 to 20.

Now, consider what happens at runtime where you set up the secondary entity object marked as updatable and reference is disabled:

  • The Product entity object is the primary entity usage.

  • The Inventory entity object is a secondary entity usage, but this usage is not marked as a Reference.

In this scenario, when the user creates a new row in the ProductInventoryVO, ADF Business Components will create both a new Product entity instance and a new Inventory entity instance (since the Reference flag for the Inventory entity usage is disabled). If the user changes the product's InventoryId attribute to 10, it will have no effect on the value of the AmountInStock attribute being displayed in the row. Additionally, if the user sets InventoryInventoryId to 99 and AmountInStock to 20 in this new view row, after commiting the changes, the database will have both a new product for inventory 10 and a new inventory number 99.

10.7 Programmatically Creating View Definitions and View Objects

The oracle.jbo.server.ViewDefImpl class lets you dynamically define the view definition meta-object for view object instances. The view definition describes the view object's structure.

Typically, the application creates the view definition object by loading an XML file that you create using JDeveloper overview editors. When the application needs to create a view object instance, it queries the MetaObjectManager for the view object's view definition by the view definition name, it then finds the XML file, opens it, parses it, and constructs a view definition object in memory.

Alternatively, you can create the view definition programmatically using methods of the ViewDefImpl class. When you create a programmatic view definition, your application code begins with code like:

ViewDefImpl viewDef = new ViewDefImpl("MyViewDef");
viewDef.setFullName("sessiondef.mypackage.MyViewDef");

A view definition that you create must be uniquely identified by its full name, where the full name is a package-qualified name. Thus, you call setFullName() to pass in the package-qualified name of your view definition object (for example, sessiondef.mypackage.MyViewDef).

The MyViewDef name that you initially pass in is the short name of the view definition you create. Your application may pass the short name when an API requires a view definition name. For example, your application might request the defName parameter when invoking ApplicationModule.createViewObject(String, String).

To create a view definition and then create a view object instance based on that definition, follow these basic steps (as illustrated in Example 10-30):

  1. Create the view definition object and set the full name.

  2. Define the view object SQL statement.

  3. Resolve the view definition and save it into the MDS repository.

  4. With the view definition, construct instance of view objects based on it.

Note:

To save the view definition into the MDS repository, the adf-config.xml file must be appropriately configured for the saved view definition. For details about configuring the adf-config.xml file, see Section 10.7.1, "What You May Need to Know About MDS Repository Configuration.".

Example 10-30 Creating a View Definition Using the ViewDefImpl API

/*
  * 1. Create the view definition object.
  */
ViewDefImpl v = new ViewDefImpl("DefNameForTheObject");
 
v.setFullName("sessiondef.some.unique.DefNameForTheObject");
 
/*
* 2. Then, define the view object's SQL statement by either using a fully-
* specified "expert-mode" SQL query.
*/
v.setQuery("select e.empno,e.ename,e.sal,e.deptno,d.dname,d.loc,"+
               "d.deptno,trunc(sysdate)+1 tomorrow_date, "+
               "e.sal + nvl(e.comm,0) total_compensation, "+
               "to_char(e.hiredate,'dd-mon-yyyy') formated_hiredate"+
               "  from emp e, dept d "+
               " where e.deptno = d.deptno (+)"+
               " order by e.ename");
v.setFullSql(true);
 
/*
* Or, you can construct the SQL statement in parts like this.
*/
v.setSelectClause("e.empno,e.ename,e.sal,e.deptno,d.dname,d.loc,"+
                       "d.deptno,trunc(sysdate)+1 tomorrows_date,"+
                        "e.sal + nvl(e.comm,0) total_compensation, "+
                        "to_char(e.hiredate,'dd-mon-yyyy') formated_hiredate");
v.setFromClause("emp e, dept d");
v.setWhereClause("e.deptno = d.deptno (+)"); v.setOrderByClause("e.ename");
 
/*
* 3. Then resolve and save the view definition.
*/
v.resolveDefObject();
v.writeXMLContents();
v.saveXMLContents();
 
/*
* 4. Finally, use the dynamically-created view definition to construct
* instances of view objects based on it.  myAM is an instance of
* oracle.jbo.ApplicationModule that will parent this VO instance.
*/
ViewObject vo = myAM.createViewObject("SomeInstanceName", v.getFullName());

10.7.1 What You May Need to Know About MDS Repository Configuration

After defining a view definition, it is important to write and save the view definition into the MDS repository. If it is not properly saved, you may encounter issues when the request is redirected to a different node in a cluster because the definition cannot be loaded and accessed from the other node.

In order to save the definition, you need to define the mds-config element of adf-config.xml. For example, your adf-config.xml file should contain definitions similar to those shown in Example 10-31.

Example 10-31 MDS Configuration Defines Namespaces to Save View Definition

<mds-config version="11.1.1.000">
   <persistence-config>
<!-- metadata-namespaces must define /sessiondef and /persdef namespaces -->
     <metadata-namespaces>
       <namespace path="/sessiondef" metadata-store-usage="mymdsstore">
       <namespace path="/persdef" metadata-store-usage="mymdsstore">
     </metadata-namespaces>
 
     <metadata-store-usages>
       <metadata-store-usage id="mymdsstore" default-cust-store="true">
         <metadata-store name="fs1"
               class-name="oracle.mds.persistence.stores.file.FileMetadataStore">
<!-- metadata-path value should be the absolute dir path where you want 
     the metadata documents to be written -->
           <property name="metadata-path" value="/tmp">
         </metadata-store>
       </metadata-store-usage>
     </metadata-store-usages>
   </persistence-config>
 
   <cust-config>
     <match path="/">
       <customization-class name="oracle.adf.share.config.UserCC">
     </match>
   </cust-config>
</mds-config>

If your adf-config.xml file already defines the metadata-store-usage element, then you may be able to define the two namespaces /sessiondef and /persdef so that they use that metadata-store-usage definition. For more information about MDS configuration entries in the adf-config.xml file, see Section A.9, "adfc-config.xml." For more information about configuring MDS repositories, see the "Managing the Metadata Repository" chapter in Administering Oracle Fusion Middleware.

10.7.2 What You May Need to Know About Creating View Objects at Runtime

It's important to understand the overhead associated with creating view objects at runtime, as described in Section 10.7, "Programmatically Creating View Definitions and View Objects." 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.

10.8 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 oracle.summit.model.declblock package in the SummitADF_Examples application workspace.

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 10-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 10-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
}