8 Implementing Validation and Business Rules Programmatically

This chapter explains the key entity object events and features for implementing the most common kinds of business rules.

This chapter includes the following sections:

8.1 About Programmatic Business Rules

Complementing the built-in declarative validation features, entity objects and view objects have method validators and several events you can handle to programmatically implement encapsulated business logic using Java code. These concepts are illustrated in Figure 8-1.

  • Attribute-level method validators trigger validation code when an attribute value changes.

  • Entity-level method validators trigger validation code when an entity row is validated.

  • You can override the following key methods in a custom Java class for an entity:

    • create(), to assign default values when a row is created

    • initDefaultExpressionAttributes(), to assign defaults either when a row is created or when a new row is refreshed

    • remove(), to conditionally disallow deleting

    • isAttributeUpdateable(), to make attributes conditionally updatable

    • setAttribute(), to trigger attribute-level method validators

    • validateEntity(), to trigger entity-level method validators

    • prepareForDML(), to assign attribute values before an entity row is saved

    • beforeCommit(), to enforce rules that must consider all entity rows of a given type

    • afterCommit(), to send notifications about a change to an entity object's state

Figure 8-1 Key Entity Objects Features and Events for Programmatic Business Logic

Image shows entity object features

Note:

When coding programmatic business rules, it's important to have a firm grasp of the validation cycle. For more information, see Section 7.2, "Understanding the Validation Cycle."

8.1.1 Programmatic Business Rules Use Cases and Examples

While much of your validation can be implemented using basic declarative behavior, you can implement more complex business rules for your business domain layer when needed, using the Method validator to invoke custom validation code.

Some examples of when you might want to use programmatic business rules include:

  • Eagerly defaulting an attribute value from a database sequence

  • Assigning values derived from complex calculations

  • Undoing pending changes to an entity object

  • Accessing and storing information about the current user session

  • Determining conditional updatability for attributes

8.1.2 Additional Functionality for Programmatic Business Rules

You may find it helpful to understand other ADF features before you start using programmatic validation. Following are links to other functionality that may be of interest.

8.2 Using Method Validators

Method validators are the primary way of supplementing declarative validation rules and Groovy-scripted expressions using your own Java code. Method validators trigger Java code that you write in your own validation methods at the appropriate time during the entity object validation cycle. There are many types of validation you can code with a method validator, either on an attribute or on an entity as a whole.

You can add any number of attribute-level or entity-level method validators, provided they each trigger a distinct method name in your code. All validation method names must begin with the word validate; however, following that rule you are free to name them in any way that most clearly identifies the functionality. For an attribute-level validator, the method must take a single argument of the same type as the entity attribute. For an entity-level validator, the method takes no arguments. The method must also be public, and must return a boolean value. Validation will fail if the method returns false.

Note:

Although it is important to be aware of these rules, when you use JDeveloper to create method validators, JDeveloper creates the correct interface for the class.

At runtime, the Method validator passes an entity attribute to a method implemented in your entity object class.

In Example 8-1, the method accepts strings that start with a capital letter and throws an exception on null values, empty strings, and strings that do not start with a capital letter.

Example 8-1 Method That Validates If the First Letter Is a Capital

public boolean validateIsCapped(String text)
{
  if (text != null && 
      text.length() != 0 && 
      text[0] >= 'A' && 
      text[0] <= 'Z')
  {
    return true;
  }
}

8.2.1 How to Create an Attribute-Level Method Validator

The use of method validators is a programmatic approach that supplements your declarative validation rules.

Before you begin:

It may be helpful to have an understanding of what method validators are. For more information, see Section 8.2, "Using Method Validators."

You may also find it helpful to understand additional functionality that can be added using other validation features. For more information, see Section 8.1.2, "Additional Functionality for Programmatic Business Rules."

To create an attribute-level Method validator:

  1. In the Application Navigator, double-click the desired entity object.

  2. In the overview editor, click the Java navigation tab.

    The Java page shows the Java generation options that are currently enabled for the entity object. If your entity object does not yet have a custom entity object class, then you must generate one before you can add a Method validator. To generate the custom Java class, click the Edit icon, then select Generate Entity Object Class, and click OK to generate the *.java file.

  3. Click the Business Rules navigation tab, and then expand the Attributes node, and select the attribute that you want to validate.

  4. Click the New icon to add a validation rule.

  5. Select Method from the Rule Type dropdown list.

    The Add Validation Rule dialog displays the expected method signature for an attribute-level validation method. You have two choices:

    • If you already have a method in your entity object's custom Java class of the appropriate signature, it will appear in the list and you can select it after deselecting the Create and Select Method checkbox.

    • If you leave the Create and Select Method checkbox selected (see Figure 8-2), you can enter any method name in the Method Name box that begins with the word validate. When you click OK, JDeveloper adds the method to your entity object's custom Java class with the appropriate signature.

  6. Finally, supply the text of the error message for the default locale that the end user should see if this validation rule fails.

Figure 8-2 Adding an Attribute-Level Method Validator

Image of Add Validation Rule dialog

8.2.2 What Happens When You Create an Attribute-Level Method Validator

When you add a new method validator, JDeveloper updates the XML component definition to reflect the new validation rule. If you asked to have the method created, the method is added to the entity object's custom Java class. Example 8-2 illustrates a simple attribute-level validation rule that ensures that the OrderShippedDate of an order is a date in the current month. Notice that the method accepts an argument of the same type as the corresponding attribute, and that its conditional logic is based on the value of this incoming parameter. When the attribute validator fires, the attribute value has not yet been set to the new value in question, so calling the getOrderShippedDate() method inside the attribute validator for the OrderShippedDate attribute would return the attribute's current value, rather than the candidate value that the client is attempting to set.

Example 8-2 Simple Attribute-Level Method Validator

public boolean validateOrderShippedDate(Date  data) {
  if (data != null && data.compareTo(getFirstDayOfCurrentMonth()) <= 0) {
    return false;
  }
  return true;
}

Note:

The return value of the compareTo() method is zero (0) if the two dates are equal, negative one (-1) if the first date is less than the second, or positive one (1) if the first date is greater than the second.

8.2.3 How to Create an Entity-Level Method Validator

Entity-level method validators are similar to attribute-level method validators, except that they have a broader scope: the entire entity rather than a single attribute.

Before you begin:

It may be helpful to have an understanding of what method validators are. For more information, see Section 8.2, "Using Method Validators."

You may also find it helpful to understand additional functionality that can be added using other validation features. For more information, see Section 8.1.2, "Additional Functionality for Programmatic Business Rules."

To create an entity-level method validator:

  1. In the Application Navigator, double-click the desired entity object.

  2. In the overview editor, click the Java navigation tab.

    The Java page shows the Java generation options that are currently enabled for the entity object. If your entity object does not yet have a custom entity object class, then you must generate one before you can add a Method validator. To generate the custom Java class, click the Edit icon, then select Generate Entity Object Class, and click OK to generate the *.java file.

  3. Click the Business Rules navigation tab, and then select the Entity node.

  4. Click the New icon to add a validation rule.

  5. Select Method from the Rule Type dropdown list.

    The Add Validation Rule dialog displays the expected method signature for an entity-level validation method. You have two choices:

    • If you already have a method in your entity object's custom Java class of the appropriate signature, it will appear in the list and you can select it after deselecting the Create and Select Method checkbox.

    • If you leave the Create and Select Method checkbox selected (see Figure 8-3), you can enter any method name in the Method Name box that begins with the word validate. When you click OK, JDeveloper adds the method to your entity object's custom Java class with the appropriate signature.

  6. Finally, supply the text of the error message for the default locale that the end user should see if this validation rule fails.

Figure 8-3 Adding an Entity-Level Method Validator

Image of Add Validation rule dialog

8.2.4 What Happens When You Create an Entity-Level Method Validator

When you add a new method validator, JDeveloper updates the XML component definition to reflect the new validation rule. If you asked to have the method created, the method is added to the entity object's custom Java class. Example 8-3 illustrates a simple entity-level validation rule that ensures that the OrderShippedDate of an order comes after the OrderDate.

Example 8-3 Simple Entity-Level Method Validator

public boolean validateOrderShippedDateAfterOrderDate() {
  Date orderShippedDate = getOrderShippedDate();
  Date orderDate  = getOrderDate();
  if (orderShippedDate != null && orderShippedDate.compareTo(orderDate) < 0) {
    return false;
  }
  return true;
}

8.2.5 What You May Need to Know About Translating Validation Rule Error Messages

Like the locale-specific UI control hints for entity object attributes, the validation rule error messages are added to the entity object's component message bundle file. These entries in the message bundle represent the strings for the default locale for your application. To provide translated versions of the validation error messages, follow the same steps as for translating the UI control hints, as described in Section 4.7, "Working with Resource Bundles."

8.3 Assigning Programmatically Derived Attribute Values

When declarative defaulting falls short of your needs, you can perform programmatic defaulting in your entity object:

  • When an entity row is first created

  • When the entity row is first created or when refreshed to null values

  • When the entity row is saved to the database

  • When an entity attribute value is set

8.3.1 How to Provide Default Values for New Rows at Create Time

The create() method provides the entity object event you can handle to initialize default values the first time an entity row is created. Example 8-4 shows the overridden create method of the OrderEO entity object in the StoreFront module of the Fusion Order Demo. It calls an attribute setter methods to populate the OrderDate attribute in a new order entity row.

You can also define default values using a Groovy expression. For more information, see Section 4.10.6, "How to Define a Static Default Value."

Example 8-4 Programmatically Defaulting Attribute Values for New Rows

// In OrderEOImpl.java in Fusion Order Demo 
protected void create(AttributeList nameValuePair) {
   super.create(nameValuePair);
   this.setOrderDate(new Date());
}

Note:

Calling the setAttribute() method inside the overridden create() method does not mark the new row as being changed by the user. These programmatically assigned defaults behave like declaratively assigned defaults.

8.3.1.1 Choosing Between create() and initDefaultExpressionAttributes() Methods

You should override the initDefaultExpressionAttributes() method for programmatic defaulting logic that you want to fire both when the row is first created, and when it might be refreshed back to initialized status.

If an entity row has New status and you call the refresh() method on it, then the entity row is returned to an Initialized status if you do not supply either the REFRESH_REMOVE_NEW_ROWS or REFRESH_FORGET_NEW_ROWS flag. As part of this process, the entity object's initDefaultExpressionAttributes() method is invoked, but not its create() method again.

8.3.1.2 Eagerly Defaulting an Attribute Value from a Database Sequence

Section 4.10.9, "How to Synchronize with Trigger-Assigned Values," explains how to use the DBSequence type for primary key attributes whose values need to be populated by a database sequence at commit time. Sometimes you may want to eagerly allocate a sequence number at entity row creation time so that the user can see its value and so that this value does not change when the data is saved. To accomplish this, use the SequenceImpl helper class in the oracle.jbo.server package in an overridden create() method as shown in Example 8-5. It shows code from the custom Java class of the WarehouseEO entity object in the StoreFront module of the Fusion Order Demo. After calling super.create(), it creates a new instance of the SequenceImpl object, passing the sequence name and the current transaction object. Then it calls the setWarehouseId() attribute setter method with the return value from SequenceImpl's getSequenceNumber() method.

Note:

For a metadata-driven alternative to this approach, see Section 4.14.5, "Assigning the Primary Key Value Using an Oracle Sequence."

Example 8-5 Eagerly Defaulting an Attribute's Value from a Sequence at Create Time

// In WarehouseEOImpl.java
import oracle.jbo.server.SequenceImpl;
// Default WarehouseId value from WAREHOUSE_SEQ sequence at entity row create time
protected void create(AttributeList attributeList) {
    super.create(attributeList);
    SequenceImpl sequence = new SequenceImpl("WAREHOUSE_SEQ",getDBTransaction());
    setWarehouseId(sequence.getSequenceNumber());
}

8.3.2 How to Assign Derived Values Before Saving

If you want to assign programmatic defaults for entity object attribute values before a row is saved, override the prepareForDML() method and call the appropriate attribute setter methods to populate the derived attribute values. To perform the assignment only during INSERT, UPDATE, or DELETE, you can compare the value of the operation parameter passed to this method against the integer constants DML_INSERT, DML_UPDATE, DML_DELETE respectively.

Example 8-6 shows an overridden prepareForDML() method that assigns derived values.

Example 8-6 Assigning Derived Values Before Saving Using PrepareForDML

protected void prepareForDML(int operation, TransactionEvent e) {
  super.prepareForDML(operation, e);
  //Populate GL Date
  if (operation == DML_INSERT) {
    if (this.getGlDate() == null) {
      String glDateDefaultOption = 
        (String)this.getInvoiceOption().getAttribute("DefaultGlDateBasis");
      if ("I".equals(glDateDefaultOption)) {
        setAttribute(GLDATE, this.getInvoiceDate());
      } else {
        setAttribute(GLDATE, this.getCurrentDBDate());
      }
    }
  }
 
  //Populate Exchange Rate and Base Amount if null
  if ((operation == DML_INSERT) || (operation == DML_UPDATE)) {
    BigDecimal defaultExchangeRate = new BigDecimal(1.5);
    if ("Y".equals(this.getInvoiceOption().getAttribute("UseForeignCurTrx"))) {
      if (!(this.getInvoiceCurrencyCode().equals(
                            this.getLedger().getAttribute("CurrencyCode")))) {
        if (this.getExchangeDate() == null) {
          setAttribute(EXCHANGEDATE, this.getInvoiceDate());
        }
        if (this.getExchangeRateType() == null) {
          String defaultConvRateType = 
            (String)this.getInvoiceOption().getAttribute("DefaultConvRateType");
          if (defaultConvRateType != null) {
            setAttribute(EXCHANGERATETYPE, defaultConvRateType);
          } else {
            setAttribute(EXCHANGERATETYPE, "User");
          }
        }
        if (this.getExchangeRate() == null) {
          setAttribute(EXCHANGERATE, defaultExchangeRate);
        }
        if ((this.getExchangeRate() != null) && 
            (this.getInvoiceAmount() != null)) {
          setAttribute(INVAMOUNTFUNCCURR, 
                     (this.getExchangeRate().multiply(this.getInvoiceAmount())));
        }
      } else {
        setAttribute(EXCHANGEDATE, null);
        setAttribute(EXCHANGERATETYPE, null);
        setAttribute(EXCHANGERATE, null);
        setAttribute(INVAMOUNTFUNCCURR, null);
      }
    }
  }
}

8.3.3 How to Assign Derived Values When an Attribute Value Is Set

To assign derived attribute values whenever another attribute's value is set, add code to the latter attribute's setter method. Example 8-7 shows the setter method for an AssignedTo attribute in an entity object.

Example 8-7 Setting the Assigned Date Whenever the AssignedTo Attribute Changes

public void setAssignedTo(Number value) {
  setAttributeInternal(ASSIGNEDTO, value);
  setAssignedDate(getCurrentDateWithTime());
}

After the call to setAttributeInternal() to set the value of the AssignedTo attribute, it uses the setter method for the AssignedDate attribute to set its value to the current date and time.

Note:

It is safe to add custom code to the generated attribute getter and setter methods as shown here. When JDeveloper modifies code in your class, it intelligently leaves your custom code in place.

8.4 Undoing Pending Changes to an Entity Using the Refresh Method

You can use the refresh(int flag) method on a row to refresh any pending changes it might have. The behavior of the refresh() method depends on the flag that you pass as a parameter. The three key flag values that control its behavior are the following constants in the Row interface:

  • REFRESH_WITH_DB_FORGET_CHANGES forgets modifications made to the row in the current transaction, and the row's data is refreshed from the database. The latest data from the database replaces data in the row regardless of whether the row was modified or not.

  • REFRESH_WITH_DB_ONLY_IF_UNCHANGED works just like REFRESH_WITH_DB_FORGET_CHANGES, but for unmodified rows. If a row was already modified by this transaction, the row is not refreshed.

  • REFRESH_UNDO_CHANGES works the same as REFRESH_WITH_DB_FORGET_CHANGES for unmodified rows. For a modified row, this mode refreshes the row with attribute values at the beginning of this transaction. The row remains in a modified state if it had been previously posted but not committed in the current transaction prior to performing the refresh operation.

8.4.1 How to Control What Happens to New Rows During a Refresh

By default, any entity rows with New status that you refresh() are reverted back to blank rows in the Initialized state. Declarative defaults are reset, as well as programmatic defaults coded in the initDefaultExpressionAttributes() method, but the entity object's create() method is not invoked during this blanking-out process.

You can change this default behavior by combining one of the flags in Section 8.4 with one of the following two flags (using the bitwise-OR operator):

  • REFRESH_REMOVE_NEW_ROWS, new rows are removed during refresh.

  • REFRESH_FORGET_NEW_ROWS, new rows are marked Dead.

8.4.2 How to Cascade Refresh to Composed Children Entity Rows

You can cause a refresh() operation to cascade to composed child entity rows by combining the REFRESH_CONTAINEES flag (using the bitwise-OR operator) with any of the valid flag combinations described in Section 8.4 and Section 8.4.1. This causes the entity to invoke refresh() using the same mode on any composed child entities it contains.

8.5 Using View Objects for Validation

When your business logic requires performing SQL queries, the natural choice is to use a view object to perform that task. Keep in mind that the SQL statements you execute for validation will "see" pending changes in the entity cache only if they are entity-based view objects. Read-only view objects will only retrieve data that has been posted to the database.

8.5.1 How to Use View Accessors for Validation Against View Objects

Since entity objects are designed to be reused in any application scenario, they should not depend directly on a view object instance in any specific application module's data model. Doing so would prevent them from being reused in other application modules, which is highly undesirable.

Instead, you should use a view accessor to validate against a view object. For more information, see Section 10.4.1, "How to Create a View Accessor for an Entity Object or View Object."

Using a view accessor, your validation code can access the view object and set bind variables, as shown in Example 8-8.

Example 8-8 Using a Validation View Object in a Method Validator

// Sample entity-level validation method
public boolean validateSomethingUsingViewAccessor() { 
  RowSet rs = getMyValidationVO();
  rs.setNamedBindParameter("Name1", value1); 
  rs.setNamedBindParameter("Name2", value2); 
  rs.executeQuery(); 
  if ( /* some condition */) { 
  /* 
   * code here returns true if the validation succeeds 
   */ 
  } 
  return false; 
} 

Best Practice:

Any time you access a row set programmatically, you should consider creating a secondary iterator for the row set. This ensures that you will not disturb the current row set of the default row set iterator that may be utilized when your expose your view objects as data controls to the user interface project. You can call createRowSetIterator() on the row set you are working with to create a secondary named row set iterator. When you are through with programmatic iteration, your code should call closeRowSetIterator() on the row set to remove the secondary iterator from memory.

As the sample code suggests, view objects used for validation typically have one or more named bind variables in them. In this example, the bind variables are set using the setNamedBindParameter() method. However, you can also set these variables declaratively in JDeveloper using Groovy expressions in the view accessor definition page.

Depending on the kind of data your view object retrieves, the "/* some condition */" expression in the example will look different. For example, if your view object's SQL query is selecting a COUNT() or some other aggregate, the condition will typically use the rs.first() method to access the first row, then use the getAttribute() method to access the attribute value to see what the database returned for the count.

If the validation succeeds or fails based on whether the query has returned zero or one row, the condition might simply test whether rs.first() returns null or not. If rs.first() returns null, there is no "first" row. In other words, the query retrieved no rows. In other cases, you may be iterating over one or more query results retrieved by the view object to determine whether the validation succeeds or fails.

8.5.2 How to Validate Conditions Related to All Entities of a Given Type

The beforeCommit() method is invoked on each entity row in the pending changes list after the changes have been posted to the database, but before they are committed. This can be a useful method in which to execute view object-based validations that must assert some rule over all entity rows of a given type.

Note:

You can also do this declaratively using a transaction-level validator (see Section 7.6.4, "How to Set Transaction-Level Validation").

If your beforeCommit() logic can throw a ValidationException, you must set the jbo.txn.handleafterpostexc property to true in your configuration to have the framework automatically handle rolling back the in-memory state of the other entity objects that may have already successfully posted to the database (but not yet been committed) during the current commit cycle.

Note:

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

For example, consider the overridden beforeCommit() shown in Example 8-9. In this example, there are three view objects based on polymorphic entity objects (Persons, Staff, and Supplier), with the PersonTypeCode attribute as the discriminator. The PersonsImpl.java file has an overridden beforeCommit() method that calls a validation method. The validation method uses the fourth view object, PersonsValidator, to make sure that the principal name is unique across each person type. For example, there is a PrincipalName of SKING for the Staff view object, but there cannot be another SKING in this or the other person types.

Example 8-9 Overriding beforeCommit() to Validate All Entities of a Given Type

// from the PersonsImpl.java file 
. . . 
@Override
public void beforeCommit(TransactionEvent transactionEvent) throws ValidationException {
  String principalName = getPrincipalName();
  if (!validatePrincipalNameIsUniqueUsingViewAccessor(principalName)) {
    throw new ValidationException("Principal Name must be unique across person types");
  }
    super.beforeCommit(transactionEvent);
}

public boolean validatePrincipalNameIsUniqueUsingViewAccessor(String principalName) {
RowSet rs = getPersonsValidatorVO();
rs.setNamedWhereClauseParam("principalName", principalName);
rs.setRangeSize(-1);
rs.executeQuery();
Row[] validatorRows = rs.getAllRowsInRange();
if (validatorRows.length > 1)
  // more than one row has the same princpalName
{
    return false;
}
rs.closeRowSetIterator();
return true;
}

8.5.3 What You May Need to Know About Row Set Access with View Accessors

If your entity object or view object business logic iterates over its own view accessor row set, and that view accessor is not also used by a model-defined List of Values, then there is no need to use a secondary row set iterator. For example, if an entity object has a view accessor named AirportValidationVA for a view object that takes one named bind parameter, it can iterate its own view accessor row set using either Groovy script or Java. Example 8-10 show a Groovy script that iterates over a view accessor row set.

Example 8-10 Using a View Accessor in Groovy Script

AirportValidationVA.setNamedWhereClauseParam("VarTla",newValue)
AirportValidationVA.executeQuery();
return AirportValidationVA.first() != null;

Example 8-11 shows a Java method validator that iterates over a view accessor row set.

Example 8-11 Using a View Accessor in a Method Validator

public boolean validateJob(String job) {
   getAirportValidationVA().setNamedWhereClauseParam("VarTla",job);
   getAirportValidationVA().executeQuery();
   return getAirportValidationVA().first() != null;
}

8.6 Accessing Related Entity Rows Using Association Accessors

To access information from related entity objects, you use an association accessor method in your entity object's custom Java class. By calling the accessor method, you can easily access any related entity row — or set of entity rows — depending on the cardinality of the association.

8.6.1 How to Access Related Entity Rows

You can use an association accessor to access related entity rows. Example 8-12 shows code from the ControllingPostingOrder project in the AdvancedEntityExamples module of the Fusion Order Demo that shows the overridden postChanges() method in the ProductsBase entity object's custom Java class. It uses the getSupplier() association accessor to retrieve the related supplier for the product.

Example 8-12 Accessing a Parent Entity Row In a Create Method

// In ProducstBaseImpl.java in the ControllingPostingOrder project 
// of the Fusion Order Demo Advanced Entity Examples
@Override
public void postChanges(TransactionEvent transactionEvent) {
  /* 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 product */
    if (supplier != null) {
      /* And if its post-status is NEW */
      if (supplier.getPostState() == STATUS_NEW) {
        /* Post the supplier first, before posting this entity */
        supplier.postChanges(transactionEvent);
      }
    }
  }
  super.postChanges(transactionEvent);
}

8.6.2 How to Access Related Entity Row Sets

If the cardinality of the association is such that multiple rows are returned, you can use the association accessor to return sets of entity rows.

Example 8-13 illustrates the code for the overridden postChanges() method in the Suppliers entity object's custom Java class. It shows the use of the getProductsBase() association accessor to retrieve the RowSet object of ProductsBase rows in order to update the SupplierId attribute in each row using the setSupplierId() association accessor.

Example 8-13 Accessing a Related Entity Row Set Using an Association Accessor

// In SuppliersImpl.java in the ControllingPostingOrder project 
// of the Fusion Order Demo Advanced Entity Examples
RowSet newProductsBeforePost = null;
@Override
public void postChanges(TransactionEvent transactionEvent) {
  /* Only update references if Supplier is new */
  if (getPostState() == STATUS_NEW) {
   /*
    * Get a rowset of products related to this new supplier before calling super
    */
    newProductsBeforePost = (RowSet)getProductsBase();
  }
  super.postChanges(transactionEvent);
}

...

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

8.7 Referencing Information About the Authenticated User

If you have run the Configure ADF Security wizard on your application to enable the ADF authentication servlet to support user login and logout, the oracle.jbo.server.SessionImpl object provides methods you can use to get information about the name of the authenticated user and about the roles of which they are a member. This is the implementation class for the oracle.jbo.Session interface that clients can access.

For information about how to access information about the authenticated user, see Section 35.11.3.3, "How to Determine the Current User Name, Enterprise Name, or Enterprise ID" and Section 35.11.3.4, "How to Determine Membership of a Java EE Security Role".

For more information about security features in Oracle Fusion Web Applications, read Chapter 35, "Enabling ADF Security in a Fusion Web Application."

8.8 Accessing Original Attribute Values

If an entity attribute's value has been changed in the current transaction, when you call the attribute getter method for it you will get the pending changed value. Sometimes you want to get the original value before it was changed. Using the getPostedAttribute() method, your entity object business logic can consult the original value for any attribute as it was read from the database before the entity row was modified. This method takes the attribute index as an argument, so pass the appropriate generated attribute index enums that JDeveloper maintains for you.

8.9 Storing Information About the Current User Session

If you need to store information related to the current user session in a way that entity object business logic can reference, you can use the user data hash table provided by the Session object.

8.9.1 How to Store Information About the Current User Session

When a new user accesses an application module for the first time, the prepareSession() method is called. As shown in Example 8-14, the application module overrides prepareSession() to retrieve information about the authenticated user by calling a retrieveUserInfoForAuthenticatedUser() method on the view object instance. Then, it calls the setUserIdIntoUserDataHashtable() helper method to save the user's numerical ID into the user data hash table.

Example 8-14 Overriding prepareSession() to Query User Information

// In the application module
protected void prepareSession(Session session) {
  super.prepareSession(session);
  /*
   * Query the correct row in the VO based on the currently logged-in
   * user, using a custom method on the view object component
   */
  getLoggedInUser().retrieveUserInfoForAuthenticatedUser();     
  setUserIdIntoUserDataHashtable();
}

Example 8-15 shows the code for the view object's retrieveUserInfoForAuthenticatedUser() method. It sets its own EmailAddress bind variable to the name of the authenticated user from the session and then calls executeQuery() to retrieve the additional user information from the USERS table.

Example 8-15 Accessing Authenticated User Name to Retrieve Additional User Details

// In the view object's custom Java class
public void retrieveUserInfoForAuthenticatedUser() {
  SessionImpl session = (SessionImpl)getDBTransaction().getSession();
  setEmailAddress(session.getUserPrincipalName());
  executeQuery();
  first();
}

One of the pieces of information about the authenticated user that the view object retrieves is the user's numerical ID number, which that method returns as its result. For example, the user sking has the numeric UserId of 300.

Example 8-16 shows the setUserIdIntoUserDataHashtable() helper method — used by the prepareSession() code in Example 8-14 — that stores this numerical user ID in the user data hash table, using the key provided by the string constant CURRENT_USER_ID.

Example 8-16 Setting Information into the UserData Hashtable for Access By Entity Objects

// In the application module
private void setUserIdIntoUserDataHashtable() {
  Integer userid = getUserIdForLoggedInUser();
  Hashtable userdata = getDBTransaction().getSession().getUserData();
  userdata.put(CURRENT_USER_ID, userid);
}  

The corresponding entity objects in this example can have an overridden create() method that references this numerical user ID using a helper method like the one in Example 8-17 to set the CreatedBy attribute programmatically to the value of the currently authenticated user's numerical user ID.

Example 8-17 Referencing the Current User ID in a Helper Method

protected Number getCurrentUserId() {
  Hashtable userdata = getDBTransaction().getSession().getUserData();
  Integer userId = (Integer)userdata.get(CURRENT_USER_ID);
  return userdata != null ? Utils.intToNumber(userId):null;
}

8.9.2 How to Use Groovy to Access Information About the Current User Session

The top-level adf object allows you access to objects that the framework makes available to Groovy script. The adf.userSession object returns a reference to the ADF Business Components user session, which you can use to reference values in the userData hash map that is part of the session.

Example 8-18 shows the Groovy script you would use to reference a userData hash map key named MyKey.

Example 8-18 Accessing the Current User Session Using Groovy Script

adf.userSession.userData.MyKey

8.10 Accessing the Current Date and Time

You might find it useful to reference the current date and time in your entity object business logic. You can reference the current date or current date and time using the following Groovy script expressions:

  • adf.currentDate — returns the current date (time truncated)

  • adf.currentDateTime — returns the current date and time

For more information about using Groovy script in your entity object business logic, see Section 3.6, "Overview of Groovy Scripting Language Support."

8.11 Sending Notifications Upon a Successful Commit

The afterCommit() method is invoked on each entity row that was in the pending changes list and got successfully saved to the database. You can use this method to send a notification on a commit.

A better way to send notifications upon a successful commit is by declaring a business event. For more information on how to create a business event, see Section 4.12, "Creating Business Events."

8.12 Conditionally Preventing an Entity Row from Being Removed

Before an entity row is removed, the remove() method is invoked on an entity row. You can throw a JboException in the remove() method to prevent a row from being removed if the appropriate conditions are not met.

For example, you can add a test in the remove() method that determines the state of the entity object and allows the removal only if it is a new record. Example 8-19 demonstrates this technique.

Note:

This example is in the AddressesImpl.java file in the ConditionalDelete project of the DevGuideExamples workspace in the StandaloneExamples module of the Fusion Order Demo application.

Example 8-19 Overriding the remove() Method to Verify Entity Status Before Removal

// In the Addresses entity object custom Java class
private boolean isDeleteAllowed() {
    byte s = this.getEntityState();
  return s==STATUS_NEW;
}
 
/**
 * Add entity remove logic in this method.
 */
public void remove() {
  if (isDeleteAllowed())
    super.remove();
  else
      throw new JboException("Delete not allowed in this view");
}

Note:

The entity object offers declarative prevention of deleting a master entity row that has existing, composed children rows. You configure this option on the Relationship page of the overview editor for the association.

8.13 Determining Conditional Updatability for Attributes

You can override the isAttributeUpdateable() method in your entity object class to programmatically determine whether a given attribute is updatable or not at runtime based on appropriate conditions.

Example 8-20 shows how an entity object can override the isAttributeUpdateable() method to enforce that its PersonTypeCode attribute is updatable only if the current authenticated user is a staff member. Notice that when the entity object fires this method, it passes in the integer attribute index whose updatability is being considered.

You can implement conditional updatability logic for a particular attribute inside an if or switch statement based on the attribute index. Here PERSONTYPECODE is referencing the integer attribute index enums that JDeveloper maintains in your entity object custom Java class.

Example 8-20 Conditionally Determining an Attribute's Updatability at Runtime

// In the entity object custom Java class
public boolean isAttributeUpdateable(int index) {
  if (index == PERSONTYPECODE) {
    if (!currentUserIsStaffMember()) {
      return super.isAttributeUpdateable(index);
    }
    return CUSTOMER_TYPE.equals(getPersonTypeCode()) ? false : true;
  }
  return super.isAttributeUpdateable(index);
}

Note:

Entity-based view objects inherit this conditional updatability as they do everything else encapsulated in your entity objects. Should you need to implement this type of conditional updatability logic in a way that is specific to a transient view object attribute, or to enforce some condition that involves data from multiple entity objects participating in the view object, you can override this same method in a view object's view row class to achieve the desired result.

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

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

Before you begin:

It may be helpful to have an understanding of custom validation rules. For more information, see Section 8.14, "Implementing Custom Validation Rules."

You may also find it helpful to understand additional functionality that can be added using other validation features. For more information, see Section 8.1.2, "Additional Functionality for Programmatic Business Rules."

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 8-21, JBOValidatorInterface contains one main validate() method, and a getter and setter method for a Description property.

Example 8-21 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 8-22 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 8-22 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 8-22 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.

8.14.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 8-22), the two properties that the developers must configure are the initialDateAttrName and laterDateAttrName properties.

Figure 8-4 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 8-4 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 8-23, 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 8-23 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);
  }
}

8.14.3 How to Register 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.

8.14.3.1 Registering a Custom Validator at the Project Level

When you register a custom validation rule at the project level, you can use it within the project.

Before you begin:

It may be helpful to have an understanding of custom validation rules. For more information, see Section 8.14, "Implementing Custom Validation Rules."

You may also find it helpful to understand additional functionality that can be added using other validation features. For more information, see Section 8.1.2, "Additional Functionality for Programmatic Business Rules."

To register a custom validation rule, you must have already created it, as described in Section 8.14.1, "How to Create a Custom Validation Rule."

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 8.14.1, "How to Create a Custom Validation Rule"), and click OK.

8.14.3.2 Registering a Custom Validator at the IDE Level

When you register a custom validation rule at the IDE level for JDeveloper, you can use it in other projects as well as your current project.

Before you begin:

It may be helpful to have an understanding of custom validation rules. For more information, see Section 8.14, "Implementing Custom Validation Rules."

You may also find it helpful to understand additional functionality that can be added using other validation features. For more information, see Section 8.1.2, "Additional Functionality for Programmatic Business Rules."

To register a custom validation rule, you must have already created it, as described in Section 8.14.1, "How to Create a Custom Validation Rule."

To register a custom validator at the IDE level:

  1. From the Tools menu, choose Preferences.

  2. In the Preferences dialog, expand Business Components, and select Register Rules.

  3. From the 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.