Friday, July 13, 2007

We Are Proud of Borg

I case you wonder why I don't write that much lately, my wife just got out of the hospital with a so-called Ilizarov frame, with the purpose to try and fix an arthritis in her ankle (caused by an ill-treated complicated fracture many, many years ago).

Believe me, anyone who've seen her was impressed! She looks like an unfinished Robocop, a kind of Transformer that just got out of it's egg. When she wants something from you, you just know that resistance is futile. Every time she walks I have a hard time suppressing the urge to make 'uhnk-chuck uhnk-chuck' sounds. Fortunately she doesn't read my blog (I hope) and if she does, I'm just too fast for her as long as I don't let myself get cornered.

What also is impressive is the amount of time you need to spend in the beginning to take care of some one in her condition. People with children might remember the first couple of weeks, how chaotic things can be and you trying to get a grip on the situation and finding a modus operandi to get you through the day without going wacko. Picture that, plus a full-time job and the Dutch health care system of today and you cannot help feeling sorry for me. Fortunately there are plenty of people around me willing to help out, so forgive me for making things look worse than they are. I needed an excuse for not writing that much and thought a little exaggeration could do some good here.

But what is most impressive is the courage of my wife, to go through the operation knowing what she nows about the frame that she will have to suffer that for three months. That really makes me proud!

Wednesday, July 04, 2007

How to Prevent Your Rule Gets Fired in ADF BC?

When using the Oracle ADF (Application Development Framework), implementing data-related business rules in the persistance layer is a good practice. In ADF this business layer is called ADF Business Components (aka BC4J, or Business Components for Java). This article will go briefly into this subject, just enough to let you get an appetite for the upcoming revised white paper Business Rules in ADF BC. So don't eat too much of this apetizer, to leave room for the main course to come!

Implemening business rules in ADF Business Components means implementing them in so-called entity objects. For an application that uses a relational database, to a certain extend you can compare an entity object with an EJB entity bean, as like an entity bean the purpose of the entity object is to manage storage of data in a specific table. Unlike EJB entity beans, ADF entity objects provide hooks to implement business rules that go way beyond what you can do with EJB entity beans.

I won't go into detail about these hooks. When you're interested, enough documentation about the subject can be found on the internet (to begin with the Steve Muench's web log) or read the white paper! I will let you know when it is available and where to find it.

Where I do want to go into detail is one aspect, being how to prevent that rules get fired unneccessarily, for example for reasons of performance. I assume some basic knowledge of ADF Business Components, so if you don't have that, this is where you might want to stop reading.

There are the following typical options, provided as methods on any EntityImpl:
  • The isAttributeChanged() method can be used to check if the value of an attribute has actually changed, before firing a rule that only makes sense when this is the case.
  • Furthermore there is the getEntityState() method that can be used to check the status of an entity object in the current transaction, which can either be new, changed since it has been queried, or deleted. You can use this method to make that a rule that only gets fired, for example when the entity object is new.
  • There is also the getPostState() that does a similiar thing as getEntityState() but which takes into consideration whether the change has been posted to the database.
Sometimes knowing whether or not something has changed does not suffice, for example because you need to be compare the old value of an attribute with the new one. That typically happens in case of status attributes with restrictions on the state changes. Normally you would be able to do so using the getPostedAttribute() method, that will return the original value of an attribute as read from or posted to the database. However, that won't work when you are using the beforeCommit() method to fire your rule, as at that time the change already has been posted, so the value getPostedAttribute() returns will not differ from what the getter will return.

"So what", you might think, "when do I ever want to use the beforeCommit()?". Well, you have to as soon as you are dealing with a rule that concerns two or more entity objects that could trigger the rule, as in that case the beforeCommit() is the only hook of which you can be sure that all changes made are reflected by the entity objects involved.

Suppose you have a Project with ProjectAssignments and you want to make sure that the begin and end date of the ProjectAssignments fall within the begin and end date of the Project. A classical example, I dare say. Now the events that could fire this rule are the creation of a ProjectAssignment, the update of the Project its start or end date or the update of the ProjectAssignment its start or end date.

Regarding the creation of the ProjectAssignment, that you can verify by using the getEntityState() which would return STATUS_NEW in that case. Regarding the change of any of the dates, you can check for STATUS_MODIFIED, but that also returns true when any of the other attributes have been changed.

Now suppose that, as there can be multiple ProjectAssignments for one Project, you only want to validate this rule when one of those dates actually did change. The only way to know is by comparing the old values with the new one. As explained before, as the hook you are using will be the beforeCommit() method, the getPostedAttribute() also will return the new value as that time the changes already have been posted to the database.

Bugger, what now? Well, I wouldn't have raised the question unless I would have some solution to it, would I? The solution involves a bit yet pretty straightforward coding. I will only show how to solve this for the Project, for the ProjectAssignment the problem can be solved likewise. The whole idea behind the work-around is that you will use instance variables to store the old values so that they are still available in the beforeCommit().

Open the ProjectImpl.java and add the following private instance variables:

private Date startDateOldValue;
private Date endDateOldValue;

In general you can use a convention to call this specific type of custom instance variables [attribute name]OldValue, to reflect their purpose clearly. Now you need to make that these variables are initialized at the proper moment. This will not be the validateEntity(), as that can fire more than once with unpredictable results. No, it should be the in the doDML() of the ProjectImpl, as follows:

protected void doDML(int operation, TransactionEvent e)
{
  // store old values to be able to compare them with
  // new ones in beforeCommit()
  startDateOldValue = (Date)getPostedAttribute(STARTDATE);
  endDateOldValue = (Date)getPostedAttribute(ENDDATE);

  super.doDML(operation, e);
}

Now in the beforeCommit() you can compare the startDateOldValue with what will be returned by getStartDate(), etc. Although it might not look like it at first sight, this might be the most trickiest part as you need to deal with null values as well. In JHeadstart the following convenience method has been created to tackle this, in the oracle.jheadstart.model.adfbc.AdfbcUtils class:

public static boolean valuesAreDifferent(Object firstValue
              , Object secondValue)
{
  boolean returnValue = false;
  if ((firstValue == null) || (secondValue == null))
  {
    if (( (firstValue == null) && !(secondValue == null))
        ||
        (!(firstValue == null) && (secondValue == null)))
    {
      returnValue = true;
    }
  }
  else
  {
    if (!(firstValue.equals(secondValue)))
    {
      returnValue = true;
    }
  }
  return returnValue;
}

This method is being used in the beforeCommit() of the ProjectImpl, as follows:

public void beforeCommit(TransactionEvent p0)
{
  if ( getEntityState() == STATUS_MODIFIED &&
       ( AdfbcUtils.valuesAreDifferent(startDateOldValue,
              getStartDate()) ||
         AdfbcUtils.valuesAreDifferent(endDateOldValue,
              getEndDate())
       )
    )
  {
    brProjStartEndDate();
  }
  super.beforeCommit(p0);
}

Finally, the actual rule has been implemented as the brProjStartEndDate() method on the ProjectImpl as follows:

public void brProjStartEndDate()
{
  RowIterator projAssignSet = getProjectAssignments();
  ProjectAssignmentImpl projAssign;
  while (projAssignSet.hasNext())
  {
    projAssign =
      (ProjectAssignmentImpl)projAssignSet.next();
    if (((getEndDate() == null) ||
         (projAssign.getStartDate().
              compareTo(getEndDate()) <= 0)
        ) &&
         (projAssign.getStartDate().
              compareTo(getStartDate()) >= 0)
       )
    {
      // rule is true
    }
    else
    {
      throw new JboException("Project start date must " +
        "be before start date of any Project Assignment");
    }
  }
}

Well that wasn't to difficult, was it?