Creating a Reusable Date Range Validation Rule

Send me a mail
 Dive into Oracle ADF   Click to see the XML version of this web page.   (Updated: 2/3/2008; 9:25:50 PM.)
Tips and tricks from Steve Muench on Oracle ADF Framework and JDeveloper IDE

Creating a Reusable Date Range Validation Rule

Using the ADF Business Components, our prescriptive architecture's best practice is to implement all validation in your entity objects. This way business validation rules are applied consistently regardless of application module or view object those entities might be involved with.

Assuming you have an entity object named FiscalYear, with attributes StartingDate and EndingDate of type oracle.jbo.domain.Date, here are the steps for implementing a code-based validation rule that checks that the StartingDate comes before EndingDate.

  1. Write a validation method in your FiscalYearImpl.java class like this. It's name must begin with the prefix validate:
    /**
     * Return false if BeginDate and EndDate
     * are not null and BeginDate > EndDate
     *
     * The error message is supplied when attaching
     * a MethodValidator for this method on the
     * validation panel of the entity object editor.
     */
    public boolean validateDateRange() {
        return   getEndDate()  == null
               ||getHiredate() == null
               ||getHiredate().compareTo(getEndDate()) <= 0;
      }
  2. Edit FiscalYear entity with the Entity Editor, and visit the Validation panel
  3. Click on the FiscalYear entity name in the tree, and press (New...) to add a new entity-level rule
  4. Select the type of MethodValidator and pick validateDateRange() from the list of methods
  5. Enter a validation failure message in the error message box.
  6. Click (OK) to add the new entity-level method validation rule, and (OK) again to finish.

That's it.

However, if you notice that you are writing the same kinds of code-based validations over and over, you can encapsulate a validation "pattern" into a parameter-driven, reusable validation rule. Then, rather than writing code each time you want to use the rule, you simply setup the rule-instance-specific properties, and the generic rule does the rest.

For example, to create a reusable DateRangeRule, you would do the following steps:

  1. Visit the Registered Rules panel (beneath Business Components) in your project properties.
  2. Click on the (New...) button to create a new, registered validation rule
  3. Enter a package name like test.rules for example, and a rule class name like DateRangeRule and click (OK).

Your project will now have the test.rules.DateRangeRule declared.

Edit the DateRangeRule so that its source code looks like this:

package test.rules;
import oracle.jbo.LocaleContext;
import oracle.jbo.ValidationException;
import oracle.jbo.domain.Date;
import oracle.jbo.server.EntityImpl;
import oracle.jbo.server.rules.JbiValidator;
import oracle.jbo.server.util.PropertyChangeEvent;
/**
 * Generic Date Range Validation Rule
 *
 * When the rule is added to an entity object, the
 * developer configures its rule-instance properties
 * for "BeginDateAttrName" and "EndDateAttrName"
 * so the rule knows which date-valued attributes
 * to validate as beginning and ending dates.
 */
public class DateRangeRule implements JbiValidator {
  public DateRangeRule() {}
  /* Return true if start date is less than end date */
  public boolean validateValue(Object entity) {
    EntityImpl eo = (EntityImpl) entity;
    Date beginDate = (Date) eo.getAttribute(getBeginDateAttrName());
    Date endDate = (Date) eo.getAttribute(getEndDateAttrName());
    return (beginDate == null) || (endDate == null) ||
    (beginDate.compareTo(endDate) < 0);
  }
  /* Invoked by the framework for validation */
  public void vetoableChange(PropertyChangeEvent eventObj) {
    EntityImpl eo = (EntityImpl) eventObj.getSource();
    if (!validateValue(eo)) {
      LocaleContext ctx = eo.getDBTransaction().getSession().getLocaleContext();
      String beginPrompt = eo.getStructureDef()
                             .findAttributeDef(getBeginDateAttrName())
                             .getUIHelper().getLabel(ctx);
      String endPrompt = eo.getStructureDef()
                           .findAttributeDef(getEndDateAttrName()).getUIHelper()
                           .getLabel(ctx);
      throw new ValidationException(beginPrompt + " must be before " +
        endPrompt);
    }
  }
  public String getDescription() {
    return description;
  }
  public String getBeginDateAttrName() {
    return beginDateAttrName;
  }
  public String getEndDateAttrName() {
    return endDateAttrName;
  }
  public void setDescription(String str) {
    description = str;
  }
  public void setBeginDateAttrName(String beginDateAttrName) {
    this.beginDateAttrName = beginDateAttrName;
  }
  public void setEndDateAttrName(String endDateAttrName) {
    this.endDateAttrName = endDateAttrName;
  }
  private String description;
  private String beginDateAttrName;
  private String endDateAttrName;
}

There are really just three things happening in the code above to notice:

  • The bean properties beginDateAttrName and endDateAttrName are exposed via standard getter/setter methods. The ADF Business Components design time will introspect these properties and let users of this generic validation rule setup appropriate values for the properties at the time they attach this rule to their entity object.
  • The vetoableChange() method calls validate() to check whether the date-pair is valid, and throws an exception if they are not. The exception message pull the user-visible UI Hint "prompt" information so that if those are setup in your entity, a string like "Date of Hire" would appear in the error message, instead of the raw attribute value name like "Hiredate".
  • The validate() method gets the two attribute values, whose names are supplied in the rule instance properties beginDateAttrName and endDateAttrName, then does the comparison to see if the begin date value comes before the end date value.

To add this new rule to an entity object like our FiscalYear example above, we would:

  1. Edit the FiscalYear entity in the Entity Editor, and visit the Validation panel
  2. Click on the FiscalYear entity at the root of the tree and press (New...)
  3. In the Rules drop-down list, pick the DateRangeRule
  4. Enter values for the beginDateAttrName = StartingDate and the endDateAttrName = EndingDate
  5. Click (OK)

Now if you were to test an application module containing a view object related to the FiscalYear entity, an attempt to enter an EndingDate that came before the StartingDate would result in an error. The big difference with the rule-based approach is that the next time you need a date range validation on a different entity object, you just repeat the steps above to add the generic DateRangeRule to that new entity and away you go.


 



© Copyright 2008 Steve Muench. Click here to send an email to the editor of this weblog.
Last update: 2/3/2008; 9:25:50 PM.