7 Defining Polymorphic View Objects

This chapter describes how to use inheritance in ADF Business Components to create an ADF view object that exposes multiple view row types in an Oracle ADF application. As an alternative to working with entity object inheritance, this chapter also describes how to define a view object inheritance hierarchy to generate heterogeneous view rows.

This chapter includes the following sections:

7.1 About Polymorphic View Objects

The view object lets you create a table represented by its base entity object. Typically, when you create the entity-based view object, you create it to work with entity rows of a single type like Domestics, which perhaps include specific attributes that pertain to domestic customers. At other times you may want to query and update rows based on an entity object inheritance hierarchy in the same row set. For example, in the same row set, you might work with attributes that are common to the inheritance hierarchy of Customers, Domestics, and Internationals entity objects. Such a view object is called a polymorphic view object. For a client to properly use the polymorphic view object, the view object must know which entity object to delegate to for each row in its heterogeneous row set. To identify the row type, the polymorphic view object relies on a discriminator attribute to identify each row's corresponding table.

ADF Business Component supports two ways to create polymorphic view objects. You can create polymorphic view objects:

  • Based on polymorphic entity usages, which derive from the inheritance of the view object's entity subtypes

  • Based on polymorphic view rows, which derive from a view object hierarchy that you define

When you use the first type of polymorphic view object (based on inheritance of entity subtypes), the various entity usage subtypes are not exposed to the client. As far as the client is concerned, every row in the view object's result set has the same set of attributes and the same row type. This is accomplished because ADF Business Components ensures that the correct entity object row subtype is created based on the value of the discriminator attribute. In other words, with a polymorphic entity usage, only the entity row parts of a view row change type, while the view row type is constant.

The second type of polymorphic view object (based on the inheritance a view object hierarchy) allows you to configure the application module to allow one or more view row subtypes in the result. Only polymorphic view rows produces different subtypes of client-visible view row types. And, unlike polymorphic entity usages, only polymorphic view rows allows you to expose view row subtypes to the the client with additional attributes.

Note:

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

7.1.1 Polymorphic Entity Usage and Polymorphic View Rows Usages

You can work with either type of polymorphism, or you can combine the two.

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

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

A limitation exists when defining polymorphic view objects that requires view objects with discriminator attributes to be defined no more than one level deep. Your project may define a polymorphic view object with discriminator attributes to specify the row subtypes, but using another view object to specify further subrows based on the first polymorphic view object will cause the display of subrows to fail. When creating polymorphic view objects, limit the discriminator attribute definition to one level.

In summary, to work with view row polymorphism:

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

  2. Define a hierarchy of inherited view objects each of which provides a distinct value for the Subtype Value property of that discriminator attribute at the view object level (identified as DefaultValue for the attribute in the view object definition file).

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

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

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

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

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

7.2 Working with Polymorphic Entity Usages

A polymorphic entity usage is one that references a base entity object in an inheritance hierarchy and is configured to handle subtypes of that entity as well. Figure 7-1 shows the results of using a view object with a polymorphic entity usage. The entity-based CustomerList view object has the Customers entity object as its primary entity usage. The view object partitions each row retrieved from the database into an entity row with attributes specific to the various subtypes of Customers. It creates the appropriate entity row subtype based on consulting the value of the discriminator attribute. For example, if the CustomerList query retrieves one row for domestic company ABC Company and one row for international company Simms Athletic, the underlying entity rows would be as shown in the figure.

Figure 7-1 View Object with a Polymorphic Entity Usage Handles Entity Subtypes

Flow of entity subtypes

7.2.1 How to Create a View Object with a Polymorphic Entity Usage

The view object that you create with a polymorphic entity usage may inherit one or more of the attributes of the base entity object and the subtype entities. Attributes that you select from the entity objects will be overridden by the view object attribute definitions. When an entity-based view object references an entity object with a discriminator attribute, then JDeveloper enforces that the discriminator attribute is included in the query (in addition to the primary key attribute).

Before you begin:

It may be helpful to have an understanding of polymorphic view types. For more information, see Section 7.1, "About Polymorphic View Objects."

You will need to complete these tasks:

  1. Create the base entity object from which the polymorphic entity usages will inherit, as described in Section 4.2.2, "How to Create Single Entity Objects Using the Create Entity Wizard."

  2. Extend the base entity object to create the polymorphic entity usages and specify the discriminator attribute upon which the entity row subtype will be based, as described in Section 4.10.14, "How to Set the Discriminator Attribute for Entity Object Inheritance Hierarchies."

  3. Create an entity-based view object from the base entity object and select the attributes that are common to the subtype view objects that you will create for each polymorphic entity usage. The base view object will be used to create the subtype view objects. For details about creating an entity-based view object, see Section 5.2.1, "How to Create an Entity-Based View Object."

To create a view object with a polymorphic entity usage:

  1. In the Applications window, right-click your data model project and choose New and then View Object.

  2. In the Create View Object wizard, name the view object and then next to the Extends field, click Browse.

    For example, the data model project might define a base Customers entity object in order to support the creation of view objects with polymorphic entity usages for the Domestics and Internationals subtype entity objects. When you create the view object for each polymorphic entity usage, you might create one view object named DomesticList and another view object named InternationalList.

  3. In the Select Parent dialog, select the entity-based view object that you created from the base entity object and click OK.

    For example, when you create the view object DomesticList for the subtype entity object Domestics, you would select TheCustomer as the view object to extend.

  4. In the Create View Object wizard, click Next and note that the base entity object already appears in the Selected list and is labeled Extended, as shown in Figure 7-2.

    Figure 7-2 View Object with a Base Entity Selection

    Base entity object selected
  5. In the Entity Objects page, in the Available list, locate the entity subtype that you created from the base entity object and click OK.

    For example, when you create the view object DomesticList for the base entity object Customers, you would select Domestics as the entity subtype.

  6. In the Business Components dialog, click OK to override the entity usage for your view object.

    The Business Components dialog warns you that you will override the attributes of the base entity usage with the entity subtype, as shown in Figure 7-3.

    Figure 7-3 View Object with a Polymorphic Entity Subtype Selection

    Overriding entity with subtype
  7. In the Selected list, select the overridden entity object and click Subtypes.

  8. In the Select Subtypes dialog, select the desired entity subtype from the Selected list, and click OK.

    For example, for the DomesticList view object you would select the entity subtype Domestics, as shown in Figure 7-4.

    Figure 7-4 View Object with a Entity Subtype Selection

    Select Subtypes dialog
  9. In the Create View Object wizard, in the Entity Objects page, click Next and on the Attributes page of the wizard shuttle desired attributes from the entity subtype to the Selected list.

    For example, for the DomesticList view object you would select the State attribute from the entity subtype Domestics, as shown in Figure 7-5.

    Figure 7-5 View Object with a Entity Subtype Attribute Addition

    Select attribute from subtype
  10. Complete the wizard and click Finish.

    The Entity Objects page of the overview editor identifies the selected entity object with the entity subtype override. For example, the overview editor for the DomesticList view object identifies the overridden entity object Customers (Domestics): overridden with the subtype in parenthesis, as shown in Figure 7-6.

    Figure 7-6 View Object Editor Shows Entity Subtype is Overridden

    Overridden subtype in view object editor
  11. Repeat this procedure to create view objects for additional polymorphic entity usages that you created for the base entity object.

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

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

<ViewObject Name="DomesticList" ... >
   <EntityUsage Name="TheCustomer"
                Entity="oracle.summit.model.polymorphicsample.Customers" >
   </EntityUsage>
...
   <AttrArray Name="EntityImports">
      <Item Value="oracle.summit.model.polymorphicsample.Domestics" />
      <Item Value="oracle.summit.model.polymorphicsample.Internationals" />
   </AttrArray>
   <!-- etc. -->
</ViewObject>

7.2.3 What You May Need to Know About Entity Usages

When you work with polymorphic entity usages, you will want to follow these best practices to allow the discriminator attribute to filter the query to only contain the expected subtypes.

7.2.3.1 Your Query Must Limit Rows to Expected Entity Subtypes

If your view object expects to work with only a subset of the available entity subtypes in a hierarchy, you need to include an appropriate WHERE clause that limits the query to only return rows whose discriminator column matches the expected entity types. You should not rely solely on the discriminator attribute to perform subtype filtering in the query. Without the addition of attributes to limit the query, the view object will effectively query all rows and cause the client to discard rows that do not match any discriminator values.

7.2.3.2 Exposing Selected Entity Methods in View Rows Using Delegation

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

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

  • Invokes a method on it

For example, assume that the Customers entity object contains a performCustomerFeature() method in its CustomersImpl class. To expose this method to clients on the CustomerList view row, you can enable a custom view row Java class and write the method shown in Example 7-1. JDeveloper generates an entity accessor method in the view row class for each participating entity usage based on the entity usage alias name. Since the alias for the Customers entity in the CustomerList view object is "TheCustomer", it generates a getTheCustomer() method to return the entity row part related to that entity usage.

Example 7-1 Exposing Selected Entity Object Methods on View Rows Through Delegation

// In CustomerListRowImpl.java
public void performCustomerFeature() {
  getTheCustomer().performCustomerFeature();
}

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

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

Example 7-2 Invoking a View Row Method That Delegates to an Entity Object

CustomerList customerlist = (CustomerList)am.findViewObject("CustomerList");
customerlist.executeQuery();
while (customerlist.hasNext()) {
  CustomerListRow customer = (CustomerListRow)customerlist.next();
  System.out.print(customer.getEmail()+"->");
  customer.performCustomerFeature();
}

Running the client code in Example 7-2 produces the following output:

austin->## performCustomerFeature as Domestics
hbaer->## performCustomerFeature as Internationals
:
sking->## performCustomerFeature as Domestic
:

Rows related to Customers entities display a message confirming that the performCustomerFeature() method in the CustomersImpl class was used. Rows related to Domestics and Internationals entities display a different message, highlighting the different implementations that the respective DomesticsImpl and InternationalsImpl classes have for the inherited performCustomerFeature() method.

7.2.3.3 Creating New Rows With the Desired Entity Subtype

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

Example 7-3 Exposing Custom Methods to Create New Rows with Entity Subtypes

// In CustomerListImpl.java
public CustomerListRow createCustomersRow() {
  NameValuePairs nvp = new NameValuePairs();
  nvp.setAttribute("CustomerTypeCode","DOMESTIC");
  return (CustomerListRow)createAndInitRow(nvp);
}
public CustomerListRow createInternationalsRow() {
  NameValuePairs nvp = new NameValuePairs();
  nvp.setAttribute("CustomerTypeCode","INTERNATIONAL");
  return (CusotmersListRow)createAndInitRow(nvp);
} 

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

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

## performCustomerFeature as Customer
## performCustomerFeature as Domestic
## performCustomerFeature as International

Example 7-4 Creating New View Rows with Different Entity Subtypes

// In TestEntityPolymorphism.java
CustomerListRow newCustomer = (CustomerListRow)CustomerList.createRow();
CustomerListRow newDomestic  = Customerlist.createDomesticsRow();
CustomerListRow newInternational = Customerlist.createInternationalsRow();
newCustomer.performCustomerFeature();
newDomestic.performCustomerFeature();
newInternational.performCustomerFeature();

7.3 Working with Polymorphic View Rows

In the example shown in Section 7.2, "Working with Polymorphic Entity Usages," the polymorphism occurs "behind the scenes" at the entity object level. Since the client code works with all view rows using the same CustomerListRow interface, it cannot distinguish between rows based on a Domestics entity object from those based on a Customers entity object. The code works with all view rows using the same set of view row attributes and methods common to all types of underlying entity subtypes.

If you configure a view object to support polymorphic view rows, then the client can work with different types of view rows using a view row interface specific to the type of row it is. By doing this, the client can access view attributes or invoke view row methods that are specific to a given subtype as needed. Figure 7-7 illustrates the hierarchy of view objects that enables this feature for the CustomerList example considered above. DomesticList and InternationalList are child view objects that extend the base (or parent) CustomerList view object. Notice that each one includes an additional attribute specific to the subtype of Customers they have as their entity usage. DomesticList includes an additional State attribute, while InternationalList excludes the State attribute and includes the Language attribute. When configured for view row polymorphism, a client can work with the results of the CustomerList view object using:

  • CustomerListRow interface for view rows related to customers

  • DomesticListRow interface for view rows related to domestic customers

  • InternationalListRow interface for view rows related to international customers

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

Figure 7-7 Hierarchy of View Object Subtypes Enables View Row Polymorphism

Subtype hierarchy enables row polymorphism

7.3.1 How to Create a View Object with Polymorphic View Rows

The view object that you create with polymorphic view rows may inherit one or more of the attributes from a hierarchy of view objects, each with their own base entity object. Attributes that you select from the extended view objects will be overridden by the polymorphic view row definitions in the parent view object. When an entity-based view object references an entity object with a discriminator attribute, then JDeveloper enforces that the discriminator attribute is included in the query (in addition to the primary key attribute).

Before you begin:

It may be helpful to have an understanding of polymorphic view types. For more information, see Section 7.3, "Working with Polymorphic View Rows."

To create a view object with polymorphic view rows:

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

    For example, Figure 7-7 shows CustomerList view object is the base view object.

  2. In the overview editor, click the Attributes navigation tab, select a discriminator attribute for the view row, and then click the Details tab.

  3. In the Details page, give the discriminator attribute a default value and select the Polymorphic Discriminator checkbox to mark the attribute as the one that distinguishes which view row interface to use.

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

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

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

    For example, Figure 7-7 shows DomesticList extends the base CustomerList view object.

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

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

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

    The DomesticList view object provides the value of "DOMESTIC" for the CustomerTypeCode discriminator attribute.

  8. Repeat steps 5-7 to add additional extended view objects as needed.

    For example, the InternationalList view object is a second one that extends CustomerList. It supplies the value "INTERNATIONAL" for the CustomerTypeCode discriminator attribute.

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

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

    For example, the CustomersModule application module has instances of CustomerList, DomesticList, and InternationalList view objects.

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

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

7.3.2 What You May Need to Know About Polymorphic View Rows

When you work with polymorphic view rows, you can interact with the row types to customize the attributes to display or to delegate to methods specific to the entity subtypes of the view rows.

7.3.2.1 Selecting Subtype-Specific Attributes in Extended View Objects

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

  • To select attributes that are specific to the entity subtype

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

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

To override the entity usage for the view object:

  1. In the Applications window, double-click the view object that contains the entity usage that you want to override.

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

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

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

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

  4. Click > to shuttle it to the Selected list

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

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

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

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

After overriding the entity usage in an extended view object to reference a subtype entity, you can write view row methods that delegate to methods specific to the subtype entity class. Example 7-5 shows the code for a performInternationalFeature() method in the custom view row class for the InternationalList view object. It casts the return value from the getTheCustomer() entity row accessor to the subtype InternationalsImpl, and then invokes the performInternationalFeature() method that is specific to Internationals entity objects.

Example 7-5 View Row Method Delegating to Method in Subtype Entity

// In InternationalListRowImpl.java
public void performInternationalFeature() {
   InternationalsImpl international = (InternationalsImpl)getTheCustomer();
   international.performInternationalFeature();
}

Note:

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

7.3.2.3 Working with Different View Row Interface Types in Client Code

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

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

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

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

    • Call the performDomesticFeature() method specific to the DomesticListRow interface, and

    • Access the value of the State attribute that is specific to the DomesticList view object.

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

    • Call the performInternationalFeature() method specific to the InternationalListRow interface, and

    • Access the value of the Language attribute that is specific to the InternationalList view object.

  4. Otherwise, just call a method on the CustomerListRow

Example 7-6 Using View Row Polymorphism in Client Code

// In TestViewRowPolymorphism.java
ViewObject vo = am.findViewObject("CustomerList");
vo.executeQuery();
// 1. Iterate over the rows in the CustomerList view object
while (vo.hasNext()) {
  CustomerListRow Customer = (CustomerListRow)vo.next();
  System.out.print(Customer.getEmail()+"->");
  if (Customer instanceof DomesticListRow) {
    // 2. If the row is a DomesticListRow, cast it
    DomesticListRow mgr = (DomesticListRow)Customer;
    mgr.performDomesticFeature();       
    System.out.println("State: "+domestic.getState());
  }
  else if (Customer instanceof InternationalListRow) {
    // 3. If the row is an InternationalListRow, cast it
    InternationalListRow international = (InternationalListRow)Customer;
    international.performInternationalFeature();       
    System.out.println("Speaks English: "+international.getLanguage());        
  }
  else {
    // 4. Otherwise, just call a method on the CustomerListRow 
    Customer.performCustomerFeature();
  }
}

Running the code in Example 7-6 produces the following output:

daustin->## performInternationalFeature called
English spoken: Yes
hbaer->## performCustomerFeature as Customer
:
sking->## performDomesticFeature called
State: CA
:

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