Friday, August 03, 2007

How to Pimp ADF BC Exception Handling

When you have been doing things in a particular way for a long time, you sometimes find that in the mean time your way has become the slow way. Not necessarily the wrong way as it works (otherwise you would not have kept doing it all the time, would you?), but you're just not fashionable anymore. Like wearing tight pants in the 60's or wide pants in the 80's. Or using vi instead of a state-of-the-art IDE like JDeveloper or Eclipse for that matter (oh yes, I dare!).

I recently discovered that I became unfashionable by using the setAttributeXXX() and validateEntity() method for implementing business rules in ADF BC instead of using Validators. Not that I didn't know of Validators, I just thought that they would not give me proper control over exception and message handling. Because one of the things I would like to have, is one single message bundle in which I could use a consistent coding of my messages, like APP-00001, APP-00002, etc. Even more important, the other thing I would like to have is that I can translate all messages to Kalaallisut the minute I need to make my application available for the Inuit people of Greenland.

As you might know, up till JDeveloper 10.1.3 ADF BC will create an entity-specific message bundle to store the messages you provide with Validators. So with many entity objects big chance you end up with many message bundles, making that keeping error codes consistent becomes a nightmare. And what about all the separate files you need to translate! You might find yourself no longer to be able to tell the messages for the bundles.

But as Steve Muench was very persistent in trying to convince me using Validators I finally gave in and tried finding a way to tackle my problem, and succeeded! No worries, don't expect rocket science from me. I hate complex or obscure code, as building maintainable Information Systems already is hard enough as it is, and therefore always try to practice Simple Design.

I assume that you have created a layer of framework extension classes as described in section 2.5 of the ADF Developers Guide and that your entity base class is called MyAppEntityImpl. If you have not yet created such a layer, do that first and come back after you finished. Chop chop!

Basically the steps are as follows:
  • Create extension classes that extend the exceptions that ADF BC will throw when validation fails
  • Override the constructor of the super class and pass in the message bundle in the call to super()
  • Override the setAttributeInternal() and validateEntity() methods in the MyAppEntityImpl.java entity base class and make they thrown your exceptions instead of the default ones.
Does that sound simple or what? No? OK, let me show you how I did it.

Attribute-level Validators will throw the AttrValException. So what I did was create a MyAppAttrValException as follows:

package myapp.model.exception;

import oracle.jbo.AttrValException;
import myapp.model.ResourceBundle;

public class AttrValException extends AttrValException
{
public MyAppAttrValException(String errorCode, Object[] params)
{
super(ResourceBundle.class, errorCode, params);
}

/**
* When the message contains a semicolon, return the message
* starting with the position after the semicolon and limit the
* text to the message text from the resource bundle.
*
* @return the stripped message
*/
public String getMessage()
{
String message = super.getMessage();
// strip off product code and error code
int semiColon = message.indexOf(":");
if (semiColon > 0)
{
message = message.substring(semiColon + 2);
}
return message;
}

And this is how I override the setAttributeInternal in the MyAppEntityImpl:

/**
* Overrides the setAttributeInternal of the superclass in order to
* pass in a custom message bundle to MyAppAttrValException subclass
* of the AttrValException. To be able to uniquely identify the
* entries in the message bundle, the error code is extended with the
* fully qualified class name of the Impl.
*/
protected void setAttributeInternal(int index, Object val)
{
try
{
super.setAttributeInternal(index, val);
}
catch (AttrValException e)
{
String errorCode = new StringBuffer(getClass().getName())
.append(".")
.append(e.getErrorCode())
.toString();
throw new MyAppAttrValException(errorCode, e.getErrorParameters());
}
}

In a similar way you can handle entity-instance-level Validators and Method Validator by extending the ValidationException and overriding the validateEntity() method to throw your MyAppValidationException.

As said, the message you provided when creating build-in Validators and Method Validators end up in an entity-specific message bundle. I've been told that this is going to change in JDeveloper 11g, but currently there is no stopping ADF BC from doing that.

So, at a convenient point in time (for example when most of your Validators have been implemented and tested), what you need to do is copying the error messages from the entity-specific message bundles to your custom message bundle.

To prevent duplicates in the keys of the custom message bundle, you should extend the key of each message with the fully qualified class name of the Impl.java file of the entity object where it's coming from. Otherwise duplicates might occur whenever you have two different entity objects, both with an attribute with the same name and a build-in Validator specified for them. The overriden setAttributeInternal() method assumes you did.

The entries in the message bundle would look
then similar to this:

{ "myapp.model.adfbc.businessobject.EmployeeImpl.Salary_Rule_0",
"APP-00001 Employee's Salary may not be below zero" },
...


Well that wasn't rocket science, was it?

16 comments:

  1. well done! Very nice idea and quite helpful.

    ReplyDelete
  2. Anonymous3:41 PM

    seems to work well when just one attribute's method validator fails. But, when more than one attribute for the same entity has it's own method validator and they each fail validation, the error message now includes something like this: JBO-27024: Failed to validate a row with key oracle.jbo.Key, along with lines for the specific errors. Any easy ways to eliminate this problem?

    ReplyDelete
  3. Sorry, I only saw your comment recently (because of the spam).

    I tried to find time to find out, but I didn't and it is not likely I will on a short notice. You might try to post this question on the JDeveloper forum.

    ReplyDelete
  4. Anonymous10:13 AM

    The JBO-27024 is an exception that is used to bundle multiple validation exceptions. The ADF SR Demo (ADF BC) by Steve Muench customizes the default way a "tree" of
    bundled ADF exceptions gets translated into JSF error message objects for display in the
    JSP page by overriding the reportErrors() method of the ADF FacesPageLifecycle, which ensures that end users don't see the JBO-27024 but only the individual errors (see ADF Developer's Guide section 20.8).
    JHeadstart has copied the SR Demo error reporting extension to the JHeadstart ErrorReportingUtils class and made some further customizations (see JHeadstart Developer's Guide section 11.4).

    ReplyDelete
  5. Anonymous2:21 PM

    If you use JHeadstart it might be easier to apply the method described by Frank Houweling in this Amis blog post.

    ReplyDelete
  6. Anonymous7:45 AM

    I like a game which needs to use wonderland Gold, when you do not have wonderland online Gold, you must borrow wonderland money from friends, or you buy wonderland Gold. If you get cheap wonderland online Gold, you can continue this game.

    ReplyDelete
  7. An existing database SQL, and Oracle will have a wide range of table constraints, including primary keys, unique keys, foreign keys, not null values and restrictions for verification. In this blog I will show the power of ADF workflow and how it can save much time.
    Printer cartridges

    ReplyDelete
  8. very good tutorial i like the style

    ReplyDelete
  9. Anonymous8:17 PM

    JBO-27024 is an exception that is used to bundle multiple validation exceptions. Menu Stands

    ReplyDelete
  10. Oh my god, there's so much useful info above!

    ReplyDelete