38 Advanced Entity Object Techniques

This chapter describes advanced techniques for use in ADF entity objects in an ADF Business Components data model project.

This chapter includes the following sections:

Note:

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

38.1 Creating Custom, Validated Data Types Using Domains

When you find yourself repeating the same sanity-checking validations on the values of similar attributes across multiple entity objects, you can save yourself time and effort by creating your own data types that encapsulate this validation. For example, imagine that across your business domain layer there are numerous entity object attributes that store strings that represent email addresses. One technique you could use to ensure that end users always enter a valid email address everywhere one appears in your business domain layer is to:

  • Use a basic String data type for each of these attributes

  • Add an attribute-level method validator with Java code that ensures that the String value has the format of a valid email address for each attribute

However, these approaches can become tedious quickly in a large application. Fortunately, ADF Business Components offers an alternative that allows you to create your own EmailAddress data type that represents an email address. After centralizing all of the sanity-checking regarding email address values into this new custom data type, you can use the EmailAddress as the type of every attribute in your application that represents an email address. By doing this, you make the intention of the attribute values more clear to other developers and simplify application maintenance by putting the validation in a single place. ADF Business Components calls these developer-created data types domains.

Domains are Java classes that extend the basic data types like String, Number, and Date to add constructor-time validation to insure the candidate value passes relevant sanity checks. They offer you a way to define custom data types with cross-cutting behavior such as basic data type validation, formatting, and custom metadata properties in a way that are inherited by any entity objects or view objects that use the domain as the Java type of any of their attributes.

Note:

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

38.1.1 How to Create a Domain

To create a domain, use the Create Domain wizard. This is available in the New Gallery in the ADF Business Components category.

To create a domain:

  1. In the Application Navigator, right-click the project for which you want to create a domain and choose New.

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

    This launches the Create Domain wizard.

  3. On the Name page, specify a name for the domain and a package in which it will reside. To create a domain based on a simple Java type, leave Domain for an Oracle Object Type unchecked.

  4. Click Next.

  5. On the Settings page, indicate the base type for the domain and the database column type to which it will map. For example, if you were creating a domain called ShortEmailAddress to hold eight-character short email addresses, you would set the base type to String and the Database Column Type to VARCHAR2(8). You can set other common attribute settings on this panel as well.

  6. Click Finish to create your domain.

38.1.2 What Happens When You Create a Domain

When you create a domain, JDeveloper creates its XML component definition in the subdirectory of your project's source path that corresponds to the package name you chose. For example, if you created the ShortEmailAddress domain in the devguide.advanced.domains package, JDeveloper would create the ShortEmailAddress.xml file in the ./devguide/advanced/domains subdirectory. A domain always has a corresponding Java class, which JDeveloper creates in the common subpackage of the package where the domain resides. This means it would create the ShortEmailAddress.java class in the devguide.advanced.domains.common package. The domain's Java class is automatically generated with the appropriate code to behave in a way that is identical to one of the built-in data types.

38.1.3 What You May Need to Know About Domains

The sections that follow describe some of the things you may need to know about when working with domains.

38.1.3.1 Using Domains for Entity and View Object Attributes

After you've created a domain in a project, it appears among the list of available data types in the Attribute Type dropdown list in the entity object and view object wizards and dialogs. To use the domain as the type of a given attribute, just pick it from the list.

Note:

The entity-mapped attributes in an entity-based view object inherit their data type from their corresponding underlying entity object attribute, so if the entity attribute uses a domain type, so will the matching view object attribute. For transient or SQL-derived view object attributes, you can directly set the type to use a domain since it is not inherited from any underlying entity.

38.1.3.2 Validate Method Should Throw DataCreationException If Sanity Checks Fail

Typically, the only coding task you need to do for a domain is to write custom code inside the generated validate() method. Your implementation of the validate() method should perform your sanity checks on the candidate value being constructed, and throw a DataCreationException in the oracle.jbo package if the validation fails.

In order to throw an exception message that is translatable, you can create a message bundle class similar to the one shown in Example 38-1. Create it in the same common package as your domain classes themselves. The message bundle returns an array of {MessageKeyString,TranslatableMessageString} pairs.

Example 38-1 Custom Message Bundle Class for Domain Exception Messages

package devguide.advanced.simpledomains.common;

import java.util.ListResourceBundle;

public class ErrorMessages extends ListResourceBundle {
  public static final String INVALID_SHORTEMAIL = "30002";
  public static final String INVALID_EVENNUMBER = "30003";
  private static final Object[][] sMessageStrings = new String[][] {
      { INVALID_SHORTEMAIL,
        "A valid short email address has no @-sign or dot."},
      { INVALID_EVENNUMBER,
        "Number must be even."}
    };

  /**
   * Return String Identifiers and corresponding Messages
   * in a two-dimensional array.
   */
  protected Object[][] getContents() {
    return sMessageStrings;
  }
}

38.1.3.3 String Domains Aggregate a String Value

Since String is a base JDK type, a domain based on a String aggregates a private mData String member field to hold the value that the domain represents. Then, the class implements the DomainInterface expected by the ADF runtime, as well as the Serializable interface, so the domain can be used in method arguments or returns types of ADF components custom client interfaces.

Example 38-2 shows the validate() method for a simple ShortEmailAddress domain class. It tests to make sure that the mData value does not contains an at-sign or a dot, and if it does, then the method throws DataCreationException referencing an appropriate message bundle and message key for the translatable error message.

Example 38-2 Simple ShortEmailAddress String-Based Domain Type with Custom Validation

public class ShortEmailAddress
               extends Object implements DomainInterface, Serializable {
  private String mData;
  // . . .
  /**Implements domain validation logic and throws a JboException on error. */
  protected void validate() {
    int atpos = mData.indexOf('@');
    int dotpos = mData.lastIndexOf('.');
    if (atpos > -1 || dotpos > -1) {
      throw new DataCreationException(ErrorMessages.class,
      ErrorMessages.INVALID_SHORTEMAIL,null,null);
    }
  }
  // . . .
} 

38.1.3.4 Other Domains Extend Existing Domain Type

Other simple domains based on a built-in type in the oracle.jbo.domain package extend the base type as shown in Example 38-3. It illustrates the validate() method for a simple Number-based domain called EvenNumber that represents even numbers.

Example 38-3 Simple EvenNumber Number-Based Domain Type with Custom Validation

public class EvenNumber extends Number {
  // . . .
  /**
   * Validates that value is an even number, or else
   * throws a DataCreationException with a custom
   * error message.
   */
  protected void validate() {
    if (getValue() % 2 == 1) {
      throw new DataCreationException(ErrorMessages.class,
      ErrorMessages.INVALID_EVENNUMBER,null,null);
    }
  }
  // . . .
}

38.1.3.5 Simple Domains Are Immutable Java Classes

When you create a simple domain based on one of the basic data types, it is an immutable class. This just means that once you've constructed a new instance of it like this:

ShortEmailAddress email = new ShortEmailAddress("emailaddress1");

You cannot change its value. If you want to reference a different short email address, you just construct another one:

ShortEmailAddress email = new ShortEmailAddress("emailaddress2");

This is not a new concept since it's the same way that String, Number, and Date classes behave, among others.

38.1.3.6 Creating Domains for Oracle Object Types When Useful

The Oracle database supports the ability to create user-defined types in the database. For example, you could create a type called POINT_TYPE using the following DDL statement:

create type point_type as object (
   x_coord number,
   y_coord number
);

If you use user-defined types like POINT_TYPE, you can create domains based on them, or you can reverse-engineer tables containing columns of object type to have JDeveloper create the domain for you.

Manually Creating Oracle Object Type Domains

To create a domain yourself, perform the following steps in the Create Domain wizard:

  1. In the Application Navigator, right-click the project for which you want to create a domain and choose New.

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

    This launches the Create Domain wizard.

  3. On the Name page, check the Domain for an Oracle Object Type checkbox, then select the object type for which you want to create a domain from the Available Types list.

  4. Click Next.

  5. On the Settings page, use the Attribute dropdown list to switch between the multiple domain properties to adjust the settings as appropriate.

  6. Click Finish to create the domain.

Reverse-Engineering Oracle Object Type Domains

In addition to manually creating object type domains, when you use the Business Components from Tables wizard and select a table containing columns of an Oracle object type, JDeveloper creates domains for those object types as part of the reverse-engineering process. For example, imagine you created a table like this with a column of type POINT_TYPE:

create table interesting_points(
  id number primary key,
  coordinates point_type,
  description varchar2(20)
);

If you create an entity object for the INTERESTING_POINTS table in the Business Components from Tables wizard, then you will get both an InterestingPoints entity object and a PointType domain. The latter will have been automatically created, based on the POINT_TYPE object type, since it was required as the data type of the Coordinates attribute of the InterestingPoints entity object.

Unlike simple domains, object type domains are mutable. JDeveloper generates getter and setter methods into the domain class for each of the elements in the object type's structure. After changing any domain properties, when you set that domain as the value of a view object or entity object attribute, it is treated as a single unit. ADF does not track which domain properties have changed, only that a domain-valued attribute value has changed.

Note:

Domains based on Oracle object types are useful for working programmatically with data whose underlying type is an oracle object type. They also can simplify passing and receiving structure information to stored procedures. However, support for working with object type domains in the ADF binding layer is complete, so it's not straightforward to use object domain-valued attributes in declaratively-databound user interfaces.

38.1.3.7 Quickly Navigating to the Domain Class

After selecting a domain in the Application Navigator, you can quickly navigate to its implementation class using one of the following methods:

  • In the Application Navigator, right-click the domain and choose Go to Domain Class from the context menu.

  • In the Structure window, double-click the domain class.

38.1.3.8 Domains Get Packaged in the Common JAR

When you create a business components archive, as described in Section 37.6, "Working with Libraries of Reusable Business Components," the domain classes and message bundle files in the *.common subdirectories of your project's source path get packaged into the *CSCommon.jar. They are classes that are common to both the middle-tier application server and to an eventual remote-client you might need to support.

38.1.3.9 Entity and View Object Attributes Inherit Custom Domain Properties

You can define custom metadata properties on a domain. Any entity object or view object attribute based on that domain inherits those custom properties as if they had been defined on the attribute itself. If the entity object or view object attribute defines the same custom property, its setting takes precedence over the value inherited from the domain.

38.1.3.10 Domain Settings Cannot Be Less Restrictive at Entity or View Level

JDeveloper enforces the declarative settings you impose at the domain definition level; they cannot be made less restrictive for the entity object or view object for an attribute based on the domain type. For example, if you define a domain to have its Updatable property set to While New, then when you use your domain as the Java type of an entity object attribute, you can set Updatable to be Never (more restrictive) but you cannot set it to be Always. Similarly, if you define a domain to be Persistent, you cannot make it transient later. When sensible for your application, set declarative properties for a domain to be as lenient as possible so you can later make them more restrictive as needed.

38.2 Updating a Deleted Flag Instead of Deleting Rows

For auditing purposes, once a row is added to a table, sometimes your requirements may demand that rows are never physically deleted from the table. Instead, when the end user deletes the row in the user interface, the value of a DELETED column should be updated from "N" to "Y" to mark it as deleted. You can use two method overrides to alter an entity object's default behavior to achieve this effect. For example, say you want to change the Products entity from the Fusion Order Demo application to behave in this way.

38.2.1 How to Update a Deleted Flag When a Row Is Removed

To update a deleted flag when a row is removed, enable a custom Java class for your entity object and override the remove() method to set the deleted flag before calling the super.remove() method. Example 38-4 shows what this would look like in the custom Java class of an entity object. It is important to set the attribute before calling super.remove() since an attempt to set the attribute of a deleted row will encounter the DeadEntityAccessException.

This example presumes that you've altered the PRODUCTS table to have an additional DELETED column, and synchronized the Products entity with the database to add the corresponding Deleted attribute.

Example 38-4 Updating a Deleted Flag When a Products Entity Row Is Removed

// In your custom Java entity class
public void remove() {
  setDeleted("Y");
  super.remove();
}

The row will still be removed from the row set, but it will have the value of its Deleted flag modified to "Y" in the entity cache. The second part of implementing this behavior involves forcing the entity to perform an UPDATE instead of an DELETE when it is asked to perform its DML operation. You need to implement both parts for a complete solution.

38.2.2 Forcing an Update DML Operation Instead of a Delete

To force an entity object to be updated instead of deleted, override the doDML() method and write code that conditionally changes the operation flag. When the operation flag equals DML_DELETE, your code will change it to DML_UPDATE instead. Example 38-5 shows what this would look like in the custom Java class of an entity object.

This example presumes that you've altered the PRODUCTS table to have an additional DELETED column, and synchronized the Products entity with the database to add the corresponding Deleted attribute.

Example 38-5 Forcing an Update DML Operation Instead of a Delete

// In your custom Java entity class
protected void doDML(int operation, TransactionEvent e) {
   if (operation == DML_DELETE) {
     operation = DML_UPDATE;
   }
   super.doDML(operation, e);
  }

With this overridden doDML() method in place to complement the overridden remove() method described in Section 38.2.1, any attempt to remove an entity through any view object with a corresponding entity usage will update the DELETED column instead of physically deleting the row. Of course, in order to prevent "deleted" products from appearing in your view object query results, you will need to appropriately modify their WHERE clauses to include only products WHERE DELETED = 'N'.

38.3 Using Update Batching

You can use update batching to reduce the number of DML statements issued with multiple entity modifications.

By default, the ADF Business Components framework performs a single DML statement (INSERT, UPDATE, DELETE) for each modified entity of a given entity definition type. For example, say you have an Employee entity object type for which multiple instances are modified during typical use of the application. If two instances were created, three existing instances modified, and four existing instances deleted, then at transaction commit time the framework issues nine DML statements (2 INSERTs, 3 UPDATEs, and 4 DELETEs) to save these changes.

If you will frequently be updating more than one entity of a given type in a transaction, consider using the update batching feature for that entity definition type. In the example, update batching (with a threshold of 1) causes the framework to issue just three DML statements: one bulk INSERT statement processing two inserts, one bulk UPDATE statement processing three updates, and one bulk DELETE statement processing four deletes.

Note:

The batch update feature is disabled if any of following conditions are present:

  • The entity object has attributes of BLOB or CLOB type. Batch DML with streaming data types is not supported.

  • The entity object has attributes that are set to Refresh After Insert or Refresh After Update. There is no method to bulk-return all of the trigger-assigned values in a single round trip, so retrieving and updating attributes doesn't work with batch DML.

  • The entity object was created from a table that did not have a primary key. If an entity object is reverse-engineered from a table that does not have a primary key, a ROWID-valued attribute is created and assigned as the primary key instead. The ROWID value is managed as a Retrieve-on-Insert value, so it will not work with batch DML.

To enable update batching for an entity

  1. Open the appropriate entity object in the overview editor.

  2. In the Application Navigator, double-click the appropriate entity to open it in the overview editor.

  3. On the General page of the overview editor, expand the Tuning section, select the Use Update Batching checkbox, and specify the appropriate threshold.

This establishes a batch processing threshold beyond which ADF will process the modifications in a bulk DML operation.

38.4 Advanced Entity Association Techniques

This section describes several advanced techniques for working with associations between entity objects.

38.4.1 Modifying Association SQL Clause to Implement Complex Associations

When you need to represent a more complex relationship between entities than one based only on the equality of matching attributes, you can modify the association's SQL clause to include more complex criteria. For example, sometimes the relationship between two entities depends on effective dates. A Product may be related to a Supplier, however if the name of the supplier changes over time, each row in the SUPPLIERS table might include additional EFFECTIVE_FROM and EFFECTIVE_UNTIL columns that track the range of dates in which that product row is (or was) in use. The relationship between a Product and the Supplier with which it is associated might then be described by a combination of the matching SupplierId attributes and a condition that the product's RequestDate lie between the supplier's EffectiveFrom and EffectiveUntil dates.

You can setup this more complex relationship in the overview editor for the association. First add any additional necessary attribute pairs on the Relationship page, which in this example would include one (EffectiveFrom, RequestDate) pair and one (EffectiveUntil, RequestDate) pair. Then on the Query page you can edit the Where field to change the WHERE clause to be:

(:Bind_SupplierId = Product.SUPPLIER_ID) AND
(Product.REQUEST_DATE BETWEEN :Bind_EffectiveFrom
                                 AND :Bind_EffectiveUntil)

For more information about creating associations, see Section 4.3, "Creating and Configuring Associations."

38.4.2 Exposing View Link Accessor Attributes at the Entity Level

When you create a view link between two entity-based view objects, on the View Link Properties page, you have the option to expose view link accessor attributes both at the view object level as well as at the entity object level. By default, a view link accessor is only exposed at the view object level of the destination view object. By checking the appropriate In Entity Object: SourceEntityName or In Entity Object:DestinationEntityName checkbox, you can opt to have JDeveloper include a view link attribute in either or both of the source or destination entity objects. This can provide a handy way for an entity object to access a related set of related view rows, especially when the query to produce the rows only depends on attributes of the current row.

38.4.3 Optimizing Entity Accessor Access by Retaining the Row Set

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

Since there is a small amount of overhead associated with creating the row set, if your code makes numerous calls to the same association accessor attributes, you can consider enabling the association accessor row set retention for the source entity object in the association. You can enable retention of the association accessor row set using the overview editor for the entity object that is the source for the association accessor. Select Retain Association Accessor Rowset in the Tuning section of the General page of the overview editor for the entity object.

Alternatively, you can enable a custom Java entity collection class for your entity object. As with other custom entity Java classes you've seen, you do this on the Select Java Options dialog that you open from the Java page of the overview editor for the entity object. In the dialog, select Generate Entity Collection Class. Then, in the YourEntityCollImpl class that JDeveloper creates for you, override the init() method, and add a line after super.init() that calls the setAssociationAccessorRetained() method passing true as the parameter. It affects all association accessor attributes for that entity object.

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

Note, however, that with accessor retention enabled, your failure to call reset() each time before you iterate through the rows in the accessor row set can result in a subtle, hard-to-detect error in your application. For example, say you iterate over the rows in an association accessor row set as shown in Example 38-6 to calculate some aggregate total.

Example 38-6 Iterating Over a Row Set Incorrectly

// In your custom Java entity classRowSet rs = (RowSet)getProducts();
while (rs.hasNext()) {
  ProductImpl r = (ProductImpl)rs.next();
  // Do something important with attributes in each row
}

The first time you work with the accessor row set, the code will work. However, since the row set (and its default row set iterator) are retained, the second and any subsequent time you access the row set, the current row will already be at the end of the row set and the while loop will be skipped because rs.hasNext() will be false. Instead, with this feature enabled, write your accessor iteration code as shown in Example 38-7.

Example 38-7 Iterating Over a Row Set and Resetting to the First Row

// In your custom Java entity class
RowSet rs = (RowSet)getProducts();
rs.reset(); // Reset default row set iterator to slot before first row!
while (rs.hasNext()) {
  ProductImpl r = (ProductImpl)rs.next();
  // Do something important with attributes in each row
}

38.5 Basing an Entity Object on a PL/SQL Package API

If you have a PL/SQL package that encapsulates insert, update, and delete access to an underlying table, you can override the default DML processing event for the entity object that represents that table to invoke the procedures in your PL/SQL API instead. Often, such PL/SQL packages are used in combination with a companion database view. Client programs read data from the underlying table using the database view, and "write" data back to the table using the procedures in the PL/SQL package.

For example, say you want to create a Product entity object based on such a combination of a view and a package.

Given the PRODUCTS table in the Fusion Order Demo schema, consider a database view named PRODUCTS_V, created using the following DDL statement:

create or replace view products_v
as select product_id,name,image,description from products;

In addition, consider the simple PRODUCTS_API package shown in Example 38-8 that encapsulates insert, update, and delete access to the underlying PRODUCTS table.

Example 38-8 Simple PL/SQL Package API for the PRODUCTS Table

create or replace package products_api is
  procedure insert_product(p_prod_id number,
                           p_name varchar2,
                           p_supplier_id number,
                           p_list_price number,
                           p_min_price number,
                           p_ship_code varchar2);
  procedure update_product(p_prod_id number,
                           p_name varchar2,
                           p_supplier_id number,
                           p_list_price number,
                           p_min_price number,
                           p_ship_code varchar2);
  procedure delete_product(p_prod_id number);
end products_api;

To create an entity object based on this combination of database view and PL/SQL package, you would perform the following tasks:

Note:

The example in these sections refers to the EntityWrappingPLSQLPackage project of the AdvancedEntityExamples application workspace in the StandaloneExamples module of the Fusion Order Demo application.

38.5.1 How to Create an Entity Object Based on a View

To create an entity object based on a view, you use the Create Entity Object wizard as described in Section 4.2.2, "How to Create Single Entity Objects Using the Create Entity Wizard," with the following exceptions:

  • On the Name page, give the entity a name like Product and check the Views checkbox at the bottom of the Database Objects section.

    This enables the display of the available database views in the current schema in the Schema Object list.

  • Select the desired database view in the Schema Object list.

  • On the Attribute Settings page, use the Select Attribute dropdown list to choose the attribute that will act as the primary key, then enable the Primary Key setting for that property.

    Note:

    When defining the entity based on a view, JDeveloper cannot automatically determine the primary key attribute since database views do not have related constraints in the database data dictionary.

38.5.2 What Happens When You Create an Entity Object Based on a View

By default, an entity object based on a view performs all of the following directly against the underlying database view:

  • SELECT statement (for findByPrimaryKey())

  • SELECT FOR UPDATE statement (for lock()), and

  • INSERT, UPDATE, DELETE statements (for doDML())

To use stored procedure calls, you will need to override the doDML() operations (as described in Section 38.5.3, "Centralizing Details for PL/SQL-Based Entities into a Base Class"), and possibly override the lock()and findByPrimaryKey()handling (as described in Section 38.5.4, "Implementing the Stored Procedure Calls for DML Operations").

38.5.3 Centralizing Details for PL/SQL-Based Entities into a Base Class

If you plan to have more than one entity object based on a PL/SQL API, it's a smart idea to abstract the generic details into a base framework extension class. In doing this, you'll be using several of the concepts described in Chapter 37, "Advanced Business Components Techniques." Start by creating a PLSQLEntityImpl class that extends the base EntityImpl class that each one of your PL/SQL-based entities can use as their base class. As shown in Example 38-9, you'll override the doDML() method of the base class to invoke a different helper method based on the operation.

Note:

If you are already using an extended entity implementation class for your entity, you can extend it further with the PLSQLEntityImpl class. For example, if you have a framework extension class named zzEntityImpl, you would create a PLSQLEntityImpl class that extends the zzEntityImpl class.

Example 38-9 Overriding doDML() to Call Different Procedures Based on the Operation

// In PLSQLEntityImpl.java
protected void doDML(int operation, TransactionEvent e) {
  // super.doDML(operation, e);
  if (operation == DML_INSERT)
    callInsertProcedure(e);
  else if (operation == DML_UPDATE)
    callUpdateProcedure(e);
  else if (operation == DML_DELETE)
    callDeleteProcedure(e);
}

In the PLSQLEntityImpl.java base class, you can write the helper methods so that they perform the default processing like this:

// In PLSQLEntityImpl.java
/* Override in a subclass to perform non-default processing */
protected void callInsertProcedure(TransactionEvent e) {
  super.doDML(DML_INSERT, e);
}
/* Override in a subclass to perform non-default processing */  
protected void callUpdateProcedure(TransactionEvent e) {
  super.doDML(DML_UPDATE, e);
}
/* Override in a subclass to perform non-default processing */  
protected void callDeleteProcedure(TransactionEvent e) {
  super.doDML(DML_DELETE, e);
}

After putting this infrastructure in place, when you base an entity object on the PLSQLEntityImpl class, you can use the Source > Override Methods menu item to override the callInsertProcedure(), callUpdateProcedure(), and callDeleteProcedure() helper methods and perform the appropriate stored procedure calls for that particular entity.

Note:

If you do not override these helper methods in a subclass, they will perform the default processing as defined in the superclass. You only need to override the operations in the doDML() method that you want to provide alternative processing for.

To simplify the task of implementing these calls, you could add the callStoredProcedure() helper method (described in Chapter 37, "Invoking Stored Procedures and Functions") to the PLSQLEntityImpl class as well. This way, any PL/SQL-based entity objects that extend this class can leverage the helper method.

38.5.4 Implementing the Stored Procedure Calls for DML Operations

To implement the stored procedure calls for DML operations, you will need to create a custom Java class for the entity object and override the operations in it.

To create the custom Java class with the override methods:

  1. In the Application Navigator, double-click the Product entity object to open it in the overview editor.

  2. On the Java page of the overview editor, click the Edit Java options icon.

  3. In the Select Java Options dialog, click Classes Extend.

  4. In the Override Base Classes dialog, in the Row field, enter the package and class of the PLSQLEntityImpl class, or click Browse to search and select it.

  5. Then select Generate Entity Object Class, and click OK.

  6. In the Application Navigator, double-click ProductsImpl.java to open it in the overview editor.

  7. From the Source menu, choose Override Methods.

  8. In the Override Methods dialog, select the callInsertProcedure(), callUpdateProcedure(), and callDeleteProcedure() methods, and click OK.

  9. Then enter the necessary code to override these procedures.

Example 38-10 shows the code you would write in these overridden helper methods.

Example 38-10 Leveraging a Helper Method to Invoke Insert, Update, and Delete Procedures

// In ProductsImpl.java
protected void callInsertProcedure(TransactionEvent e) {
    callStoredProcedure("products_api.insert_product(?,?,?,?,?,?)",
         new Object[] { getProductId(), getProductName(), getSupplierId(),
                getListPrice(), getMinPrice(), getShippingClassCode() });
}
protected void callUpdateProcedure(TransactionEvent e) {
    callStoredProcedure("products_api.update_product(?,?,?,?,?,?)",
         new Object[] { getProductId(), getProductName(), getSupplierId(),
                        getListPrice(), getMinPrice(), getShippingClassCode() });
}
protected void callDeleteProcedure(TransactionEvent e) {
    callStoredProcedure("products_api.delete_product(?)",
         new Object[] { getProductId() });
}

At this point, if you create a default entity-based view object called Products for the Products entity object and add an instance of it to a ProductsModule application module you can quickly test inserting, updating, and deleting rows from the Products view object instance in the Business Component Browser.

Often, overriding just the insert, update, and delete operations will be enough. The default behavior that performs the SELECT statement for findByPrimaryKey() and the SELECT FOR UPDATE statement for the lock() against the database view works for most basic kinds of views.

However, if the view is complex and does not support SELECT FOR UPDATE or if you need to perform the findByPrimaryKey() and lock() functionality using additional stored procedures API's, then you can use the technique described in Section 38.5.5, "Adding Select and Lock Handling.".

38.5.5 Adding Select and Lock Handling

You can handle the lock() and findByPrimaryKey() functionality of an entity object by invoking stored procedures if necessary. Imagine that the PRODUCTS_API package were updated to contain the two additional procedures shown in Example 38-11. Both the lock_product and select_product procedures accept a primary key attribute as an IN parameter and return values for the remaining attributes using OUT parameters.

Example 38-11 Additional Locking and Select Procedures for the PRODUCTS Table

/* Added to PRODUCTS_API package */
  procedure lock_product(p_prod_id number,
                         p_name OUT varchar2,
                         p_supplier_id OUT number,
                         p_list_price OUT number,
                         p_min_price OUT number,
                         p_ship_code OUT varchar2);
  procedure select_product(p_prod_id number,
                         p_name OUT varchar2,
                         p_supplier_id OUT number,
                         p_list_price OUT number,
                         p_min_price OUT number,
                         p_ship_code OUT varchar2); 

38.5.5.1 Updating PLSQLEntityImpl Base Class to Handle Lock and Select

You can extend the PLSQLEntityImpl base class to handle the lock() and findByPrimaryKey() overrides using helper methods similar to the ones you added for insert, update, delete. At runtime, both the lock() and findByPrimaryKey() operations end up invoking the lower-level entity object method called doSelect(boolean lock). The lock() operation calls doSelect() with a true value for the parameter, while the findByPrimaryKey() operation calls it passing false instead.

Example 38-12 shows the overridden doSelect() method in PLSQLEntityImpl to delegate as appropriate to two helper methods that subclasses can override as necessary.

Example 38-12 Overriding doSelect() to Call Different Procedures Based on the Lock Parameter

// In PLSQLEntityImpl.java
protected void doSelect(boolean lock) {
  if (lock) {
    callLockProcedureAndCheckForRowInconsistency();
  } else {
    callSelectProcedure();
  }
}

The two helper methods are written to just perform the default functionality in the base PLSQLEntityImpl class:

// In PLSQLEntityImpl.java
/* Override in a subclass to perform non-default processing */  
protected void callLockProcedureAndCheckForRowInconsistency() {
  super.doSelect(true);
}
/* Override in a subclass to perform non-default processing */  
protected void callSelectProcedure() {
  super.doSelect(false);
}

Notice that the helper method that performs locking has the name callLockProcedureAndCheckForRowInconsistency(). This reminds developers that it is their responsibility to perform a check to detect at the time of locking the row whether the newly-selected row values are the same as the ones the entity object in the entity cache believes are the current database values.

To assist subclasses in performing this old-value versus new-value attribute comparison, you can add one final helper method to the PLSQLEntityImpl class like this:

// In PLSQLEntityImpl
protected void compareOldAttrTo(int attrIndex, Object newVal) {
  if ((getPostedAttribute(attrIndex) == null && newVal != null) || 
      (getPostedAttribute(attrIndex) != null && newVal == null) || 
      (getPostedAttribute(attrIndex) != null && newVal != null && 
       !getPostedAttribute(attrIndex).equals(newVal))) {
    throw new RowInconsistentException(getKey());
  }
}

38.5.5.2 Implementing Lock and Select for the Product Entity

With the additional infrastructure in place in the base PLSQLEntityImpl class, you can override the callSelectProcedure() and callLockProcedureAndCheckForRowInconsistency() helper methods in the Product entity object's ProductImpl class. Since the select_product and lock_product procedures have OUT arguments, as described in Section 37.4.4, "How to Call Other Types of Stored Procedures," you need to use a JDBC CallableStatement object to perform these invocations.

Example 38-13 shows the code required to invoke the select_product procedure. It's performing the following basic steps:

  1. Creating a CallableStatement for the PLSQL block to invoke.

  2. Registering the OUT parameters and types, by one-based bind variable position.

  3. Setting the IN parameter value.

  4. Executing the statement.

  5. Retrieving the possibly updated column values.

  6. Populating the possibly updated attribute values in the row.

  7. Closing the statement.

Example 38-13 Invoking the Stored Procedure to Select a Row by Primary Key

// In ProductsImpl.java
protected void callSelectProcedure() {
    String stmt = "begin products_api.select_product(?,?,?,?,?,?);end;";
    // 1. Create a CallableStatement for the PLSQL block to invoke
    CallableStatement st = 
        getDBTransaction().createCallableStatement(stmt, 0);
    try {
        // 2. Register the OUT parameters and types
        st.registerOutParameter(2, VARCHAR2);
        st.registerOutParameter(3, NUMBER);
        st.registerOutParameter(4, NUMBER);
        st.registerOutParameter(5, NUMBER);
        st.registerOutParameter(6, VARCHAR2);

        // 3. Set the IN parameter value
        st.setObject(1, getProductId());
        // 4. Execute the statement
        st.executeUpdate();
        // 5. Retrieve the possibly updated column values
        String possiblyUpdatedName = st.getString(2);
        String possiblyUpdatedSupplierId = st.getString(3);
        String possiblyUpdatedListPrice = st.getString(4);
        String possiblyUpdatedMinPrice = st.getString(5);
        String possiblyUpdatedShipCode = st.getString(6);

        // 6. Populate the possibly updated attribute values in the row
        populateAttribute(PRODUCTNAME, possiblyUpdatedName, true, false, 
                                false);
        populateAttribute(SUPPLIERID, possiblyUpdatedSupplierId, true, 
                                false, false);
        populateAttribute(LISTPRICE, possiblyUpdatedListPrice, true, false, 
                                false);
        populateAttribute(MINPRICE, possiblyUpdatedMinPrice, true, false, 
                                false);
        populateAttribute(SHIPPINGCLASSCODE, possiblyUpdatedShipCode, true, 
                                false, false);
    } catch (SQLException e) {
        throw new JboException(e);
    } finally {
        if (st != null) {
            try {
                // 7. Closing the statement
                st.close();
            } catch (SQLException e) {
            }
        }
    }
}

Example 38-14 shows the code to invoke the lock_product procedure. It's doing basically the same steps as those in Example 38-13, with just the following two interesting differences:

  • After retrieving the possibly updated column values from the OUT parameters, it uses the compareOldAttrTo() helper method inherited from the PLSQLEntityImpl to detect whether or not a RowInconsistentException should be thrown as a result of the row lock attempt.

  • In the catch (SQLException e) block, it is testing to see whether the database has thrown the error:

    ORA-00054: resource busy and acquire with NOWAIT specified
    

    and if so, it again throws the ADF Business Components AlreadyLockedException just as the default entity object implementation of the lock() functionality would do in this situation.

Example 38-14 Invoking the Stored Procedure to Lock a Row by Primary Key

// In ProductsImpl.java
protected void callLockProcedureAndCheckForRowInconsistency() {
    String stmt = "begin products_api.lock_product(?,?,?,?,?,?);end;";
    CallableStatement st = 
             getDBTransaction().createCallableStatement(stmt, 0);
        try {
            st.registerOutParameter(2, VARCHAR2);
            st.registerOutParameter(3, NUMBER);
            st.registerOutParameter(4, NUMBER);
            st.registerOutParameter(5, NUMBER);
            st.registerOutParameter(6, VARCHAR2);
            st.setObject(1, getProductId());
            st.executeUpdate();
            String possiblyUpdatedName = st.getString(2);
            String possiblyUpdatedSupplierId = st.getString(3);
            String possiblyUpdatedListPrice = st.getString(4);
            String possiblyUpdatedMinPrice = st.getString(5);
            String possiblyUpdatedShipCode = st.getString(6);
            compareOldAttrTo(PRODUCTNAME, possiblyUpdatedName);
            compareOldAttrTo(SUPPLIERID, possiblyUpdatedSupplierId);
            compareOldAttrTo(LISTPRICE, possiblyUpdatedListPrice);
            compareOldAttrTo(MINPRICE, possiblyUpdatedMinPrice);
            compareOldAttrTo(SHIPPINGCLASSCODE, possiblyUpdatedShipCode);
       } catch (SQLException e) {
            if (Math.abs(e.getErrorCode()) == 54) {
            throw new AlreadyLockedException(e);
        } else {
            throw new JboException(e);
        }
    } finally {
        if (st != null) {
            try {
                st.close();
            } catch (SQLException e) {
            }
        }
    }
}

With these methods in place, you have a Products entity object that wraps the PRODUCTS_API package for all of its database operations. Due to the clean separation of the data querying functionality of view objects and the data validation and saving functionality of entity objects, you can now leverage this Products entity object in any way you would use a normal entity object. You can build as many different view objects that use Products as their entity usage as necessary.

38.5.5.3 Refreshing the Entity Object After RowInconsistentException

You can override the lock() method to refresh the entity object after a RowInconsistentException has occurred. Example 38-15 shows code that can be added to the entity object implementation class to catch the RowInconsistentException and refresh the entity object.

Example 38-15 Overridden lock() Method to Refresh Entity Object on RowInconsistentException

// In the entity object implementation class 
@Override
public void lock() { 
  try { 
    super.lock(); 
  } 
  catch (RowInconsistentException ex) { 
    this.refresh(REFRESH_UNDO_CHANGES); 
    throw ex; 
  } 
}

38.6 Basing an Entity Object on a Join View or Remote DBLink

If you need to create an entity object based on either of the following:

  • Synonym that resolves to a remote table over a DBLINK

  • View with INSTEAD OF triggers

Then you will encounter the following error if any of its attributes are marked as Refresh on Insert or Refresh on Update:

JBO-26041: Failed to post data to database during "Update"
## Detail 0 ##
ORA-22816: unsupported feature with RETURNING clause

These types of schema objects do not support the RETURNING clause, which by default the entity object uses to more efficiently return the refreshed values in the same database roundtrip in which the INSERT or UPDATE operation was executed.

To disable the use of the RETURNING clause for an entity object of this type:

  1. Enable a custom entity definition class for the entity object.

  2. In the custom entity definition class, override the createDef() method to call:

    setUseReturningClause(false)
    
  3. If the Refresh on Insert attribute is the primary key of the entity object, you must specify some other attribute in the entity as an alternate unique key by setting the Unique Key property on it.

At runtime, when you have disabled the use of the RETURNING clause in this way, the entity object implements the Refresh on Insert and Refresh on Update behavior using a separate SELECT statement to retrieve the values to refresh after insert or update as appropriate.

38.7 Using Inheritance in Your Business Domain Layer

Inheritance is a powerful feature of object-oriented development that can simplify development and maintenance when used appropriately. As shown in Section 37.8, "Creating Extended Components Using Inheritance," ADF Business Components supports using inheritance to create new components that extend existing ones in order to add additional properties or behavior or modify the behavior of the parent component. Inheritance can be useful in modeling the different kinds of entities in your reusable business domain layer.

Note:

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

38.7.1 Understanding When Inheritance Can Be Useful

Your application's database schema might contain tables where different logical kinds of business information are stored in rows of the same table. These tables will typically have one column whose value determines the kind of information stored in each row. For example, the Fusion Order Demo's PERSONS table stores information about customers, suppliers, and staff in the same table. It contains a PERSON_TYPE_CODE column whose value — STAFF, CUST, or SUPP — determines what kind of PERSON the row represents.

While the Fusion Order Demo implementation doesn't yet contain this differentiation in this release, it's reasonable to assume that a future release of the application might require:

  • Managing additional database-backed attributes that are specific to suppliers or specific to staff

  • Implementing common behavior for all users that is different for suppliers or staff

  • Implementing new functionality that is specific to only suppliers or only staff

Figure 38-1 shows what the business domain layer would look like if you created distinct Persons, Staff, and Supplier entity objects to allow distinguishing the different kinds of business information in a more formal way inside your application. Since suppliers and staff are special kinds of persons, their corresponding entity objects would extend the base Persons entity object. This base Persons entity object contains all of the attributes and methods that are common to all types of users. The performPersonFunction() method in the figure represents one of these common methods.

Then, for the Supplier and Staff entity objects you can add specific additional attributes and methods that are unique to that kind of user. For example, Supplier has an additional ContractExpires attribute of type Date to track when the supplier's current contract expires. There is also a performSupplierFunction() method that is specific to suppliers. Similarly, the Staff entity object has an additional DiscountEligible attribute to track whether the person qualifies for a staff discount. The performStaffFunction() is a method that is specific to staff.

Figure 38-1 Distinguishing Persons, Suppliers, and Staff Using Inheritance

Image of inheritance among users

By modeling these different kinds of persons as distinct entity objects in an inheritance hierarchy in your domain business layer, you can simplify having them share common data and behavior and implement the aspects of the application that make them distinct.

38.7.2 How to Create Entity Objects in an Inheritance Hierarchy

To create entity objects in an inheritance hierarchy, you use the Create Entity Object wizard to create each entity. The example described here assumes that you've altered the FOD application's PERSONS table by executing the following DDL statement to add two new columns to it:

alter table persons add (
  discount_eligible varchar2(1),
  contract_expires date
);

38.7.2.1 Start by Identifying the Discriminator Column and Distinct Values

Before creating entity objects in an inheritance hierarchy based on a table containing different kinds of information, you should first identify which column in the table is used to distinguish the kind of row it is. In the FOD application's PERSONS table, this is the PERSON_TYPE_CODE column. Since it helps partition or "discriminate" the rows in the table into separate groups, this column is known as the discriminator column.

Next, determine the valid values that the descriminator column takes on in your table. You might know this off the top of your head, or you could execute a simple SQL statement in the JDeveloper SQL Worksheet to determine the answer. To access the worksheet:

  1. Choose Database Navigator from the View menu.

  2. Expand the AdvancedEntityExamples folder and select the FOD connection.

  3. Right-click FOD, and choose Open SQL Worksheet from the context menu.

Figure 38-2 shows the results of performing a SELECT DISTINCT query in the SQL Worksheet on the PERSON_TYPE_CODE column in the PERSONS table. It confirms that the rows are partitioned into three groups based on the PERSON_TYPE_CODE discriminator values: SUPP, STAFF, and CUST.

Figure 38-2 Using the SQL Worksheet to Find Distinct Discriminator Column Values

Image of using SQL Worksheet to find column values

38.7.2.2 Identify the Subset of Attributes Relevant to Each Kind of Entity

Once you know how many different kinds of business entities are stored in the table, you will also know how many entity objects to create to model these distinct items. You'll typically create one entity object per kind of item. Next, in order to help determine which entity should act as the base of the hierarchy, you need to determine which subset of attributes is relevant to each kind of item.

For example, assume you determine that all of the attributes except ContractExpires and DiscountEligible are relevant to all users, that ContractExpires is specific to suppliers, and that DiscountEligible is specific to staff. This information leads you to determine that the Persons entity object should be the base of the hierarchy, with the Supplier and Staff entity objects each extending Persons to add their specific attributes.

38.7.2.3 Creating the Base Entity Object in an Inheritance Hierarchy

To create the base entity object in an inheritance hierarchy, use the Create Entity Object wizard.

To create the base entity object

  1. In the Application Navigator, right-click the project you want to add the entity object to, and choose New.

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

  3. In the Create Entity Object wizard, on the Name Page, provide a name and package for the entity, and select the schema object on which the entity will be based.

    For example, name the entity object Persons and base it on the PERSONS table.

  4. On the Attributes page, select the attributes in the Entity Attributes list that are not relevant to the base entity object (if any) and click Remove to remove them.

    For example, remove the DiscountEligible and ContractExpires attributes from the list.

  5. On the Attribute Settings page, use the Select Attribute dropdown list to choose the attribute that will act as the discriminator for the family of inherited entity objects and check the Discriminator checkbox to identify it as such. Importantly, you must also supply a Default Value for this discriminator attribute to identify rows of this base entity type.

    For example, select the PersonTypeCode attribute, mark it as a discriminator attribute, and set its Default Value to the value "cust".

    Note:

    Leaving the Default Value blank for a discriminator attribute is legal. A blank default value means that a row with the discriminator column value IS NULL will be treated as this base entity type.

  6. Then click Finish to create the entity object.

38.7.2.4 Creating a Subtype Entity Object in an Inheritance Hierarchy

To create a subtype entity object in an inheritance hierarchy, you use the Create Entity Object wizard.

Before you begin:

  1. Determine the entity object that will be the parent entity object from which your new entity object will extend.

    For example, the parent entity for a new Manager entity object will be the User entity.

  2. Ensure that the parent entity has a discriminator attribute already identified.

    If it does not, use the overview editor to set the Discriminator property on the appropriate attribute of the parent entity before creating the inherited child.

To create the new subtype entity object in the hierarchy:

  1. In the Application Navigator, right-click the project you want to add the entity object to, and choose New.

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

  3. In the Create Entity Object wizard, on the Name Page, provide a name and package for the entity, and click the Browse button next to the Extends field to select the parent entity from which the entity being created will extend.

    For example, name the new entity Staff and select the Persons entity object in the Extends field.

  4. On the Attributes page, the Entity Attributes list displays the attributes from the underlying table that are not included in the base entity object. Select the attributes you do not want to include in this entity object and click Remove.

    For example, since you are creating the Staff entity remove the ContractExpires attribute and leave the DiscountEligible attribute.

  5. Click Override to select the discriminator attribute so that you can customize the attribute metadata to supply a distinct Default Value for the Staff subtype.

    For example, override the PersonTypeCode attribute.

  6. On the Attribute Settings page, use the Select Attribute dropdown list to select the discriminator attribute. Change the Default Value field to supply a distinct default value for the discriminator attribute that defines the entity subtype being created.

    For example, select the PersonTypeCode attribute and change its Default Value to the value "staff".

  7. Click Finish to create the subtype entity object.

Note:

You can repeat the same steps to define the Supplier entity object that extends Persons to add the additional ContractExpires attribute and overrides the Default Value of the UserRole discriminator attribute to have the value "supp".

38.7.3 How to Add Methods to Entity Objects in an Inheritance Hierarchy

To add methods to entity objects in an inheritance hierarchy, enable the custom Java class for the entity object and use the source editor to add the method.

38.7.3.1 Adding Methods Common to All Entity Objects in the Hierarchy

To add a method that is common to all entity objects in the hierarchy, enable a custom Java class for the base entity object in the hierarchy and add the method in the source editor. For example, if you add the following method to the PersonsImpl class for the base User entity object, it will be inherited by all entity objects in the hierarchy:

// In PersonsImpl.java
public void performPersonFunction() {
  System.out.println("## performPersonFunction as Customer");
}

38.7.3.2 Overriding Common Methods in a Subtype Entity

To override a method in a subtype entity that is common to all entity objects in the hierarchy, enable a custom Java class for the subtype entity and choose Override Methods from the Source menu to launch the Override Methods dialog. Select the method you want to override, and click OK. Then, customize the overridden method's implementation in the source editor. For example, imagine overriding the performPersonFunction() method in the StaffImpl class for the Staff subtype entity object and change the implementation to look like this:

// In StaffImpl.java
public void performPersonFunction() {
  System.out.println("## performPersonFunction as Staff");
}

When working with instances of entity objects in a subtype hierarchy, sometimes you will process instances of multiple different subtypes. Since Staff and Supplier entities are special kinds of Persons, you can write code that works with all of them using the more generic PersonsImpl type that they all have in common. When doing this generic kind of processing of classes that might be one of a family of subtypes in a hierarchy, Java will always invoke the most specific override of a method available.

This means that invoking the performPersonFunction() method on an instance of PersonsImpl that happens to really be the more specific StaffImpl subtype, will the result in printing out the following:

## performPersonFunction as Staff

instead of the default result that regular PersonsImpl instances would get:

## performPersonFunction as Customer

38.7.3.3 Adding Methods Specific to a Subtype Entity

To add a method that is specific to a subtype entity object in the hierarchy, enable a custom Java class for that entity and add the method in the source editor. For example, you could add the following method to the SupplierImpl class for the Supplier subtype entity object:

// In SupplierImpl.java
public void performSupplierFunction() {
  System.out.println("## performSupplierFunction called");    
}

38.7.4 What You May Need to Know About Using Inheritance

When using inheritance, you can also introduce a new base entity, find subtype entities using a primary key, and create view objects with polymorphic entity usages.

38.7.4.1 Sometimes You Need to Introduce a New Base Entity

In the InheritanceAndPolymorphicQueries example project, the Persons entity object corresponded to a concrete kind of row in the PERSONS table and it also played the role of the base entity in the hierarchy. In other words, all of its attributes were common to all entity objects in the hierarchy. You might wonder what would happen, however, if the Persons entity required a property that was specific to customers, but not common to staff or suppliers. Imagine that customers can participate in customer satisfaction surveys, but that staff and suppliers do not. The Persons entity would require a LastSurveyDate attribute to handle this requirement, but it wouldn't make sense to have Staff and Supplier entity objects inherit it.

In this case, you can introduce a new entity object called BasePersons to act as the base entity in the hierarchy. It would have all of the attributes common to all Persons, Staff, and Supplier entity objects. Then each of the three entities the correspond to concrete rows that appear in the table could have some attributes that are inherited from BasePersons and some that are specific to its subtype. In the BasePersons type, so long as you mark the PersonTypeCode attribute as a discriminator attribute, you can just leave the Default Value blank (or some other value that does not occur in the PERSON_TYPE_CODE column in the table). Because at runtime you'll never be using instances of the BasePersons entity, it doesn't really matter what its discriminator default value is.

38.7.4.2 Finding Subtype Entities by Primary Key

When you use the findByPrimaryKey() method on an entity definition, it only searches the entity cache for the entity object type on which you call it. In the InheritanceAndPolymorphicQueries example project, this means that if you call PersonsImpl.getDefinitionObject() to access the entity definition for the Persons entity object when you call findByPrimaryKey() on it, you will only find entities in the cache that happen to be customers. Sometimes this is exactly the behavior you want. However, if you want to find an entity object by primary key allowing the possibility that it might be a subtype in an inheritance hierarchy, then you can use the EntityDefImpl class' findByPKExtended() method instead. In the Persons example described here, this alternative finder method would find an entity object by primary key whether it is a customer, supplier, or staff. You can then use the Java instanceof operator to test which type you found, and then cast the PersonsImpl object to the more specific entity object type in order to work with features specific to that subtype.

38.7.4.3 You Can Create View Objects with Polymorphic Entity Usages

When you create an entity-based view object with an entity usage corresponding to a base entity object in an inheritance hierarchy, you can configure the view object to query rows corresponding to multiple different subtypes in the base entity's subtype hierarchy. Each row in the view object will use the appropriate subtype entity object as the entity row part, based on matching the value of the discriminator attribute. See Section 39.6.2, "How To Create a View Object with a Polymorphic Entity Usage," for specific instructions on setting up and using these view objects.

38.8 Controlling Entity Posting Order to Avoid Constraint Violations

Due to database constraints, when you perform DML operations to save changes to a number of related entity objects in the same transaction, the order in which the operations are performed can be significant. If you try to insert a new row containing foreign key references before inserting the row being referenced, the database can complain with a constraint violation. You must understand the default order for processing of entity objects during commit time and how to programmatically influence that order when necessary.

Note:

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

38.8.1 Understanding the Default Post Processing Order

By default, when you commit the transaction the entity objects in the pending changes list are processed in chronological order, in other words, the order in which the entities were added to the list. This means that, for example, if you create a new Product and then a new Supplier related to that product, the new Product will be inserted first and the new Supplier second.

38.8.2 How Compositions Change the Default Processing Ordering

When two entity objects are related by a composition, the strict chronological ordering is modified automatically to ensure that composed parent and child entity rows are saved in an order that avoids violating any constraints. This means, for example, that a new parent entity row is inserted before any new composed children entity rows.

38.8.3 Overriding postChanges() to Control Post Order

If your related entities are associated but not composed, then you need to write a bit of code to ensure that the related entities get saved in the appropriate order.

38.8.3.1 Observing the Post Ordering Problem First Hand

Consider the newProductForNewSupplier() custom method from an PostModule application module in Example 38-16. It accepts a set of parameters and:

  1. Creates a new Product.

  2. Creates a new Supplier.

  3. Sets the product ID to which the server request pertains.

  4. Commits the transaction.

  5. Constructs a Result Java bean to hold new product ID and supplier ID.

  6. Returns the result.

Note:

The code makes the assumption that both Products.ProductId and Suppliers.SupplierId have been set to have DBSequence data type to populate their primary keys based on a sequence.

Example 38-16 Creating a New Product, Then a New Supplier, and Returning the New IDs

// In PostModuleImpl.java
public Result newProductForNewSupplier(String supplierName, 
                                           String supplierStatus,
                                           String productName,
                                           String productStatus,
                                           Number listPrice,
                                           Number minPrice,
                                           String shipCode) {
    oracle.jbo.domain.Date today = new Date(Date.getCurrentDate());
    Number objectId = new Number(0);
    // 1. Create a new product
    ProductsBaseImpl newProduct = createNewProduct();
    // 2. Create a new supplier
    SuppliersImpl newSupplier = createNewSupplier();
    newSupplier.setSupplierName(supplierName);
    newSupplier.setSupplierStatus(supplierStatus);
    newSupplier.setCreatedBy("PostingModule");
    newSupplier.setCreationDate(today);
    newSupplier.setLastUpdatedBy("PostingModule");
    newSupplier.setLastUpdateDate(today);
    newSupplier.setObjectVersionId(objectId);
    // 3. Set the supplier id to which the product pertains
    newProduct.setSupplierId(newSupplier.getSupplierId().getSequenceNumber());
    newProduct.setProductName(productName);
    newProduct.setProductStatus(productStatus);
    newProduct.setListPrice(listPrice);
    newProduct.setMinPrice(minPrice);
    newProduct.setShippingClassCode(shipCode);
    newProduct.setCreatedBy("PostingModule");
    newProduct.setCreationDate(today);
    newProduct.setLastUpdatedBy("PostingModule");
    newProduct.setLastUpdateDate(today);
    newProduct.setObjectVersionId(objectId);
    // 4. Commit the transaction
    getDBTransaction().commit();
    // 5. Construct a bean to hold new supplier id and product id
    Result result = new Result();
    result.setProductId(newProduct.getProductId().getSequenceNumber());
    result.setSupplierId(newSupplier.getSupplierId().getSequenceNumber());
    // 6. Return the result
    return result;
}
private ProductsBaseImpl createNewProduct(){
   EntityDefImpl productDef = ProductsBaseImpl.getDefinitionObject();
   return (ProductsBaseImpl) productDef.createInstance2(getDBTransaction(), null);
}private SuppliersImpl createNewSupplier(){
   EntityDefImpl supplierDef = SuppliersImpl.getDefinitionObject();
   return (SuppliersImpl) supplierDef.createInstance2(getDBTransaction(), null);
}

If you add this method to the application module's client interface and test it from a test client program, you get an error:

oracle.jbo.DMLConstraintException:
JBO-26048: Constraint "PRODUCT_SUPPLIER_FK" violated during post operation:
"Insert" using SQL Statement
"BEGIN
  INSERT INTO PRODUCTS(
    SUPPLIER_NAME,SUPPLIER_STATUS,PRODUCT_NAME,
    PRODUCT_STATUS,LIST_PRICE,MIN_PRICE, SHIPPING_CLASS_CODE)
   VALUES (?,?,?,?,?,?,?)
   RETURNING PRODUCT_ID INTO ?; 
END;".
## Detail 0 ##
java.sql.SQLException:
ORA-02291: integrity constraint (FOD.PRODUCT_SUPPILER_FK) violated
           - parent key not found

When the PRODUCTS row is inserted, the database complains that the value of its SUPPLIER_ID foreign key doesn't correspond to any row in the SUPPLIERS table. This occurred because:

  • The code created the Product before the Supplier

  • Products and Suppliers entity objects are associated but not composed

  • The DML operations to save the new entity rows is done in chronological order, so the new Product gets inserted before the new Supplier.

38.8.3.2 Forcing the Supplier to Post Before the Product

To remedy the problem of attempting to add a product with a not-yet-valid supplier ID, you could reorder the lines of code in the example to create the Supplier first, then the Product. While this would address the immediate problem, it still leaves the chance that another application developer could create things in an incorrect order.

The better solution is to make the entity objects themselves handle the posting order so it will work correctly regardless of the order of creation. To do this you need to override the postChanges() method in the entity that contains the foreign key attribute referencing the associated entity object and write code as shown in Example 38-17. In this example, since it is the Product that contains the foreign key to the Supplier entity, you need to update the Product to conditionally force a related, new Supplier to post before the service request posts itself.

The code tests whether the entity being posted is in the STATUS_NEW or STATUS_MODIFIED state. If it is, it retrieves the related product using the getSupplier() association accessor. If the related Supplier also has a post-state of STATUS_NEW, then first it calls postChanges() on the related parent row before calling super.postChanges() to perform its own DML.

Example 38-17 Overriding postChanges() in ProductsBaseImpl to Post Supplier First

// In ProductsBaseImpl.java
public void postChanges(TransactionEvent e) {
  /* If current entity is new or modified */
  if (getPostState() == STATUS_NEW ||
      getPostState() == STATUS_MODIFIED) {
    /* Get the associated supplier for the product */
    SuppliersImpl supplier = getSupplier();
    /* If there is an associated supplier */
    if (supplier != null) {
      /* And if it's post-status is NEW */
      if (supplier.getPostState() == STATUS_NEW) {
        /*
         * Post the supplier first, before posting this
         * entity by calling super below
         */
        supplier.postChanges(e);
      }
    }
  }
  super.postChanges(e);
}

If you were to re-run the example now, you would see that without changing the creation order in the newProductForNewSupplier() method's code, entities now post in the correct order — first new Supplier, then new Product. Yet, there is still a problem. The constraint violation still appears, but now for a different reason!

If the primary key for the Suppliers entity object were user-assigned, then the code in Example 38-17 would be all that is required to address the constraint violation by correcting the post ordering.

Note:

An alternative to the programmatic technique discussed here, which solves the problem at the Java EE application layer, is the use of deferrable constraints at the database layer. If you have control over your database schema, consider defining (or altering) your foreign key constraints to be DEFERRABLE INITIALLY DEFERRED. This causes the database to defer checking the constraint until transaction commit time. This allows the application to perform DML operations in any order, provided that by COMMIT time all appropriate related rows have been saved and would alleviate the parent/child ordering. However, you would still need to write the code to cascade-update the foreign key values if the parent's primary key is assigned from a sequence, as described in Section 38.8.3.3, "Understanding Associations Based on DBSequence-Valued Primary Keys" and Section 38.8.3.4, "Refreshing References to DBSequence-Assigned Foreign Keys."

In this example, however, the Suppliers.SupplierId is assigned from a database sequence, and not user-assigned in this example. So when a new Suppliers entity row gets posted its SupplierId attribute is refreshed to reflect the database-assigned sequence value. The foreign key value in the Products.SupplierId attribute referencing the new supplier is "orphaned" by this refreshing of the supplier's ID value. When the product's row is saved, its SUPPLIER_ID value still doesn't match a row in the SUPPLIERS table, and the constraint violation occurs again. The next two sections discuss the solution to address this "orphaning" problem.

38.8.3.3 Understanding Associations Based on DBSequence-Valued Primary Keys

Recall from Section 4.10.10, "How to Get Trigger-Assigned Primary Key Values from a Database Sequence" that when an entity object's primary key attribute is of DBSequence type, during the transaction in which it is created, its numerical value is a unique, temporary negative number. If you create a number of associated entities in the same transaction, the relationships between them are based on this temporary negative key value. When the entity objects with DBSequence-value primary keys are posted, their primary key is refreshed to reflect the correct database-assigned sequence number, leaving the associated entities that are still holding onto the temporary negative foreign key value "orphaned".

For entity objects based on a composition, when the parent entity object's DBSequence-valued primary key is refreshed, the composed children entity rows automatically have their temporary negative foreign key value updated to reflect the owning parent's refreshed, database-assigned primary key. This means that for composed entities, the "orphaning" problem does not occur.

However, when entity objects are related by an association that is not a composition, you need to write a little code to insure that related entity rows referencing the temporary negative number get updated to have the refreshed, database-assigned primary key value. The next section outlines the code required.

38.8.3.4 Refreshing References to DBSequence-Assigned Foreign Keys

When an entity like Suppliers in this example has a DBSequence-valued primary key, and it is referenced as a foreign key by other entities that are associated with (but not composed by) it, you need to override the postChanges() method as shown in Example 38-18 to save a reference to the row set of entity rows that might be referencing this new Suppliers row. If the status of the current Suppliers row is New, then the code assigns the RowSet-valued return of the getProduct() association accessor to the newProductsBeforePost member field before calling super.postChanges().

Example 38-18 Saving Reference to Entity Rows Referencing This New Supplier

// In SuppliersImpl.java
RowSet newProductsBeforePost = null;
public void postChanges(TransactionEvent TransactionEvent) {
  /* Only bother to update references if Product is a NEW one */
  if (getPostState() == STATUS_NEW) {
    /* 
     * Get a rowset of products related
     * to this new supplier before calling super
     */
    newProductsBeforePost = (RowSet)getProductsBase();
  }
  super.postChanges(TransactionEvent);
}

This saved RowSet object is then used by the overridden refreshFKInNewContainees() method shown in Example 38-19. It gets called to allow a new entity row to cascade update its refreshed primary key value to any other entity rows that were referencing it before the call to postChanges(). It iterates over the ProductsBaseImpl rows in the newProductsBaseBeforePost row set (if non-null) and sets the new supplier ID value of each one to the new sequence-assigned supplier value of the newly posted Suppliers entity.

Example 38-19 Cascade-Updating Entity Rows with New SupplierId Value

// In SuppliersImpl.java
protected void refreshFKInNewContainees() {
  if (newProductsBeforePost != null) {
    Number newSupplierId = getSupplierId().getSequenceNumber();
    /* 
     * Process the rowset of products that referenced
     * the new supplier prior to posting, and update their
     * SupplierId attribute to reflect the refreshed SupplierId value
     * that was assigned by a database sequence during posting.
     */
    while (newProductsBeforePost.hasNext()) {
      ProductsBaseImpl svrReq =
        (ProductsBaseImpl)newProductsBeforePost.next();
      product.setSupplierId(newSupplierId);
    }
    closeNewProductRowSet();
  }  
}

After implementing this change, the code in Example 38-16 runs without encountering any database constraint violations.

38.9 Implementing Custom Validation Rules

ADF Business Components comes with a base set of built-in declarative validation rules that you can use. However, a powerful feature of the validator architecture for entity objects is that you can create your own custom validation rules. When you notice that you or your team are writing the same kind of validation code over and over, you can build a custom validation rule class that captures this common validation "pattern" in a parameterized way.

After you've defined a custom validation rule class, you can register it in JDeveloper so that it is as simple to use as any of the built-in rules. In fact, you can even bundle your custom validation rule with a custom UI panel that JDeveloper leverages to facilitate developers' using and configuring the parameters your validation rule might require.

38.9.1 How to Create a Custom Validation Rule

To write a custom validation rule for entity objects, you need a Java class that implements the JboValidatorInterface in the oracle.jbo.rules package. You can create a skeleton class from the New Gallery.

To create a custom validator:

  1. In the Application Navigator, right-click the project where you want to create the validator, and choose New from the context menu.

  2. In the New Gallery, expand Business Tier, click ADF Business Components, select Validation Rule and click OK.

As shown in Example 38-20, JBOValidatorInterface contains one main validate() method, and a getter and setter method for a Description property.

Example 38-20 All Validation Rules Must Implement the JboValidatorInterface

package oracle.jbo.rules;
public interface JboValidatorInterface {
  void validate(JboValidatorContext valCtx) { }
  java.lang.String getDescription() { }
  void setDescription(String description) { }
}

If the behavior of your validation rule will be parameterized to make it more flexible, then add additional bean properties to your validator class for each parameter. For example, the code in Example 38-21 implements a custom validation rule called DateMustComeAfterRule which validates that one date attribute must come after another date attribute. To allow the developer using the rule to configure the names of the date attributes to use as the initial and later dates for validation, this class defines two properties initialDateAttrName and laterDateAttrName.

Example 38-21 shows the code that implements the custom validation rule. It extends the AbstractValidator to inherit support for working with the entity object's custom message bundle, where JDeveloper saves the validation error message when a developer uses the rule in an entity object.

The validate() method of the validation rule gets invoked at runtime whenever the rule class should perform its functionality. The code performs the following basic steps:

  1. Ensures validator is correctly attached at the entity level.

  2. Gets the entity row being validated.

  3. Gets the values of the initial and later date attributes.

  4. Validate that initial date is before later date.

  5. Throws an exception if the validation fails.

Example 38-21 Custom DateMustComeAfterRule

// NOTE: package and imports omitted
public class DateMustComeAfterRule extends AbstractValidator
       implements JboValidatorInterface {
  /**
   * This method is invoked by the framework when the validator should do its job
   */
  public void validate(JboValidatorContext valCtx) {
    // 1. If validator is correctly attached at the entity level...
    if (validatorAttachedAtEntityLevel(valCtx)) {
      // 2. Get the entity row being validated
      EntityImpl eo = (EntityImpl)valCtx.getSource();
      // 3. Get the values of the initial and later date attributes
      Date initialDate = (Date) eo.getAttribute(getInitialDateAttrName());
      Date laterDate = (Date) eo.getAttribute(getLaterDateAttrName());
      // 4. Validate that initial date is before later date
      if (!validateValue(initialDate,laterDate)) {
        // 5. Throw the validation exception
        RulesBeanUtils.raiseException(getErrorMessageClass(),
                                      getErrorMsgId(),
                                      valCtx.getSource(),
                                      valCtx.getSourceType(),
                                      valCtx.getSourceFullName(),
                                      valCtx.getAttributeDef(),
                                      valCtx.getNewValue(),
                                      null, null);
      }
    }
    else {
      throw new RuntimeException("Rule must be at entity level");
    }
  }
  /**
   * Validate that the initialDate comes before the laterDate.
   */
  private boolean validateValue(Date initialDate, Date laterDate) {
    return (initialDate == null) || (laterDate == null) ||
    (initialDate.compareTo(laterDate) < 0);
  }
  /**
   * Return true if validator is attached to entity object
   * level at runtime.
   */
  private boolean validatorAttachedAtEntityLevel(JboValidatorContext ctx) {
    return ctx.getOldValue() instanceof EntityImpl;
  }
  // NOTE: Getter/Setter Methods omitted
  private String description;
  private String initialDateAttrName;
  private String laterDateAttrName;
}

For easier reuse of your custom validation rules, you would typically package them into a JAR file for reference by applications that make use of the rules.

38.9.2 Adding a Design Time Bean Customizer for Your Rule

Since a validation rule class is a bean, you can implement a standard JavaBean customizer class to improve the design time experience of setting the bean properties. In the example of the DateMustComeAfter rule (in Example 38-21), the two properties that the developers must configure are the initialDateAttrName and laterDateAttrName properties.

Figure 38-3 illustrates using JDeveloper's visual designer for Swing to create a DateMustComeAfterRuleCustomizer using a JPanel with a titled border containing two JLabel prompts and two JComboBox controls for the dropdown lists. The code in the class populates the dropdown lists with the names of the Date-valued attributes of the current entity object being edited in the IDE. This will allow a developer who adds a DateMustComeAfterRule validation to their entity object to easily pick which date attributes should be used for the starting and ending dates for validation.

Figure 38-3 Using JDeveloper's Swing Visual Designer to Create a Validation Rule Customizer

Image of customized validation rule editor

To associate a customizer with your DateMustComeAfterRule Java Bean, you follow the standard practice of creating a BeanInfo class. As shown in Example 38-22, the DateMustComeAfterRuleBeanInfo returns a BeanDescriptor that associates the customizer class with the DateMustComeAfter bean class.

You would typically package your customizer class and this bean info in a separate JAR file for design-time-only use.

Example 38-22 BeanInfo to Associate a Customizer with a Custom Validation Rule

package oracle.fodemo...frameworkExt.rules;
import java.beans.BeanDescriptor;
import java.beans.SimpleBeanInfo;
public class DateMustComeAfterRuleBeanInfo extends SimpleBeanInfo {
  public BeanDescriptor getBeanDescriptor() {
    return new BeanDescriptor(DateMustComeAfterRule.class,
                              DateMustComeAfterRuleCustomizer.class);
  }
}

38.9.3 Registering and Using a Custom Rule in JDeveloper

After you've created a custom validation rule, you can add it to the project or application level in the JDeveloper IDE so that other developers can use the rule declaratively.

To register a custom validation rule in a project containing entity objects:

  1. In the Application Navigator, right-click the desired project, and choose Project Properties from the context menu.

  2. In the Project Properties dialog, expand Business Components, and select Registered Rules.

  3. On the Registered Rules page, click Add.

  4. In the Register Validation Rule dialog, browse to find the validation rule you have created (such as the one created in Section 38.9.1, "How to Create a Custom Validation Rule"), and click OK.

To register a custom validator at the IDE level:

  1. From the Tools menu, choose Preferences.

  2. From the Business Components > Register Rules page, you can add a one or more validation rules.

    When adding a validation rule, provide the fully-qualified name of the validation rule class, and supply a validation rule name that will appear in JDeveloper's list of available validators.

38.10 Creating New History Types

History types are used to track data specific to a point in time. JDeveloper ships with a number of history types, but you can also create your own. For more information on the standard history types and how to use them, see Section 4.10.12, "How to Track Created and Modified Dates Using the History Column."

38.10.1 How to Create New History Types

You are not limited to the history types provided, you can add or remove custom history types using the History Types page in the Preferences dialog, and then write custom Java code to implement the desired behavior. The code to handle custom history types should be written in your application-wide entity base class for reuse.

Figure 38-5 shows a custom type called last update login with type Id of 11. Assume that last_update_login is a foreign key in the FND_LOGINS table.

Figure 38-4 New History Types in the New Entity Attribute Dialog

Image of history column types in New Entity Attribute editor

To create a custom history type:

  1. From the Tools menu, choose Preferences.

  2. In the Preferences dialog, expand Business Components, and click History Types.

  3. On the History Types page, click New.

  4. In the Create History Type dialog, enter a string value for the name (spaces are allowed) and a numerical Id.

    The Type Id must be an integer between 11 and 126. The numerical values 0-10 are reserved for internal use. The display string is displayed in the History Column dropdown list the next time you use the Edit Attribute dialog.

    Figure 38-5 Creating New History Types

    Image of history types in the Preferences editor
  5. Open the EntityImpl.java file and add a definition similar to the one in Example 38-23.

    Example 38-23 History Type Definition

    private static final byte LASTUPDATELOGIN_HISTORY_TYPE = 11;
    
  6. Override the getHistoryContextForAttribute(AttributeDefImpl attr) method in the EntityImpl base class with code similar to Example 38-24.

    Example 38-24 Overriding getHistoryContextForAttribute()

    @Override
    protected Object getHistoryContextForAttribute(AttributeDefImpl attr) {
        if (attr.getHistoryKind() == LASTUPDATELOGIN_HISTORY_TYPE) {
            // Custom History type logic goes here
        }
        else {
            return super.getHistoryContextForAttribute(attr);
        }
    }
    

38.10.2 How to Remove a History Type

Because they are typically used for auditing values over the life of an application, it is rare that you would want to remove a history type. However, in the event that you need to do so, perform the following tasks:

  1. Remove the history type from the JDeveloper history types list in the Preferences dialog.

  2. Remove any custom code you implemented to support the history type in the base EntityImpl.getHistoryContextForAttribute method.

  3. Remove all usages of the history type in the entity attribute metadata. Any attribute that you have defined to use this history type must be edited.

To remove a history type from the JDeveloper history types list:

  1. From the Tools menu, choose Preferences.

  2. In the Preferences dialog, expand Business Components, and click History Types.

  3. On the History Types page, select the history type that you want to remove and click Delete.