Fusion Middleware Documentation
Advanced Search


Developing Fusion Web Applications with Oracle Application Development Framework
Close Window

Table of Contents

Show All | Collapse

12 Implementing Validation and Business Rules Programmatically

This chapter describes how to use ADF entity object events and features to programmatically implement business rules in an Oracle ADF application. It also describes how to invoke custom validation code, for example, using setter methods to populate entity rows.

This chapter includes the following sections:

12.1 About Programmatic Business Rules

Complementing the built-in declarative validation features, entity objects and view objects contain method validators and several events you can handle that allow you to programmatically implement encapsulated business logic using Java code. These concepts are illustrated in Figure 12-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 12-1 Key Entity Objects Features and Events for Programmatic Business Logic

Image shows entity object features

Note:

Only new and modified rows participate in transaction validation. Although deleted rows are part of the transaction, they are not included in the validation.

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

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

12.1.2 Additional Functionality for Programmatic Business Rules

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

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

12.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 12.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 12.1.2, "Additional Functionality for Programmatic Business Rules."

To create an attribute-level Method validator:

  1. In the Applications window, 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 section and select the attribute that you want to validate.

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

  5. In the Add Validation Rule dialog, select Method from the Rule Type dropdown list.

    Figure 12-2 Adding an Attribute-Level Method Validator

    Image of Add Validation Rule dialog

    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 12-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. Optionally, click the Validation Execution tab to enter criteria for the execution of the rule, such as dependent attributes and a precondition expression. For more information, see Section 11.6, "Triggering Validation Execution."

  7. Click the Failure Handling tab and enter or select the error message that will be shown to the user if the validation rule fails.

    For more information, see Section 11.7, "Creating Validation Error Messages."

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

When you add a new method validator, JDeveloper updates the XML document 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 12-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 12-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.

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

In addition, you can defer execution of the validator to time of the transaction when using an entity-level method validator.

Before you begin:

It may be helpful to have an understanding of what method validators are. For more information, see Section 12.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 12.1.2, "Additional Functionality for Programmatic Business Rules."

To create an entity-level method validator:

  1. In the Applications window, 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 and click the New icon to add a validation rule.

  4. In the Add Validation Rule dialog, 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 12-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.

  5. Optionally, click the Validation Execution tab to enter criteria for the execution of the rule, such as dependent attributes and a precondition expression. For more information, see Section 11.6, "Triggering Validation Execution."

    Figure 12-3 Adding an Entity-Level Method Validator

    Image of Add Validation rule dialog

    For Method entity validators, you can also use the Validation Execution tab to specify the validation level. If you select Defer Execution to Transaction Level, the validator will fire when the entity is committed. This is useful when multiple rows are displayed for editing in a table.

  6. Click the Failure Handling tab and enter or select the error message that will be shown to the user if the validation rule fails.

    For more information, see Section 11.7, "Creating Validation Error Messages."

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

When you add a new method validator, JDeveloper updates the XML document 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 12-3 illustrates a simple entity-level validation rule that ensures that the DateShipped of an order comes after the DateOrdered.

Example 12-3 Simple Entity-Level Method Validator

public boolean validateDateShippedAfterDateOrdered() {
  Date DateShipped = getDateShipped();
  Date DateOrdered  = getDateOrdered();
  if (DateShipped != null && DateShipped.compareTo(DateOrdered) < 0) {
    return false;
  }
  return true;
}

If you select Defer Execution to Transaction Level on the Validation Execution tab of the Add Validation Rule dialog and set SkipValidation="skipDataControls" on the page that displays the entity object, you can postpone the validation until transaction time. This allows the user to modify multiple rows in a table and then validate them as a group when the user commits. Example 12-4 shows a method validator that verifies that the department name is not null for a collection of entity rows.

Example 12-4 Simple Transaction-Level Method Validator

// Validation method for Departments.
public boolean validateDepartments(ArrayList ctxList) {
   Iterator iter = ctxList.iterator();
   while (iter.hasNext()) {
      JboValidatorContext entValContext = (JboValidatorContext)iter.next();
      DepartmentsImpl deptEO = (DepartmentsImpl)entValContext.getSource();
      if (deptEO.getDepartmentName() == null) {
         // if Dept is null, throw error
         return false;
      }
   }
   return true;
}

12.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."

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

12.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 12-5 shows the overridden create method of an entity object. It calls an attribute setter method to populate the DateOrdered 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 12-5 Programmatically Defaulting Attribute Values for New Rows

// In entity object implementation class 
protected void create(AttributeList nameValuePair) {
   super.create(nameValuePair);
   this.setDateOrdered(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. Also note that any changes you make to the create() method must occur after the call to super.create().

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

12.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 12-6. It shows code from the custom Java class for an entity object. 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 12-6 Eagerly Defaulting an Attribute's Value from a Sequence at Create Time

// In entity object implementation class 
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());
}

12.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 12-7 shows an overridden prepareForDML() method that assigns derived values.

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

12.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 12-8 shows the setter method for an AssignedTo attribute in an entity object.

Example 12-8 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.

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

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

12.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 12.4 and Section 12.4.1. This causes the entity to invoke refresh() using the same mode on any composed child entities it contains.

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

12.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 14.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 12-9.

Example 12-9 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.

12.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 11.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.

For example, consider the overridden beforeCommit() shown in Example 12-10. 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 12-10 Overriding beforeCommit() to Validate All Entities of a Given Type

// In entity object implementation class 
. . . 
@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;
}

12.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 12-11 show a Groovy script that iterates over a view accessor row set.

Example 12-11 Using a View Accessor in Groovy Script

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

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

Example 12-12 Using a View Accessor in a Method Validator

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

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

12.6.1 How to Access Related Entity Rows

You can use an association accessor to access related entity rows. Example 12-13 shows code from the controlpostorder module in the SummitADF application workspace that shows the overridden postChanges() method in the EmpEO entity object's custom Java class. It uses the getDeptEO() association accessor to retrieve the related department for the employee.

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

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

12.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 12-14 illustrates the code for the overridden postChanges() method in the DeptEO entity object's custom Java class. It shows the use of the getEmpEO() association accessor to retrieve the RowSet object of EmpEO rows in order to update the DeptId attribute in each row using the setDeptId() association accessor.

Example 12-14 Accessing a Related Entity Row Set Using an Association Accessor

// In DeptEOImpl.java in the controlpostorder module 
// of the SummitADF application workspace
RowSet newEmployeesBeforePost = null;
@Override
public void postChanges(TransactionEvent transactionEvent) {
      /* Update references only if Department is a NEW one */
   if (getPostState() == STATUS_NEW) {
      /*
      * Get a rowset of employees related
      * to this new department before calling super
      */
   newEmployeesBeforePost = (RowSet)getEmpEO();
   }
   super.postChanges(transactionEvent);
    }
@Override
protected void refreshFKInNewContainees() {
   if (newEmployeesBeforePost != null) {
      Number newDeptId = getId().getSequenceNumber();
         /* 
         * Process the rowset of employees that referenced
         * the new department prior to posting, and update their
         * Id attribute to reflect the refreshed Id value
         * that was assigned by a database sequence during posting.
         */
      while (newEmployeesBeforePost.hasNext()){
         EmpEOImpl emp = (EmpEOImpl)newEmployeesBeforePost.next();
         emp.setDeptId(newDeptId);
      }
      closeNewProductRowSet();
   }  
}

12.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 41.11.4.3, "How to Determine the Current User Name, Tenant Name, or Tenant ID" and Section 41.11.4.4, "How to Determine Membership of a Java EE Security Role".

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

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

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

12.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 12-15, 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 12-15 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 12-16 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 12-16 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 12-17 shows the setUserIdIntoUserDataHashtable() helper method — used by the prepareSession() code in Example 12-15 — that stores this numerical user ID in the user data hash table, using the key provided by the string constant CURRENT_USER_ID.

Example 12-17 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 12-18 to set the CreatedBy attribute programmatically to the value of the currently authenticated user's numerical user ID.

Example 12-18 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;
}

12.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 12-19 shows the Groovy script you would use to reference a userData hash map key named MyKey.

Example 12-19 Accessing the Current User Session Using Groovy Script

adf.userSession.userData.MyKey

12.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.5.6, "How to Use Groovy Scripting Language With Business Components."

12.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."

12.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 12-20 demonstrates this technique.

Example 12-20 Overriding the remove() Method to Verify Entity Status Before Removal

// In 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.

12.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 12-21 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 12-21 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.

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

12.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 12.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 12.1.2, "Additional Functionality for Programmatic Business Rules."

To create a custom validator:

  1. In the Applications window, right-click the project in which you want to create the validator and choose New > From Gallery.

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

  3. In the Create Validation Rule Class dialog, enter a name and package for the rule.

  4. Enter a display name and a description, and click OK.

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

Example 12-22 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 12-23 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 12-23 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 12-23 Custom DateMustComeAfterRule

// 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 and 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.

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

Figure 12-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 12-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 12-24, 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 12-24 BeanInfo to Associate a Customizer with a Custom Validation Rule

package oracle.summit.model...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);
  }
}

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

12.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 12.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 12.1.2, "Additional Functionality for Programmatic Business Rules."

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

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

  1. In the Applications window, right-click the project containing the entity objects and choose Project Properties.

  2. In the Project Properties dialog, expand ADF 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 and click OK.

12.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 12.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 12.1.2, "Additional Functionality for Programmatic Business Rules."

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

To register a custom validator at the IDE level:

  1. From the main menu, choose Tools > Preferences.

  2. In the Preferences dialog, expand ADF 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.