The eventual resulting list of these errors for a given transaction can be sent back to the User Interaction Architecture. Within the presentation logic for displaying errors, the error key can be used to look up the error messages from a resource file or config-uration service. The corresponding arguments can be substituted into the message and the error list can be displayed to the user.
When to Use Exceptions Instead of Errors
A general rule of thumb to follow regarding the use of exceptions and errors is sum-marized by the following Best Practice statement.
Use exceptions whenever processing should halt immedi-ately in a given method. Business errors should then be used wherever processing may continue in the case of an error occurring.
An extension to this rule is that the ErrorListutility should be used anytime there are multiple edits taking place in a method. It is used to manage a list of errors, and it works well for this purpose. However, how should the business object client be notified if errors have occurred in a given method? You don’t usually want to make the ErrorListthe return argument for a method because business methods usually want to return some result. Rather than create return objects that encapsulate both an ErrorListand a return value, you can use the ValidationExceptionclass to hold the list of errors that occurred.
You can use the ValidationExceptionto communicate a list of errors back to a business object client.
T H O U G H T B E ST P R AC T I C E
94 J2EE Best Practices: Java Design Patterns, Automation, and Performance
If business errors are encountered in a method, you can throw a Validation-Exceptionto notify users of the situation. This takes advantage of the power of ex-ceptions in a programming language, which is the fact that your main body of code can assume that operations are successful. A catch block at the bottom, outside of the main processing logic, can be used to handle the error conditions. To use exceptions for com-municating business errors, a method is added to the ErrorListutility that creates aValidationExceptionwith the list of errors already in it.
public ValidationException createValidationException() {
// Add error list to ValidationException.
ValidationException ve = new ValidationException(
"ValidationException: see error list", getErrorList());
return ve;
}
TheValidationExceptionclass would look like this:
public class ValidationException extends Exception {
/*
* The list of errors that occurred
*/
protected ArrayList errorList = null;
/*
* Default constructor
*/
public ValidationException(String message) {
super(message);
} /*
* Construct a validation exception with error list.
*/
public ValidationException(String message, ArrayList errorList) {
super(message);
this.errorList = errorList;
} /*
* Returns the list of BusinessError objects that caused
* this validation exception
*/
public ArrayList getErrorList()
{
return errorList;
} }
You can then use the throwExceptionIfErrors method onErrorList to communicate errors to the client. This method automatically creates the exception and throws it if any errors occurred. It can be invoked at the end of allvalidatemethods.
public void throwExceptionIfErrors() throws ValidationException {
if (!isTransactionSuccess()) {
ValidationException ve = createValidationException();
throw ve;
} }
For each business method that requires validation, either you can create an instance of ErrorList, or you can give all of the business objects anErrorListby adding it as a member variable to the base class. In this case, you need to add aclearmethod to be able to empty the list of errors so you can start fresh within a given method. You now have all of the tools you need on theErrorListto manage business errors successfully. So that there is an example in which multiple errors can occur, assume also that the account number must start with a ‘C’ for checking accounts (such as ‘C1234’) and an ‘S’ for sav-ings accounts (such as ‘S5678’). Thevalidatemethod code would now look like this:
public void validate() throws ValidationException {
// Create an error list for the validation.
ErrorList error = new ErrorList();
try {
// Get hold of the current balance.
BigDecimal balance =
getDecimalProperty("currentBalance");
// Validations for a checking account if (getProperty("type").equals("C")) {
// Validate that the account number starts // with a 'C'.
if (!(getProperty("number").startsWith("C"))) {
errorList.addError("INVALID_ACCT_NUMBER", getProperty("number"));
}
96 J2EE Best Practices: Java Design Patterns, Automation, and Performance
// Validate that the balance is above // the minimum allowed.
if ((balance.compareTo(
new BigDecimal("100.00"))) == -1) {
errorList.addError("CHECKING_MIN_BALANCE", balance.toString());
} }
// Validations for a savings account if (getProperty("type").equals("S")) {
// Validate that the account number starts // with an 'S'.
if (!(getProperty("number").startsWith("S"))) {
errorList.addError("INVALID_ACCT_NUMBER", getProperty("number"));
}
// Validate that the balance is above // the minimum allowed.
if ((balance.compareTo(
new BigDecimal("50.00"))) == -1) {
errorList.addError("SAVINGS_MIN_BALANCE", balance.toString());
} } }
catch (PropertyException pe) {
errorList.addError("GEN_PROPERTY_ERROR", pe.getMessage());
}
// Use the error list utility to automatically throw // an exception with the business errors
// if any occurred.
errorList.throwExceptionIfErrors();
}
Thevalidateexample can be used as a sort of template for all validation methods.
You start out by creating anErrorListinstance, applying the validation edits within a try-catch block, and then calling thethrowExceptionIfErrorsmethod at the end. The try-catch block is used primarily to catch system exceptions or other general type exceptions. In this example, the PropertyException is thrown if a given property name does not exist for a business object. This is an exception thrown by the business logic foundation layer. It is analogous to a system level exception that you
TE AM FL Y
Team-Fly
®would not normally expect. Note that the code maps this exception to one of the de-fined business errors. This is a common technique you will want to use for system-level exceptions such as aRemoteExceptionor aSQLException. These exceptions that have technical messages will not make much sense to a user, so they should be wrapped with some kind of meaningful business error that has been defined.
Map system-level exceptions to defined business errors that have more meaningful messages that can be presented to the users.
Note that if you allow a system-level or other run-time exception to be thrown out of a transactional EJB method, the container automatically rolls back the transac-tion and throws either a RemoteException or a TransactionRolledBack-Exceptiondepending on the transaction context. In these cases, the client needs to handle these exceptions and extract the wrapped exception to determine the cause of failure.
TheaddErrormethod can automatically throw aValidationExceptionif a critical level error is added to the bucket. Thus, you can useValidationException as the primary application-level exception. The other categories of exceptions already mentioned include system-level exceptions such as database failures, resource unavail-able, and other run-time Java exceptions. Many of these conditions can be handled in try-catch blocks at the highest level of the application code on the EJB tier, usually the service component. Again, these conditions need to be mapped to one of the defined business errors so that consistent error messages can be provided to the user. You can also provide the option to show or log the actual exception stack trace, which will be of interest to any support staff, but not usually of much interest to the end user. Remember that if you allowEJBExceptionor other run-time exceptions to be thrown out of a transactional method, the container rolls back the transaction automatically. If you use an application exception, such asValidationException, then you need to explic-itly roll back the transaction usingEJBContext.setRollbackOnly().