Special Edition Using Enterprise JavaBeans 2.0
Application Exceptions
As far as the EJB container is concerned , exceptions can be separated into two types: those that represent a violation of a business rule (or some other condition in the application logic) and those that represent a low-level system problem. These types are known as application exceptions and system exceptions , respectively. It's important for you to learn the distinction because the EJB container treats them quite differently. This is especially true when it comes to transactions. An application exception is thrown to indicate that some type of problem has been detected by an application's business logic. As an example, you might throw an application exception if an attempt to submit an auction bid that is less than the minimum required amount is made. This isn't a catastrophic event, but it is a problem and it needs to be reported and dealt with. Other problems are much more serious and need to be addressed in an entirely different way. An application exception wouldn't be appropriate at all if the method call to submit the bid never made it to the auction object because of a communications failure. This type of error would clearly be a system exception because it has nothing to do with the auction application itself, but is instead a problem with the underlying infrastructure. You'll learn more about the types of problems system exceptions represent and how they're handled in the next section. You might be wondering at this point why this distinction between the types of exceptions is so important. Application and system exceptions represent different conditions, but, in most ways, an exception is an exception after all. This is true, but categorizing exceptions can be used to make the nature of a problem more clear, enforce different constraints at compile time, or allow the runtime environment to respond differently when an exception occurs. The brief discussion you've seen so far about the differences between application and system exceptions illustrates how grouping exceptions can help you more clearly convey the nature of a problem to a programmer or an end user . The corrective action taken by a programmer who receives a system exception when calling a method on an enterprise bean is likely to be totally different from the response to an application exception. For example, a system exception might indicate that the client application is unable to communicate with the server, but an application exception might just mean that the data being passed isn't valid. Just knowing to which of these two categories an exception belongs provides a good deal of information to the caller. This can simplify the design of error handling code and help the caller take appropriate action when a problem occurs. This is similar to the reason that error handling in Java is based on a hierarchy of exception classes and not just one. Simply choosing the appropriate exception class to represent a problem provides information about the condition that caused it. The next reason to distinguish application exceptions from system exceptions relates to compile-time checking. Distinctions between exceptions at compile time are core to Java, so they're nothing new to you. Two of the branches that occur high in the language's exception class hierarchy determine how certain error conditions are viewed and how the compiler in turn expects you to address them. First of all, subclasses of java.lang.Throwable are immediately split into being subclasses of either java.lang.Error or java.lang.Exception . Error and its subclasses are for the most part used to indicate serious low-level problems, such as a flaw in the JVM itself. As a developer, you're not expected to catch Error and its subclasses because it's unlikely you could do anything about the problem even if you did. The branch underneath Throwable that begins with Exception is of more interest to you and there is an immediate distinction to be made here as well. You might want to catch Exception or any of its subclasses, but they are not all treated the same. Where a particular subclass of Exception is found in the inheritance hierarchy determines what type of problem it represents. Subclasses of RuntimeException , which is a direct subclass of Exception , represent errors that aren't expected to occur in a correct program. For example, ClassCastException is a subclass of RuntimeException that you've probably encountered more times than you'd like to remember. A casting error should never occur unless the code that caused it wasn't written to handle the situation it encountered . A low-level system failure doesn't by itself cause a ClassCastException . This exception is caused by code that attempts a cast without first verifying that it knows the type of the object with which it's working. When you see a ClassCastException , you usually know that there's a bug in the code you're running or something about the environment isn't set up in a way that matches the assumptions made by the program. RuntimeException and its subclasses are referred to as unchecked exceptions because the compiler doesn't require you to catch them. The idea is that if the code is correct and hasn't made any inappropriate assumptions, a RuntimeException won't occur. If a RuntimeException does occur, the code is likely broken and catching the exception and attempting to continue on isn't likely of much use. Subclasses of Exception that don't extend RuntimeException are known as checked exceptions. These are exceptions, such as java.io.IOException , that indicate error conditions that are often outside the control of your program. A checked exception could very well occur in perfectly correct code. Because this type of exception can happen in a correct program, the compiler requires you to catch checked exceptions. The separation of exceptions into checked and unchecked allows both you and the compiler to treat them differently. Shortly, you'll see how the checked versus unchecked distinction applies to application and system exceptions at compile time. But first, consider the runtime implication of this separation, because it's actually the more important one. Most exceptions (at least the checked ones, anyway) are to some extent recoverable. What's significant about application exceptions is that they are intended to represent error conditions that might be recoverable in a transactional sense. You'll see more about this later in this chapter, but the basic idea is that the occurrence of an application exception doesn't necessarily invalidate the transaction that's currently in progress. In fact, one of the high-level goals for exception handling expressed in the EJB 2.0 Specification is that application exceptions should be handled in such a way that the client is given the opportunity to recover from them. System exceptions, on the other hand, represent low-level problems deemed to be outside the client's control that are serious enough to invalidate a transaction and cause it to roll back. To correctly declare your exception classes, you need to know the practical details of how you designate an exception as an application exception. It's one thing to define an application exception in terms of what type of error condition it's supposed to represent and what it implies from a transactional standpoint, but it's another matter to pass that distinction on to the EJB container. Given that the container treats application and system exceptions differently, it must have a concrete way to tell them apart. Fortunately, the distinction is a simple one to make. Application and system exceptions are separated based entirely on their superclass hierarchies. Any exception class that is a subclass of Exception , but not a subclass of RuntimeException or java.rmi.RemoteException , is an application exception.
Note Because application exceptions are not subclasses of RuntimeException , they are always checked exceptions. Whenever you call a method that can throw an application exception, you must either catch that exception (or a superclass of it) or declare it in the throws clauses of your calling method. As you'll see later, some system exceptions are checked and others are unchecked.
You've already seen a number of application exceptions referenced in the code examples in earlier chapters. In particular, application exceptions are seen listed in the throws clauses of methods defined in the home and component interfaces of entity and session beans. It's important to note that application exceptions don't apply to message-driven beans. The whole intent of an application exception is to give the caller a chance to recover from an error. Message-driven beans don't provide for synchronous interaction with a client the way entity and session beans do, so there's not a client available to handle an application exception from this bean type. The Standard EJB Application Exceptions
Other than RemoteException , most of the exceptions you've seen in earlier chapters have been application exceptions. The EJB API defines a number of application exceptions to address some of the more common application-level error conditions. When you develop your own enterprise bean classes, you're responsible for using these exceptions (or subclasses of them) to report the error conditions they represent. To do that you need to understand each of these exceptions and how you're expected to use them. CreateException
A javax.ejb.CreateException is thrown to indicate that an application-level error occurred during an attempt to create an instance of a session bean or entity bean. This exception must be included in the throws clause of each create method you declare in the remote or local home interface of an enterprise bean. This allows you or the container to report a problem during a call to an ejbCreate or ejbPostCreate method. For example, it would be appropriate for you to throw a CreateException if a caller passes invalid parameters to a create method.
Note Although you're required to declare your create methods to throw CreateException , you're not required to do the same for your ejbCreate and ejbPostCreate methods. These methods have to include CreateException in their throws clauses only if they actually have the potential to throw it.
If a CreateException is thrown during an attempt to create a session bean instance, the exception implies that the instance was never created. This is not necessarily true for an entity bean. A CreateException during the creation of a session bean strictly comes from an ejbCreate method. This exception might instead come from an ejbPostCreate method for an entity bean. In this case, the object has been created but not fully initialized . DuplicateKeyException
javax.ejb.DuplicateKeyException is a subclass of CreateException that makes the root cause of a creation error more clear to the caller. If the creation of an entity bean instance fails because of a unique key constraint (typically meaning that the primary key is a duplicate in the database), the container (or you if you're doing BMP) is expected to throw this exception. Unlike CreateException , DuplicateKeyException is thrown only from ejbCreate methods. This means that you can be sure that the entity wasn't created when this exception is reported.
Note Because you already have to include a superclass of DuplicateKeyException ( CreateException ) in the throws clause for a create method, you don't have to explicitly include this exception.
FinderException
A java.ejb.FinderException indicates that an application error occurred during execution of an ejbFind or ejbSelect method. You're required to include this exception in the throws clause of every finder method you declare in an entity bean's remote or local home interface and in every ejbFind and ejbSelect method declaration in an entity bean class. Notice that this requirement is different than the one imposed for CreateException . You must always include this application exception in the declarations of your finder method implementations . A particular constraint imposed by the EJB 2.0 Specification is that you must throw this exception from a single-object finder or select method if the query it executes returns more than one object. This situation should be rare, because it indicates a bad assumption on the part of a method implementation about the data it's working with or the validity of the parameters supplied by its caller. If there's any chance of a finder or select method returning more than one object, you should declare it to return multiple objects. Most often, you should only need to use a FinderException if you have to report invalid parameters being passed to a finder or select method. ObjectNotFoundException
The javax.ejb.ObjectNotFoundException subclass of FinderException indicates that the object requested from a single-object finder or select method does not exist. If you're using CMP, the container is responsible for throwing this exception from its ejbFind and ejbSelect methods. When using BMP, you're responsible for throwing it from your ejbFind methods when your query doesn't return the desired object. Multi-object finders and select methods shouldn't throw this exception but should instead return an empty collection when no matching objects are found. An exception is appropriate for the single-object versions because a caller is expecting exactly one match for the criteria used by the method ”not finding that match is likely the sign of an error. An ObjectNotFoundException for an entity bean likely means that the entity being requested has been deleted from the database.
Note Because FinderException is already included in the declaration of each finder method, you don't have to explicitly include ObjectNotFoundException .
RemoveException
A javax.ejb.RemoveException is used to report an error that occurs during an attempt to remove a session or entity bean. For example, calling the remove method of EJBHome (or EJBLocalHome ) that accepts a primary key as an argument is invalid for a session bean, so it results in this exception. Calling remove on a stateful session bean that is currently involved in a transaction will also cause the container to throw a RemoveException . Although it's used with session beans, RemoveException applies more often to problems that occur when an attempt is made to delete an entity object from the database. If you're using CMP, the container calls the ejbRemove method for an entity object before attempting to remove it from the database. This gives you the chance to do any preparation that's needed before the database delete is performed. If the bean is in a state such that it shouldn't be deleted, you can veto the delete by throwing a RemoveException . For example, if an instance of EnglishAuctionBean has assigned and notified an auction winner but the purchase of the item hasn't been completed, disallowing the deletion of the auction entity would be a reasonable business rule to enforce. If you're using BMP, you can report problems such as a foreign key constraint that prevents deletion of the entity using a RemoveException . When a client receives a RemoveException , it doesn't know, in general, whether the remove occurred. If you use RemoveException to report business rule constraints that prevent the removal of an entity, you should do so in a way that makes it clear to the client that the entity wasn't deleted. Extending the Application Exceptions
It's a good practice in general to extend high-level exceptions so that you can provide more specific error reporting to a caller. As you just saw, this is done within the standard EJB application exceptions with DuplicateKeyException and ObjectNotFoundException . There's no reason to stop there in making these exceptions more useful. Something the standard exceptions share that is also somewhat of a limitation is that they offer only a no-argument constructor and a constructor that accepts a string message. This is typical for exceptions included in the Java API, but it still doesn't offer you much flexibility in error reporting. For example, CreateException is ambiguous when it's thrown for an entity bean because it doesn't specify whether the error occurred before the entity object was created or while it was being initialized. You can supply a message string when you're constructing the exception object that describes the error, but that's only helpful to a person reading the error message. Responding programmatically to the exact meaning of an exception requires more than embedding information in a message string (if you want it to be robust and reliable, that is). Consider the alternative shown in Listing 13.1. Listing 13.1 CreateEntityException.java “A More Informative Extension of CreateException
import javax.ejb.CreateException; public class CreateEntityException extends CreateException { // read-only attribute that indicates whether the associated // entity was created before this exception was thrown protected boolean entityCreated; public CreateEntityException(boolean entityCreated) { this(null, entityCreated); } public CreateEntityException(String msg, boolean entityCreated) { super(msg); this.entityCreated = entityCreated; } public boolean getEntityCreated() { return entityCreated; } } CreateEntityException extends CreateException by adding an entityCreated attribute that tells the caller more about the creation error that occurred. If a bean provider throws CreateEntityException from an ejbCreate or ejbPostCreate method, the code that requested the creation of the entity object can handle the error differently based on what point the error occurred. CreateEntityException extends a standard exception to provide an error reporting option that is still generic. You should also consider extending the standard exceptions to better support your specific application needs. For example, Listing 13.2 illustrates an extension of RemoveException that supports a specific need of the auction site without needing additional attributes in the exception class. Listing 13.2 RemoveUnfinishedAuctionException.java “An Application-Specific Exception Class
package com.que.ejb20.auction.exceptions; import javax.ejb.RemoveException; /** * This exception class reports an attempt to remove an auction that has * not been completed */ public class RemoveUnfinishedAuctionException extends RemoveException { public RemoveUnfinishedAuctionException() { } public RemoveUnfinishedAuctionException(String msg) { super(msg); } } You do have to exercise caution in extending the standard application exceptions to make sure the intent of the superclass exception is preserved. CreateEntityException and RemoveUnfinishedAuctionException are valid extensions because they are only intended to be used to report creation and removal errors. That's an important distinction to make. Instead of RemoveUnfinishedAuctionException , you could have declared an InvalidAuctionStateException that extends RemoveException . This exception would still provide useful information in the event of a failure during a remove, but it would be tempting to use it in other methods to report invalid state errors that have nothing to do with object removal. This would quickly cause problems for callers that received this exception, because a RemoveException wouldn't be a valid generalization of the other state errors. A caller that is catching RemoveException to respond to a failure to delete an auction would be thrown off track by an InvalidAuctionStateException that was meant to report some other type of error. An extension of a standard application exception should only be used to provide a more specific version of that exception. If you need more general exceptions, you should extend them from Exception or one of its subclasses that is a valid superclass for every situation in which it will be used. Another potential benefit of declaring your own exception classes that might not be apparent at first relates to internationalization. As you know, the need for an application to present its user interface in more than one language is becoming a common requirement. In a multitier Internet application, this means that the Web tier must supply display text to an end user based on an appropriate locale. If a particular user speaks English, the Web tier should generate displays in English, but those same displays should be generated in German for a user who's more comfortable with German, and so on. The good news about this from the viewpoint of an EJB developer is that, in a well-designed system, the application tier is decoupled from the presentation logic and is, for the most part, unaffected by this requirement. The application tier has little knowledge of its clients and, in general, doesn't supply any text that's displayed to a user other than that pulled from a database or some other external source. This works fine until a problem occurs within the application tier. Throwing a fairly generic exception with an associated text message is of little use to a Web tier client that supports users who speak languages other than the one used by the EJB developer who wrote the exception-throwing code. You could take measures to pull the text used for exception messages from resource bundles on the application tier, but worrying about how information is presented to end users isn't often served best by the application tier. A potentially better approach is to be sure that the error information supplied to the Web tier is precise enough to allow that tier to provide a meaningful message to the user. An error within the application tier likely needs to be reported to a user using a higher-level, less technical description anyway, so attempting to build such a message on the application tier has limited benefit. If you instead declare custom application exception classes, your enterprise beans can throw meaningful exceptions to the Web tier that can be reported to users appropriately. As illustrated by Listing 13.2, a RemoveUnfinishedAuctionException would be much easier for the Web tier to interpret and relay to a user than a generic RemoveException with an embedded text message describing the nature of the problem. |