Wednesday, March 7, 2012

Extending the Groovy support in ADF BC

If you have worked with ADF Business Components in ADF 11g, chances are you have used the very powerful Groovy support. Perhaps you used it to create an entity validation rule, as a default value for a (transient) attribute or to calculate the value of a bind variable, etc. As part of this Groovy Support you get a number of aggregate functions that are not available in "normal Java-code". See this whitepaper or the chapter on Groovy support in the ADF developers guide. This post describes how to expose (comparable, but slightly more powerful) aggregate functions to Java code.

The ADF BC aggregate functions are often very useful. The ADF developers guide says the following on aggregate functions:
You can use the following built-in aggregate functions on ADF RowSet objects:

  • rowSetAttr.sum(GroovyExpr)
  • rowSetAttr.count(GroovyExpr)
  • rowSetAttr.avg(GroovyExpr)
  • rowSetAttr.min(GroovyExpr)
  • rowSetAttr.max(GroovyExpr)
These aggregate functions accept a string-value argument that is interpreted as a Groovy expression that is evaluated in the context of each row in the rowset as the aggregate is being computed. The Groovy expression must return a numeric value (or number domain). For example:

employeesInDept.sum("Sal")
or

employeesInDept.sum("Sal!=0?Sal:0 + Comm!=0?Comm:0")

The API
An important disadvantage of these functions is that you can only use these on RowSets and only from inside Groovy scripts. Wouldn’t it be nice if we could use these functions anywhere we want, e.g. also in “normal” Java code? It turns out this is actually quite easy, because ADF BC exposes the most important parts of the Groovy implementation in its API.

Using the API we get the following for free:
  • Simplified syntax when refering to attribute values. (E.g. we can use Price * Quantity instead of row.getPrice().multiply(row.getQuantity()).)
  • Automatic conversion of oracle.jbo.domain.* values to Java types (for easy manipulation in Groovy).
  • Some special ADF BC expressions, such as adf.currentDate.
  • The mentioned aggregate functions.


GroovySupport
As an example (based on my work for one of my projects), I created the GroovySupport class.

Some examples of its use in Application Module methods:
/**
 * Get the sum of all salaries of employees that are not manager in the current department
 * (including new employee rows that have not yet been committed to the database).
 *
 * @return the sum
 */
public Number getSumOfSalaries() {
    GroovySupport groovy = GroovySupport.get(getEmployeesInDepartment());
    return groovy.sum("Salary", "!Job.JobTitle.contains('Manager')");
}

/**
 * Get the first free EmployeeId starting at 100.
 *
 * @return the first free EmployeeId.
 */
public int getFirstFreeEmployeeId() {
    return GroovySupport.get(getEmployees()).firstFreeInteger("EmployeeId", 100);
}

/**
 * Get the estimated cost of commission to sales.
 *
 * @return the estimated cost
 */
public Number getCommissionEstimates() {
    GroovySupport groovy = GroovySupport.get(getEmployees());
    return groovy.sum("CommissionPct * Salary",
                      "CommissionPct && Salary && JobId.startsWith('SA')");
}

/**
 * Limit the commission percentage of all employees to the given amount.
 *
 * @param limit the new limit
 */
public void setCommissionPctLimit(BigDecimal limit) {
    GroovySupport groovy = GroovySupport.get(getEmployees());
    // Unfortunately setting values in Groovy is not simplified by the ADF BC API. Perhaps
    // we can extend this and simplify it? It would be nice to use expressions like:
    // CommissionPct = 0.10
    // Perhaps I will look into it in the (distant?) future.
    String expr = "adf.object.setCommissionPct(new oracle.jbo.domain.Number("+limit+"))";
    groovy.doForAllRows(expr, "CommissionPct && CommissionPct > "+limit);
}

For more information look at the documentation in the GroovySupport code.

Download the GroovySupport class.