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.

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.

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.

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

Click the New icon to add a validation rule.

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.

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

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.

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.

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.

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

Click the New icon to add a validation rule.

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.

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

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.

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.

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.

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.

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.

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.

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.

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

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.

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

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.

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.

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

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.

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:

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.

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.

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.

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:

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.

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