The Carbon Java Framework  

The Carbon Core Exception Subsystem

Exception Usage

Author: Greg Hinkle (ghinkle at sapient.com)
Version: $Revision: 1.4 $($Author: ghinkl $ / $Date: 2003/06/26 18:56:35 $)
Created: March 2003

Overview

Purpose

This document is designed to help developers understand and utilize the exception framework of Carbon. This will include utilizing the exceptions supplied with the Framework as well as creating new business and technical exceptions specifically for a project.

Exception overview

There are two types of exceptions in Java, Exception and RuntimeException. These are handled differently by the Java Compiler and the Java Language Specification, in that the former is considered a "checked" exception and the latter is considered a "runtime" exception. "Checked exceptions" are regarded as a specific part of an interface and include compile-time checks to ensure code compatibility. When it is possible that a method may throw a "checked exception", that method must declare it in the throws clause. Runtime exceptions, by contrast, can be thrown from any method and will simply propagate up the stack until they are caught.

The importance of the above discussion is clear from a design perspective and comes into play when determining what the interface for a class should be. Should a class declare that it throws certain exceptions? What about exceptions that are thrown by libraries used within the class?

Exception levelization

The concept of exception levelization is based on the belief that encapsulation is a useful ideal. Encapsulation is the inclusion of one implementation within another so that the included functionality is not apparent. In theory, this simplifies the usage of the ecapsulating service, while allowing future modifications to that service so that its implementation may be changed.

A good example of the idea of encapsulation is the Configuration Service. The primary implementation of the Configuration Service is based upon file storage and therefore uses the file I/O APIs of the SDK. Some of these APIs throw FileNotFoundException for example and therefore might be a candidate for what the Configuration Service would throw to indicate failure. The problem with this, is that the external interface of configuration would be tied to a file-based implementation and would require familiarity with that underlying implementation for the client programmer to understand the problem. With an encapsulated model, this underlying implementation is hidden behind a façade, and might instead return a ConfigurationNotFoundException. This would, for example, then allow later support for a database-backed configuration storage system in which an empty JDBC result might also be returned as a ConfigurationNotFoundException.

Abstract service level exceptions

One of the best practices used in the Framework Core is the concept of creating an abstract package level exception that can serve as a base class for many specific exceptions in the same package. One of the primary benefits of this design is that it allows client code to catch exceptions at various levels of granularity, including ones that span across many service calls.

Runtime versus checked exceptions

As described above, checked exceptions contribute to the declaration of an interface whereas runtime exceptions are automatically passed up the stack until they are caught or they reach the top. The primary decision to use checked versus runtime exceptions comes down to whether you want to force client code to deal with an exception situation or not. By default, most new exceptions should be checked exceptions as you can often use existing runtime exceptions when they are needed. Checked exceptions, since they are part of the interface, should be of a type that makes sense to their condition. They should also include all relevant information in a manner that allows the client to access that data.

If you are throwing an exception for an abnormal condition that you feel client programmers should consciously decide how to handle, throw a checked exception.

Included runtime exceptions

The Framework ships with a few Runtime exceptions that will likely be used often on project teams. These exceptions are explained below are most often the only runtime exceptions that are needed on a project.

InvalidConfigurationException
This exception is designed to be thrown when there is a configured value that is not valid. This could happen to any service that bases its information on data from the configuration service and is generally considered a serious condition. This exception signifies that data from the configuration service must be altered to be valid before the current mechanism or function point will work.
InvalidParameterException
This exception is often used in pre-condition checks that may take place at the entry point to a service. It is designed to be thrown when a parameter to the current method was of an invalid type or had an invalid value.

Exception information

Exceptions are Java objects that are very similar to any other object in the system. They are polymorphic and can have data that relates to them. One of the biggest mistakes in creating an API is when not enough time is focused on modeling the data of exceptions. When there is a failure to retrieve a Configuration, the exception should include where it was searched for and what name was searched for, and not as a big string message. They should be internal String variables that are accessible by client code.

Creating an exception

Below you will find an example abstract exception.

Example Abstract Exception

import org.sape.carbon.core.exception.BaseException;
import org.sape.carbon.core.util.classify.SeverityEnum;

public abstract class AbstractConfigurationException extends BaseException {

    public AbstractConfigurationException(Class sourceClass, String message) {
        super(sourceClass, message);
    }

    public AbstractConfigurationException(
        Class sourceClass, String message, Throwable cause) {
        super(sourceClass, message, cause);
    }

    public SeverityEnum getSeverity(){ return SeverityEnum.TRACE;}

}

Below you find an extension of the abstract exception. Notice the rich interface to this exception that may allow someone to better deal with the exception and perhaps debug it easier.

Example Specific Exception

import org.sape.carbon.core.exception.BaseException;
import org.sape.carbon.core.util.classify.SeverityEnum;

public class DataInvalidConfiguration extends AbstractConfigurationException {
    String configurationDocument, invalidDataKey;
    Object invalidDataValue;

    public DataInvalidConfiguration(
        Class sourceClass,
        String configurationDocument,
        String invalidDataKey,
        Object invalidDataValue,
        Throwable formattingFailure) {

        super(sourceClass,
            "Failure to store configuration document [" +
            configurationDocument + "] because key [" +
            invalidDataKey + "] had an invalid value.",
            formattingFailure);

        this.configurationDocument = configurationDocument;
        this.invalidDataKey = invalidDataKey;
        this.invalidDataValue = invalidDataValue;
    }

    public String getDocumentName() {
    return this.configurationDocument;
    }

    public String getInvalidDataKey() {
        return this.invalidDataKey;
    }

    public Object getInvalidDataValue() {
        return this.invalidDataValue;
    }

    public SeverityEnum getSeverity(){ return SeverityEnum.INFO;}

}

 

Business versus Technical Exceptions

Exceptions can typically be split between whether the occur due to a technical failure or the violation of a business rule. These two types of exceptions can have very different usage patterns and it is valuable to have a different mechanisms for them. While both business and technical packages can benefit from the encapsulation provided by levelization, there are different ways this can be accomplished.

Custom source in technical exceptions

Technical services are typically not valuable in their own right, but rather makes sense as part of a system that provides busines value. In these cases, the technical services are usually encapsulated behind the business services using them. In Carbon, a best practice has been to allow users to pass in a Source Class to the exception when creating one. This is because it is often useful to see technical service failures categorized by where they happen rather than what service they are. Technical service failures are more often related to the way in which they are used.

Default source in business exceptions

With business services, this capability is less meaningful as a business failure is often traceable directly back to the implementing business code. In this case, filtering the tracking of these exceptions is best done simply by the exception class itself. When building business exceptions, you can just hardcode the exception class in the call to the superclass constructor.

Example business exception

import org.sape.carbon.core.exception.BaseException;
import org.sape.carbon.core.util.classify.SeverityEnum;

public class AbstractAccountModuleException extends BaseException {
    AccountId accountId;

    public AbstractAccountModuleException(
        AccountId accountId,
        String message,
        Throwable levelizedFailure) {

        super(getClass(),
            "Business rule failure on accoung [" +
            accountId + "] due to [" +
            message + "].",
            levelizedFailure);

        this.accountId = accountId;
    }

    public AccountID getAccountId() {
    return this.accountId;
    }

    public SeverityEnum getSeverity(){ return SeverityEnum.WARNING;}
}

The example exception above is a module-level abstract business exception. Subclasses of this exception would be created for specific business service failures, but would not need to supply a source class.

Exceptions and Logging

Exceptions are a primary concept in an application and provide the capability to track and understand what is failing in the system. They may alert administrators to serious errors or they may point out common mistakes that users are making with a user interface. Either way, there is often a lot to gain from being able to track and gather information on exceptions and a primary mechanism for this would be the logging of those exceptions. In order to filter out the interesting information from useless and expected exceptions, the exception system uses the same filtering mechanism as the primary logging service. Exceptions therefore have both a source and a severity that are based on Class object of the calling code and the org.sape.carbon.core.classify.SeverityEnum class respectively.

The default exception delegate will then attempt to log the information of all exceptions to the default logging service utilizing the source and severity information as standard filter data. This allows the logging system to route and filter exceptions just like any other log message.
 

Severities

Below is a description of the various Severity levels supported in the basic SeverityEnum of the Carbon Framework. It should be noted that technical services are never logged at a level higher than info. These services are designed merely to be the backend to other business services and therefore do not want to be handled in a manner more severe than the business problem at hand. It is very important to follow this guideline and be consistent and careful about how severity levels are assigned. A badly placed severity can significantly damage the ability for log files to serve as a good debugging tool. For further details on logging severities please refer to Logging

Fatal
Should be used when the system encounters a critical error which affects the accuracy, integrity or capability of the system as a whole, not just the current request/transaction.
Error
Should be used when the system encounters an error which it could not handle and was forced to terminate the current operation, but concurrent and future requests should not be affected.
Warn
Should be used when the system encountered a recoverable error, which should non-the-less be logged as its occurrence is significant.
Info
Should be used for moderately detailed information needed to debug system problems.
Debug
Should be used for all interesting periodic events so that anyone browsing the logs should be able to see the amount and kind of processing occurring in the system.
Trace
Should be used for high verbosity debug logging that could significantly affect the performance of the system.

Appendices

Resources

Name Resource
Sun Tutorial on Throwing Exceptions http://java.sun.com/docs/books/tutorial/essential/exceptions/throwing.html
Commons Loggin API http://jakarta.apache.org/commons/logging/api/index.html

Copyright © 2001-2003, Sapient Corporation