1600 PMP mock questions 1400 CAPM mock questions 800 SCJP 6 mock questions 600 OCAJP 7 mock questions 590 OCPJP 7 mock questions 556 SCWCD 5 mock questions 500 OCEJWCD 6 mock questions pdfDownload (java,struts, hibernet etc)

Java Tutorials

Exception Handling Basics

Chapter 18

Exception Handling

In this chapter:

1. You will learn about basics of Exception Handling

2. You will understand the exception handling from servlet specification perspective

3. You will understand exception handling facilities in Struts1.1

4. We will develop a simple yet robust utility to log exceptions

5. We will cover strategies to centralize logging in production environments




Exception handling is very crucial part often overlooked in web application development that has ramifications far beyond deployment. You know how to handle exceptions using the built-in Java construct to catch one and handle it appropriately. But what is appropriate? The basic rationale behind exception handling is to catch errors and report them. What is the level of detail needed in reporting the exception? How should the user be notified of the exception? How should customer support handle problem reports and track and trace the exception from the logs? As a developer where do you handle the exceptions? These are some of the major questions we will answer in this chapter first from a generic way and then as applicable to Struts applications.

Under normal circumstances when you catch the exception in a method, you print the stack trace using the printStacktrace() method or declare the method to throw the exception. In a production system, when an exception is thrown it's likely that the system is unable to process end user?s request. When such an exception occurs, the end user normally expects the following:

A message indicating that an error has occurred

A unique error identifier that he can use while reporting it to customer support.

Quick resolution of the problem.

The customer support should have access to back-end mechanisms to resolve the problem. The customer service team should, for example, receive immediate error notification, so that the service representative is aware of the problem before the customer calls for resolution. Furthermore, the service representative should be able to use the unique error identifier (reported by the user) to lookup the production log files for quick identification of the problem ? preferably up to the exact line number (or at least the exact method). In order to provide both the end user and the support team with the tools and services they need, you as a developer must have a clear picture, as you are building a system, of everything that can go wrong with it once it is deployed.

18.1 Exception Handling Basics

It is common usage by the developers to put System.out.println() to track the exception and flow through the code. While they come in handy, they have to be avoided due to the following reasons:



1. System.out.println is expensive. These calls are synchronized for the duration of disk I/O, which significantly slows throughput.

2. By default, stack traces are logged to the console. But browsing the console for an exception trace isn't feasible in a production system.

3. In addition, they aren't guaranteed to show up in the production system, because system administrators can map System.out and System.errs to ' ' [>nul] on NT and dev/nul on UNIX. Moreover, if you're running the J2EE app server as an NT service, you won't even have a console.

4. Even if you redirect the console log to an output file, chances are that the file will be overwritten when the production J2EE app servers are restarted.

5. Using System.out.println during testing and then removing them before production isn't an elegant solution either, because doing so means your production code will not function the same as your test code.



What you need is a mechanism to declaratively control logging so that your test code and your production code are the same, and performance overhead incurred in production is minimal when logging is declaratively turned off. The obvious solution here is to use a logging utility. It is pretty customary these days to use a utility like Log4J (http://jakarta.apache.org/log4j) for logging. With the right coding conventions in place, a logging utility will pretty much take care of recording any type of messages, whether a system error or some warning. However it is up to you as a developer to make the best use of the utilities. It requires a lot of forethought to handle exceptions effectively. In this chapter we will use Log4J to log exceptions effectively. Hence we will review Log4J before proceeding to look at some commonly accepted principles of Exception handling in Java.

18.2 Log4J crash course

Log4J is the logging implementation available from Apache?s Jakarta project and has been around long before JDK Logging appeared and quite naturally has a larger developer base. Lot of material is freely available online if you want to dig deeper into Log4J and we have held back from such a detailed treatment here. As with any Logging mechanisms, this library provides powerful capabilities to declaratively control logging and the level of logging.

In Log4J, all the logging occurs through the Logger class in org.apache.log4j package. The Logger class supports five levels for logging. They are FATAL, ERROR, WARNING, INFO, DEBUG. Without Log4J, you would perhaps use a Boolean flag to control the logging. With such a boolean flag, there are only two states ? logging or no logging. In Log4J the levels are defined to fine tune the amount of logging. Here is how you would user the Log4J.



Logger logger = Logger.getLogger (?foo.bar?);

logger.debug (?This is a debug message?);



The code above first obtains the Logger instance named foo.bar and logs a message at DEBUG level. You can declaratively turn off the logging for messages at lower level than WARNING. This means the messages logged at INFO and DEBUG level will not be logged.

Logged messages always end up in a destination like file, database table etc. The destination of the log message is specified using the Appender. The Appender can represent a file, console, email address or as exotic as a JMS channel. If you need a destination that is not supported by the classes out of the box you can write a new class that implements the Appender interface. Appenders can be configured at startup in a variety of ways. One way to configure them is through an XML file. A XML file is shown below.



<appender name="Mybank-Warn"

class="org.apache.log4j.FileAppender">

<param name="Threshold" value="WARN" />

<param name="File" value="./logs/mybank-warnings.log" />

<param name="Append" value="false" />

<layout class="org.apache.log4j.PatternLayout">

<param name="ConversionPattern"

value="%d [%x][%t] %-5p %c{2} - %m%n"/>

</layout>

</appender>



<category name="foo.bar" additivity="false">

<appender-ref ref="Mybank-Warn" />

<appender-ref ref="Developer-Console" />

</category>



The above XML when translated to simple English reads as follows: The Appender named Mybank-Warn logs the messages to a file mybank-warnings.log. Only messages with a threshold of WARN or higher are logged. The format of the message is as specified by the PatternLayout.

The format of the output message is specified using Layout. Standard classes for specifying the layout like PatternLayout are used most of the times and the format is declaratively specified using symbols like %d which instructs Log4J to include date time in the log and %m ? the actual message itself and so on.

As you saw earlier, the logging is performed through a named Logger instance. If you are wondering how the Logger would know which Appender to log to, it is the <category> element in the above XML that provides the link between the two. The Logger uses the <category> setting in the XML to get this information. The <category> in the above XML is called foo.bar. Recall that we tried to log using a Logger named foo.bar. The foo.bar Logger gets the FileAppender Mybank-Warn appender through the foo.bar category setting in the XML. And then the messages end up in the file mybank-warnings.log.

There can be more than one appenders associated with a category. This implies that the messages logged with a Logger can potentially end up in multiple locations if needed.

18.3 Principles of Exception Handling

The following are some of the generally accepted principles of exception handling:

6. If you can't handle an exception, don't catch it.

7. Catch an exception as close as possible to its source.

8. If you catch an exception, don't swallow it.

9. Log an exception where you catch it, unless you plan to re-throw it.

10. Preserve the stack trace when you re-throw the exception by wrapping the original exception in the new one.

11. Use as many typed exceptions as you need, particularly for application exceptions. Do not just use java.lang.Exception every time you need to declare a throws clause. By fine graining the throws clause, it is self-documenting and becomes evident to the caller that different exceptions have to be handled.

12. If you programming application logic, use unchecked exceptions to indicate an error from which the user cannot recover. If you are creating third party libraries to be used by other developers, use checked exceptions for unrecoverable errors too.

13. Never throw unchecked exceptions in your methods just because it clutters the method signature. There are some scenarios where this is good (For e.g. EJB Interface/Implementations, where unchecked exceptions alter the bean behavior in terms of transaction commit and rollback), but otherwise this is not a good practice.

14. Throw Application Exceptions as Unchecked Exceptions and Unrecoverable System exceptions as unchecked exceptions.

15. Structure your methods according to how fine-grained your exception handling must be.

Principle 1 is obviously in conflict with 2. The practical solution is a trade-off between how close to the source you catch an exception and how far you let it fall before you've completely lost the intent or content of the original exception.

Principles 3, 4, and 5 is a problems developers face when they catch an exception, but do not know how to handle it and hence throw a new exception of same or different type. When this happens, the original exception?s stack trace is lost. Listing 18.1 shows such a scenario. The SQLException is caught on Line 15 and re-thrown as a application specific UpdateException on Line 16. In the process, the stacktrace with valuable info about the SQLException is lost. Thus the developer can only trace back to Line 16 where the UpdateException is thrown and not beyond that (This is the best case scenario with compiler debug flags turned on. If hotspot compiler was used, the stacktrace would only have the method name without any line number). Listing 18.2 shows almost similar scenario, but the actual exception is logged to the console. This is not good choice and sometimes not feasible because of reasons cited earlier in this section.



Listing 18.1 Losing Exception stack trace

10 public void updateDetails(CustomerInfo info)

throws UpdateException

11 {

12 try {

13 CustomerDAO custDAO = CustDAOFactory.getCustDAO();

14 custDAO.update(info);

15 } catch (SQLException e) {

16 throw new UpdateException(?Details cannot be updated?);

17 }

18 }










Listing 18.2 Losing Exception stack trace

public void updateDetails(CustomerInfo info)

throws UpdateException

{

try {

CustomerDAO custDAO = CustDAOFactory.getCustDAO();

custDAO.update(info);

} catch (SQLException e) {

e.printStackTrace();

throw new UpdateException(?Details cannot be updated?);

}

}




Listing 18.3 Preserving Exception stack trace

public void updateDetails(CustomerInfo info)

throws UpdateException

{

try {

CustomerDAO custDAO = CustDAOFactory.getCustDAO();

custDAO.update(info);

} catch (SQLException e) {

throw new UpdateException(e);

}

}




A better approach is shown in Listing 18.3. Here, the SQLException is wrapped in the UpdateException. The caller of the updateDetails can catch the UpdateException, and get the knowledge of the embedded SQLException.

Principles 7, 8 and 9 in the above list pertain to the discussion of using checked v/s unchecked exceptions. Checked Exceptions are those that extend java.lang.Exception. If your method throws checked exceptions, then the caller is forced to catch these exceptions at compile time or declare in the throws clause of the method. On the other hand, unchecked exceptions are those that extend java.lang.RuntimeException, generally referred to as runtime exceptions. If your method throws a runtime exception, the caller of the method is not forced to catch the exception or add it to the method signature at compile time.

A rule of thumb is to model application exceptions as checked exceptions and system exceptions as unchecked exceptions. The code below is an example of application exception.



if (withDrawalAmt > accountBalance)

{

throw new NotEnoughBalanceException(

?Your account does not have enough balance?);

}



When the account does not have enough balance for requested withdrawal amount, the user gets a NotEnoughBalanceException. The user can decide to withdraw lesser amount. Notice that the application exception is not logged. In case of the application exceptions, the developer explicitly throws them in the code and the intent is very clear. Hence there is no need for content (log or stack trace).

Principle 10 is about the use of debug flags with compilation. At compile time it is possible to tell the JVM to ignore line number information. The byte code without the line information are optimized for hotspot or server mode and the recommended way of deployment for production systems. In such cases, the exception stack traces do not provide the line number information. You can overcome this handicap by refactoring your code during development time and creating smaller and modular methods, so that guessing the line numbers for the exceptions is relatively easier.

18.4 The cost of exception handling

In the example used earlier to illustrate application exceptions, we are checking if withdrawal amount is greater than balance to throw the exception. This is not something you should be doing every time. Exceptions are expensive and should be used exceptionally. In order top understand some of the issues involved; let us look at the mechanism used by the Java Virtual Machine (JVM) to handle the exceptions. The JVM maintains a method invocation stack containing all the methods that have been invoked by the current thread in the reverse order of invocation. In other words, the first method invoked by the thread is at the bottom of the stack and the current method is at the top. Actually it is not the actual method that is present in the stack. Instead a stack frame representing the method is added to the stack. The stack frame contains the method?s parameters, return value, local variables and JVM specific information. When the exception is thrown in a method at the top of the stack, code execution stops and the JVM takes over. The JVM searches the current method for a catch clause for the exception thrown or one of the parent classes of the thrown exception. If one is not found, then the JVM pops the current stack frame and inspects the calling method (the next method in the stack), for the catch clause for the exception or its parents. The process continues until the bottom of the stack is reached. In summary, it requires a lot of time and effort on the part of JVM.

Exceptions should be thrown only when there is no meaningful way of handling the situation. If these situations (conditions) can be handled programmatically in a meaningful manner, then throwing exceptions should be avoided. For instance if it is possible to handle the problem of withdrawal amount exceeding the balance in some other way, it has to chosen over throwing an application exception.

Examples of SystemException can be a ConfigurationException, which might indicate that the data load during start up failed. There is really nothing a user or even the customer support could do about it, except to correct the problem and restart the server. Hence it qualifies as a System exception and can be modeled as runtime exception.

Certain exceptions like SQLException might indicate a system error or application problem depending on the case. In either case, it makes a lot of sense to model SQLException as a checked exception because that is not thrown from your application logic. Rather it is thrown in the third party library and the library developer wants you to explicitly handle such a scenario.

18.5 JDK 1.4 and exception handling

If you are modeling the UpdateException as a unchecked exception, you will have to extend from RuntimeException. In addition if you are using JDK1.3.x and lower, you will also provide the wrapped exception attribute in your own exception. JDK1.4 onwards, you can wrap the ?causative exception? in the parent class RuntimeException as a java.lang.Throwable attribute thus allowing you to carry around the ?causative exception?. For e.g. SQLException is the ?causative exception? in Listing 18.3. In the Throwable class there is a new method getCause to get the cause of any exception which returns the wrapped exception if exists. This can result in an exception chain since the cause itself can have a cause. Prior to 1.4 Exception classes had their own non-standard exception chaining mechanisms. For instance, RemoteException was used to carry the actual exception across different JVMs or from EJB tier to web tier. As of 1.4, all of these classes have been retrofitted to use the standard exception chaining mechanism.

Additional exception handling features in JDK1.4 include programmatic access to stack trace. This is a boon for real time error monitoring and alert facilities. Often these systems need to manually parse the stack dump for keywords. This is been made much easier. One can invoke getStackTrace method on the Exception (or Throwable) and get an array of StackTraceElements returned. Each StackTraceElement provides the following methods.

getClassName

getFileName

getLineNumber

getMethodName

isNativeMethod

By calling the above methods, you can display the stack trace in any format you like. In addition, elegant error monitoring systems can be written. For instance, the error monitoring system should alert the appropriate support team for the sub system by intelligently analyzing the stack trace. This has been made easier. The following code snippet can be used with JDK 1.4



StackTraceElement elements[] = e.getStackTrace();

for (int i=0, n=elements.length; i<n; i++) {

if ( elements[i].getClassName.equals("LegacyAccessEJB?)

&& elements[i].getMethodName().equals(?invokeCOBOL?)

{

//Alert the COBOL support team

}

}

This code snippet checks if the exception originated in LegacyAccessEJB during invoking a method named ?invokeCOBOL?, it will alert the COBOL support team. Obviously the decision tree is not as simple as shown, but at least it removes the headache of parsing the trace for the same information.

18.6 Exception handling in Servlet and JSP specifications

In the previous section, you looked at the general principles in exception handling without a J2EE tilt. In this section, we will cover what servlet specification has to say about exception handling. Consider the doGet() method signature in a HttpServlet.

public void doGet(HttpServletRequest request,

HttpServletResponse response) throws

ServletException, IOException

The above method signature implies that a Servlet or a JSP (and finally a web application) is only allowed to throw

ServletException or its subclasses

IOException or its subclasses

RuntimeExceptions



All other checked exceptions have to be handled in the Servlet/JSP code in one of the following manner:

Catch the checked exception and log the error message and (or) take any business related action.

Wrap the exception in a ServletException and throw the ServletException. (ServletException has overloaded constructors to wrap the actual exception.)

Servlet specification provides exception-handling support through web.xml. In web.xml, you can declare <error-page> to handle exceptions that are thrown but not caught.



<error-page>

<exception-type>UnhandledException</exception-type>

<location>UnhandledException.jsp</location>

</error-page>



What this means is that if an exception of type UnhandledException is thrown from your web application but not caught anywhere, then the user gets to see the UnhandledException.jsp. This works well for ServletException, IOException, RuntimeException and their subclasses.

If the UnhandledException is a subclass of ServletException and none of the error-page declaration containing exception-type fit the class hierarchy of the thrown exception, then the Servlet container gets the wrapped exception using the ServletException.getRootCause method. Then the container attempts again to match the error-page declaration. This approach works well if the UnhandledException is not a subclass of ServletException or IOException (but is a checked exception). You have to throw a ServletException or its subclass by wrapping the UnhandledException in it and the servlet container does rest of the magic.

There are times when the user cannot see a page due to incorrect access rights or the page simply does not exist. The Servlet sends an error response with an appropriate HTTP error code. For instance, 404 corresponds to Page not found, 500 corresponds to Internal Server Error and so on. You can also assign JSPs for default HTTP error code as follows.



<error-page>

<error-code>404</error-code>

<location>exceptions/Page404.jsp</location>

</error-page>



Similarly, exceptions can occur in the JSPs in scriptlets and custom tags. These can throw runtime exceptions. In addition scriptlets can throw ServletException and IOException since a JSP gets translated into the body of _jspService() method and the signature of the _jspService() method is same as doGet().



public void _jspService(HttpServletRequest request,

HttpServletResponse response) throws

ServletException, IOException



Tags however throw JspException in their tag callback methods (doStartTag(), doEndTag() and so on). JspException is a direct subclass of java.lang.Exception and has no relationship with ServletException or IOException. The _jspService() method is container dependent but its contract is to catch all those exceptions and forward the request to the errorPage specified by the JSP. Hence it is a best practice to assign error pages in JSPs with the declarative: <%@ page errorPage="/error.jsp" %>

When forwarding to the exception page as specified by errorPage setting shown above, the exception describing the error is stored as request attribute with the key ?javax.servlet.jsp.JspException?. If the JSP assigned to handle the exceptions has the directive <%@ page isErrorPage="true" %> at the top of their page, then the exception is provided as the implicit scripting variable named exception.

18.7 Exception handling ? Struts way

ServletException, IOException, RuntimeException and their sub classes can be declaratively mapped to appropriate JSP files through the web.xml settings. What about the other Exceptions? Fortunately since Struts1.1, you can assign JSP files for other checked exceptions too. Let us start by examining the features in Struts 1.1 for exception handling.

Declarative exception handling

Consider the method signature for the execute method in the Struts Action class.



public ActionForward execute(ActionMapping mapping,

ActionForm form,

HttpServletRequest request,

HttpServletResponse response)

throws java.lang.Exception



The execute() method has java.lang.Exception in its throws clause. Hence you don?t have to handle the exceptions explicitly in Action. You can let them fall through. Consider the execute() method from an Action class.



public ActionForward execute(...) throws java.lang.Exception {

ActionForward nextPage = null;

..

userControllerEJB.createUser(UserInfo info);

..

mapping.findForward(?success?);

}



The execute() method invokes the createUser() method on UserControllerEJB ? a Session EJB that is responsible for creating the users. The createUser() method throws two Exceptions ? RemoteException and DuplicateUserException. If the user cannot be created because another user with same id exists, then the Session EJB throws DuplicateUserException. A RemoteException is thrown if the user cannot be created because of problems in looking up or creating the Session EJB. If everything goes fine, then the user is forwarded to the ActionForward identified by success. However we have made no attempt to catch them and handle. Instead we have deferred their handling to Struts through the declarative exception handling facility.



Listing 18.5 Declarative Exception Handling in Struts

<struts-config>

<action-mappings>

<action

path="/submitCustomerForm"

type="mybank.example.CustomerAction"

name="customerForm"

scope="request"

input="/CustomerDetails.jsp">



<exception

key="database.error.duplicate"

path="/UserExists.jsp"

type="mybank.account.DuplicateUserException"/>



<exception

key="rmi.error"

type="java.rmi.RemoteException"

path="/rmierror.jsp"/>



</action>

</action-mappings>

</struts-config>




Listing 18.5 shows the Struts Config file with declarative exception handling for the two exceptions ? DuplicateUserexception and RemoteException. For each exception, an <exception> element is defined in the action mapping. The path attribute in the <exception> element specifies the page shown to the user upon that exception. For instance, if a DuplicateUserException is thrown when submitting the modified user profile, the controller will forward control to the UserExists.jsp page. The key attribute is used to retrieve the error message template from the associated resource bundle. Since the <exception> is local to the action mapping, it applies only for that action invocation. As you might have already notice the J2EE and Struts way of declaratively handling exceptions are complementary to one another.

In the Listing 18.5, the declarative exception handling was local to the CustomerAction. You can add global declarative exception handling too. For instance, if you want to handle the RemoteException in the same way across the board, use the following approach:

<struts-config>

<global-exceptions>

<exception

key="rmi.error"

type="java.rmi.RemoteException"

path="/rmierror.jsp"/>

</global-exceptions>

</struts-config>

Before forwarding to the page indicated in the <exception> element, Struts sets the exception as a request attribute with name org.apache.struts.action.EXCEPTION. (This is the value of Globals.EXCEPTION_KEY. Globals is a Java class in org.apache.struts package). The exception can be retrieved in the error page by using the method: request.getAttribute(Globals.EXCEPTION_KEY).

Using the ExceptionHandler

Apart from key, type and path, the <exception> element also takes several optional attributes of which handler is a significant one. It is the fully qualified class name of the exception handler for that exception. By default org.apache.struts.action.ExceptionHandler is the class used. You can create a custom handler by extending the ExceptionHandler and overriding the execute() method. The execute() method has the following signature:

public ActionForward execute(Exception ex, ExceptionConfig ae,

ActionMapping mapping, ActionForm formInstance,

HttpServletRequest request,

HttpServletResponse response) throws ServletException



To understand the ExceptionHandler, you have to understand the RequestProcessor workings on exception. As it does everything else, RequestProcessor invokes the execute() method on the Action instance. Hence it is natural that the exception thrown in the execute() is caught by the RequestProcessor. On receiving the exception, here is what the RequestProcessor does:

It checks to see if the exception has an associated <exception> declaration either in local or global scope.

If none exists, then the exception is:

Thrown AS IS if it is ServletException, IOException or their subclasses.

Wrapped in a ServletException and thrown if the above criteria is not satisfied.

If there is a <exception> element declared then it retrieves the handler class, instantiates it and invokes execute() method in it. The default exception handler returns the path attribute of the <exception> element as an ActionForward.

As you will see later in this section, you can use a custom Exception Handler to centralize exception logging in the web tier.

When not to use declarative exception handling

Very frequently you would like to generate an ActionError and display it to the user instead of an exception. Let us look back at Listing 18.5 again for a moment. When RemoteException is thrown, the user sees rmierror.jsp. This makes sense since RemoteException is tantamount to a system exception and the only thing you can do is to ask the user to start all over again. However, it does not make sense to ask the user to start all over when DuplicateUserException is thrown since this is an application exception from which the user has a recovery path. A better option is to show this as an ActionError and give the user a chance to change the user id. For situations like this, you have to resort to programmatic exception handling.

Listing 18.6 shows the execute() method with programmatic exception handling. It catches the DuplicateUserException and creates an ActionErrors object to hold the error. The ActionErrors is set into the HTTP request as an attribute and then the same Form is shown back. The last part of showing the same page is achieved by the line mapping.getInput(). In this case you have to remove the declarative exception handling from Struts config file since it is being explicitly handled in the code.

If you use declarative exception handling, the default ExceptionHandler will still generate an ActionErrors object. However, the ActionErrors is associated with the page rather than a particular field. If you don?t have this requirement, declarative exception handling is preferred over programmatic exception handling. Just set the initial JSP as the path for the <exception> and use <html:errors/> on the JSP and you get the exception as if it was an ActionError without any effort from your side.



Listing 18.6 Alternative to declarative exception handling

public ActionForward execute(... ...) throws java.lang.Exception {

ActionForward nextPage = null;

try {

..

..

userControllerEJB.createUser(UserInfo info);

..

mapping.findForward(?success?);

}

catch (DuplicateUserException due)

{

ActionErrors errors = new ActionErrors();

ActionError error = new ActionError(?userid.taken?,

due.getUserId());

errors.add(?userid?, error);



// This saves the ActionErrors in the request attribute

// with the key Action.ERROR_KEY

saveErrors(request, errors);

nextPage = mapping.getInput();

}

return nextPage;

}


Exception handling and I18N

Another important matter of concern with exception handling is I18N. Even though the exception logging can occur in the language of your operating system, the messages should still be displayed in the language of the user?s choice. This is not much of a concern is the message is generic. For instance, in Listing 18.5, the message shown to the user on RemoteException is identified by the key rmi.error. The key can have a generic message in the resource bundle. However the problem starts when the message has to get specific or the message requires replacement values. There are two possible solutions to this problem neither of which is ideal.

Here is the first approach: If you want to keep the internationalization in the web tier, then the specific exceptions from the server side should encapsulate the resource bundle keys and some (if not all) replacement values in them. The key and the replacement values can be exposed through getter methods on the exception class. This approach makes the server side code dependent on the web tier resource bundle. This also requires a programmatic exception handling since you have to pass appropriate replacement values to the ActionError.

The second approach is to send the user?s Locale as one of the arguments to the server side and let the server side generate the entire message. This removes the server?s dependency on the web tier code, but requires the Locale to be sent as a argument on every method call to the server.

18.8 Logging Exceptions

It is common knowledge that exceptions can occur anywhere ? web-tier, ejb-tier, database. Wherever they occur, they must be caught and logged with appropriate context. It makes more sense to handle a lot, if not all of the exceptions originating in the ejb tier and database tier on the client side in the web tier. Why should exception logging take place on the client side?



Listing 18.7 Enumeration class for Exception Category

public class ExceptionCategory implements java.io.Serializable {



public static final ExceptionCategory INFO =

new ExceptionCategory(0);

public static final ExceptionCategory WARNING =

new ExceptionCategory(1);

public static final ExceptionCategory GENERAL_PROBLEM =

new ExceptionCategory(2);

public static final ExceptionCategory DATA_PROBLEM =

new ExceptionCategory(3);

public static final ExceptionCategory CONFIG_PROBLEM =

new ExceptionCategory(4);

public static final ExceptionCategory FATAL =

new ExceptionCategory(5);



private int type;



private ExceptionCategory(int aType) {

this.type = aType;

}

}




First, the control hasn't passed outside of the application server yet. (Assuming both the web tier and ejb tier do not belong to disparate entities). The so-called client tier, which is composed of JSP pages, servlets and their helper classes, runs on the J2EE application server itself. Second, the classes in a well-designed web tier have a hierarchy (for example, hierarchy in the Business Delegate classes, Intercepting Filter classes, JSP base class, or in the Struts Action classes) or single point of invocation in the form of a FrontController servlet (Business Delegate, Intercepting Filter and Front Controller are Core J2EE Patterns. Refer to Sun blueprints for more details). The base classes of these hierarchies or the central point in FrontController classes can contain the exception logging code. In the case of session EJB-based logging, each of the methods in the EJB component must have logging code. As the business logic grows, so will the number of session EJB methods, and so will the amount of logging code. A web tier system will require less logging code. You should consider this option if you have co-located web tier and EJB tiers and you don't have a requirement to support any other type of client.

To develop a full fledged exception handling strategy let us start with a simple class shown in Listing 18.7. This class, ExceptionCategory categorizes the exceptions into INFO, WARNING, ERROR and FATAL. This identification helps us when the notification of Support personnel depends on the severity of the exception.



Listing 18.8 Exception Info class

public class ExceptionInfo implements java.io.Serializable {

private ExceptionCategory exceptionCategory;

private String errorCode;

private String exceptionID;

private boolean logged;



public ExceptionInfo(ExceptionCategory aCategory,

String aErrorCode) {

this.exceptionCategory = aCategory;

this.errorCode = aErrorCode;

this.logged = false;

this.exceptionID =

UniqueIDGeneratorFactory.

getUniqueIDGenerator().getUniqueID();

}

}




The next class to look at is the ExceptionInfo class as shown in Listing 18.8 This class provides information about the Exception as the name indicates. Apart from the ExceptionCategory, this class also holds a unique id associated with the Exception and a boolean indicating if the exception has been already logged. The UniqueIDGeneratorFactory is a factory class that returns a UniqueIDGenerator. UniqueIDGenerator is represented by an interface IUniqueIDGenerator. This interface has just one method ? getUniqueID(). Listing 18.9 shows a simple Unique ID Generator implementation IP Address and time.



Listing 18.9 Simple Unique ID Generator

public class UniqueIDGeneratorDefaultImpl

implements IUniqueIDGenerator

{



private static IUniqueIDGenerator instance =

new UniqueIDGeneratorDefaultImpl();



private long counter = 0;



public String getUniqueID() throws UniqueIDGeneratorException

{

String exceptionID = null;

try {

exceptionID = InetAddress.getLocalHost().getHostName();

} catch(UnknownHostException ue) {

throw new UniqueIDGeneratorException(ue);

}



exceptionID = exceptionID +

System.currentTimeMillis() +

counter++;

return exceptionID;

}

}




Listing 18.10 MybankException class

public abstract class MybankException extends Exception {



private ExceptionInfo exceptionInfo;



public MybankException(ExceptionInfo aInfo) {

super();

this.exceptionInfo = aInfo;

}

}




And finally Listing 18.10 shows the actual Exception class. This is the base class for all the checked exceptions originating in MyBank. It is always better to have a base class for all exceptions originating in a system and then create new types as required. In this way, you can decide how much fine grained you want the catch exception blocks to be. Similarly you can have a base class for all unchecked exceptions thrown from system. Listing 18.11 shows such a class.



Listing 18.11 MybankRuntimeException class

public abstract class MybankRuntimeException extends Exception {



private ExceptionInfo exceptionInfo;

private Throwable wrappedException;



public MybankException(ExceptionInfo aInfo,

Throwable aWrappedException) {

super();

this.exceptionInfo = aInfo;

this.wrappedException = aWrappedException;

}

}




Notice that MybankRuntimeException has only one constructor that takes both ExceptionInfo and a Throwable. This is because if someone is explicitly throwing a runtime exception from his or her code, it is probably because a system error or serious unrecoverable problem has occurred. We want to get hold of the actual cause of the problem and log it. By enforcing development time disciplines like this, one can decrease the chances of exceptions in the system without a context.

Finally we also need to look at the actual Logging utility ? a stack trace printing utility shown in Listing 18.12. The default printStackTrace() method in java.lang.Throwable logs an error message to the System.err. Throwable also has an overloaded printStackTrace() method to log to a PrintWriter or a PrintStream. The above method in StackTraceUtil wraps the StringWriter within a PrintWriter. When the PrintWriter contains the stack trace, it simply calls toString() on the StringWriter to get a String representation of the stack trace.

The StackTraceUtil class has two overloaded methods ? getStackTraceAsString() ? One of them takes the MybankException as the parameter, the other takes Throwable as the parameter. All exceptions of type MybankException already have the unique id in-built. For other exceptions, to be logged the unique id has to be explicitly generated. MybankException also has the flag indicating whether the exception has been logged making it easier to prevent multiple logging, as you will see very soon. Other Exceptions don?t have this capability and it is up to the caller program and called to collaborate and ensure that duplicate logging does not happen.



Listing 18.12 Stack Trace printing utility.

public final class StackTraceTool {

private StackTraceTool() {}



public static String getStackTraceAsString(

MybankException exception)

{

String message = " Exception ID : " +

exception.getExceptionInfo().getExceptionID()

+ "\n " + "Message :" + exception.getMessage();

return getStackMessage(message, exception);

}



public static String getStackTraceAsString(Throwable throwable)

{

String message = " Exception ID : " +

UniqueIDGeneratorFactory.getUniqueIDGenerator().getUniqueID()

+ "\n " + "Message :" + exception.getMessage();

return getStackMessage(message, exception);

}



private static String getStackMessage(String message,

Throwable exception)

{

StringWriter sw = new StringWriter();

PrintWriter pw = new PrintWriter(sw);

pw.print(" [ " );

pw.print(exception.getClass().getName());

pw.print(" ] ");

pw.print(message);

exception.printStackTrace(pw);

return sw.toString();

}

}




Armed with these knowledge let us look at a scenario that will lead to duplicate logging in the system when an exception occurs. Consider a case when a method, foo(), in an entity EJB component is accessed in a session EJB method, called bar(). A web-tier client invokes the method bar() on the session EJB component, and also logs the exceptions. If an exception occurs in the entity EJB method foo() when the session EJB method bar() is invoked from the web-tier, the exception will have been logged in three places: first in the entity EJB component, then in the session EJB component, and finally in the web tier.

Fortunately, addressing these problems is fairly easy to do in a generic way. All you need is a mechanism for the caller to:

Access the unique ID

Find out if the exception has already been logged

If the exception has been already logged don?t log it again.

We have already developed the MybankException and ExceptionInfo class that let us check if the exception is already logged. If not logged already, log the exception and set the logged flag to be true. These classes also generate a unique id for every exception. Listing 18.13 shows a sample.



Listing 18.13 Sample Exception Logging

try {

CustomerDAO cDao = CustomerDAOFactory.getDAO();

cDao.createCustomer(CustomerValue);

} catch (CreateException ce) {

//Assume CreateException is a subclass of MybankException

if (! ce.isLogged() ) {

String traceStr = StackTraceTool.getStackTraceAsString(ce);

LogFactory.getLog(getClass().getName()).error(

ce.getUniqueID() + ":" + traceStr);

ce.setLogged(true);

}

throw ce;

}




Listing 18.13 shows the logging scenario when the exception caught is of type MybankException. It is very much a fact that not all of the exceptions thrown by your application are in this hierarchy. Under such conditions it is even more important that the logging is centralized in one place since there is no mechanism to prevent duplicate logging for exceptions outside the MybankException hierarchy. That brings us to the idea of centralized logging. In the beginning of this section we said that it is easy and convenient to log exceptions on web-tier since most of the web-tier classes have a hierarchy. Let us examine this claim in more detail.

18.9 Strategies for centralized logging

In the previous section, we saw how to avoid duplicate logging. But when it comes to the entire application, you also learnt that logging should not only be done once but also centralized for disparate modules of the system if possible. There are various strategies to achieve centralized logging in the web tier. This section will cover those strategies.

Consider the web-tier for MyBank. After the Struts Forms are populated the RequestProcessor invokes the execute method on Action classes. Typically, in the execute method you access enterprise data and business logic in session ejbs and legacy systems. Since you want to decouple your web tier from the business logic implementation technology (EJB for example ? which forces you to catch RemoteException) or the legacy systems, you are most likely to introduce Business Delegates. (Business Delegate is a Core J2EE Pattern). The Business Delegates might throw a variety of exceptions, most of which you want to handle by using the Struts declarative exception handling. When using the declarative exception handling you are most likely to log the exceptions in the JSPs since the control passes out of your code at the end of the execute method. Instead of adding the exception logging code to every JSP declared in Struts Config file, you can create a parent of all the error JSPs and put the logging code in there. Listing 18.14 shows a sample base JSP class.

There is quite a bit going on in Listing 18.14. First the class implements the javax.servlet.jsp.HttpJspPage interface. All the methods in this interface except the _jspService() have concrete implementations. These methods represent the various methods called during the JSP life cycle. In particular you will recognize the service method that is similar to the servlet?s service method. In the course of this method execution, the _jspService() method is also executed. _jspService() method is not implemented by the page author or the developer. Instead it is auto generated by the servlet container during JSP pre-compilation or run time. All the markup, tags and scriptlets contained in the JSP get transformed into Java code and form the gist of the _jspService() method. The page author indicates that the jsp extends from this java class by adding the directive



<%@ page extends="mybank.webtier.MybankBaseErrorJsp" %>



If all of the Error-JSPs extend from this abstract JSP class, centralized logging is achieved. Before you celebrate for having nailed down the problem, shall we remind you that this solution may not work in all servlet containers. The reason for this is JspFactory and PageContext implementations are vendor dependent. Normally the calls for JspFactory.getDefaultFactory() and factory.getPageContext() occur in the auto generated _jspService() method. It is possible that some of the implementations may not initialize these objects we accessed in the service() method until they reach the _jspService() method !







Listing 18.14 Base JSP class for error pages

public abstract class MybankBaseErrorJsp implements HttpJspPage {

private ServletConfig servletConfig;



public ServletConfig getServletConfig() {

return servletConfig;

}



public String getServletInfo() {

return "Base JSP Class for My Bank Error Pages";

}



public void init(ServletConfig config)

throws ServletException {

this.servletConfig = config;

jspInit();

}



public void jspInit() {}

public void jspDestroy() {}



public void service(ServletRequest req, ServletResponse res)

throws ServletException, IOException {

HttpServletRequest request = (HttpServletRequest)req;

HttpServletResponse response = (HttpServletResponse)res;



JspFactory factory = JspFactory.getDefaultFactory();

PageContext pageContext = factory.getPageContext(

this, request, response,

null, // errorPageURL

false, // needsSession

JspWriter.DEFAULT_BUFFER,

true // autoFlush

);

Exception exc = pageContext.getException();

String trace =StackTraceTool.getStackTraceAsString(exc);

Logger.getLogger(getClass().getName()).error(trace);



//proceed with container generated code from here

_jspService(request,response);

}



public abstract void _jspService(HttpServletRequest request,

HttpServletResponse response)

throws ServletException, IOException;

}




Don?t panic. We have an alternate solution, which is less elegant but is guaranteed to work across vendor implementations. Let us create a custom tag to be invoked from all of the Error-JSPs. Listing 18.15 shows the logic for doStartTag() method of this custom tag. You will notice that it is very much similar to the service() method in Listing 18.14. After obtaining the exception object, it is logged by obtaining the Logger from Log4J. Since this tag is invoked within the _jspService() method for the JSP, it is guaranteed to have access to all of the implicit objects including pagecontext and exception in every vendor implementation.



Listing 18.15 Custom Tag for exception logging

public class ExceptionLoggingTag extends TagSupport

{

public int doStartTag() throws ServletException, IOException

{

Exception exc = pageContext.getException();

String trace =StackTraceTool.getStackTraceAsString(exc);

LogFactory.getLog(getClass().getName()).error(trace);



return EVAL_BODY_INCLUDE;

}

}




For those of you who are visualizing the big picture, you will realize that logging from the JSP is not the right way. However there is no ideal way to achieve centralized logging without taking this approach. Each mechanism has its drawbacks and tradeoffs. This is something you will experience whenever design abstractions meet reality.

Until now you have seen a JSP based approach of exception handling and logging. What if you have a requirement to handle the exceptions originating from your application differently? Let us consider the application exceptions from our very own MyBank. The exceptions originating from the MyBank are subclasses of MybankException and MybankRuntimeException. When using Struts as the framework in your web applications, then you will most likely have a base Action class with trivial functionality common to all of the Actions factored out. The base Action class is the ideal location to centralize the special processing for the application exceptions. Listing 18.16 shows a sample base Action, MybankBaseAction for the special processing just mentioned.



Listing 18.16 Mybank Base Action

public class MybankBaseAction extends Action {



public ActionForward execute(ActionMapping mapping,

ActionForm form, HttpServletRequest request,

HttpServletResponse response) throws Exception

{

ActionForward aForward = null;

MybankBaseForm aForm = (MybankBaseForm)form;

try {

preprocess(mapping, aForm, request, response);

aForward = process(mapping, aForm, request, response);

postprocess(mapping, aForm, request, response);

}

catch(MybankException ae) {

//Any Special Processing for Mybank goes here

if (ae.isLogged()) {

String trace = StackTraceTool.getStackMessage(ae);

LogFactory.getLog(getClass().getName()).error(

ae.getUniqueID() + ":" + trace);

ae.setLogged(true);

}

aForward = errorPage;

}

return aForward;

}



protected abstract void preprocess(ActionMapping mapping,

MybankBaseForm form, HttpServletRequest request,

HttpServletResponse response) throws Exception;



protected abstract void process(ActionMapping mapping,

MybankBaseForm form, HttpServletRequest request,

HttpServletResponse response) throws Exception;



protected abstract void postprocess(ActionMapping mapping,

MybankBaseForm form, HttpServletRequest request,

HttpServletResponse response) throws Exception;

}




This class implements the execute method, but defines three abstract methods preprocess(), process() and postprocess() which take the same arguments as the execute() method but are invoked before, during and after the actual processing of the request. In the execute method, the MybankException is caught and any special processing required is done and then re-throw the exception for the declarative exception handling to work or programmatically forward to the relevant error page.

Note that you can achieve the same result by creating a custom exception handler for the MybankException. The custom exception handler?s execute() method will do exactly what the catch block in Listing 18.16 is doing.

18.10 Reporting exceptions

Until now, you have looked at various exception logging strategies. After the exception is logged, there is also a need to report the fatal and serious ones by sending out emails and pager messages to the support team. Several approaches exist and the choices are numerous, but in this chapter we would like to consolidate the logging and error reporting for better coordination and control. For this, let us look at what Log4J has to offer.



Listing 18.17 SMTP Appender setup

<appender name="Mybank-Mail"

class="org.apache.log4j.net.SMTPAppender">

<param name="Threshold" value="ERROR" />

<param name="Subject" value="Mybank Online has problems" />

<param name="From" value="prod-monitor@mybank.com" />



<!-- use commas in value to separate multiple recipients -->

<param name="To" value="prod-support@mybank.com " />



<param name="SMTPHost" value="mail.mybank.com" />

<layout class="org.apache.log4j.PatternLayout">

<param name="ConversionPattern" value="%m" />

</layout>

</appender>




As you already know, Log4J has three main components: Layout, Appender, and Category (also known as Logger). Layout represents the format of the message to be logged. Appender is an alias for the physical location at which the message will be logged. And category is the named entity; you can think of it as a handle for logging. Layouts and Appenders are declared in an XML configuration file. Every category comes with its own layout and Appender definitions. When you get a category and log to it, the message ends up in all the appenders associated with that category, and all those messages will be represented in the layout format specified in the XML configuration file.

Log4J comes with several standard appenders and one of them is called SMTPAppender. By using the SMTPAppender, you can declaratively send email messages when the errors occur in your system. You can configure the SMTPAppender like any other Appender ? in the Log4J configuration file. Listing 18.17 shows a sample setup for SMTPAppender. It takes a Threshold, beyond which the Appender is operational, a subject for the email, the From and To addresses, SMTP server name and the pattern for the email message body.

You can set up the category for the above SMTPAppender as

<category name=?com.mybank.webtier.action? additivity=?false?>

<priority value=?ERROR?/>

<appender-ref ref=?Mybank-Mail?/>

<appender-ref ref=?Mybank-ErrorLog?/>

</category>

With this setup all the exceptions that are caught in the base Struts Action ? MybankBaseAction are reported to email address prod-support@mybank.com. This is because the category name is identified by the package name for MybankBaseAction and while logging in Listing 18.16, we used the category whose name is same as the fully qualified class name for MybankBaseAction which happens to be com.mybank.webtier.action.MybankBaseAction. The email address prod-support@mybank.com is generally an email group configured in the enterprise mail server to include several individual recipients. Alternatively, you can explicitly specify multiple recipients in the To param in Listing 18.17 with commas separating the recipients. You can take a similar approach if you are logging in the Base JSP class of Listing 18.14 or the custom tag class of 18.15. But what if you are logging the exception using a scriptlet in the JSP. Although this approach is not recommended, suppose that you already have it in place and want to retrofit the Log4J email feature. In this case, you still can setup the appender as in Listing 18.17. But what about the jsp? What is the fully qualified class name for the JSP? This depends on the vendor. For instance, in weblogic a JSP in a directory called mortgage will reside in package named jsp_servlet.mortgage. Accordingly, for WebLogic, you can setup the category as follows



<category name=?jsp_servlet.mortgage? additivity=?false?>

<priority value=?ERROR?/>

<appender-ref ref=?Mybank-Mail?/>

<appender-ref ref=?Mybank-ErrorLog?/>

</category>

Note that this setting is vendor specific and may not be portable to other application servers. But this is a minor change and should not be a problem if you decide to port to another application server say JBoss.

If you are wondering, ?Email messages are all fine. How do I send pager beeps?? The quick answer is ?No problem?. Pagers have email addresses too. You can ask your service provider to associate the email address with the pager. Telecommunications companies and providers can use JavaMail API to implement a PAGER transport protocol that sends email messages to alphanumeric pagers. Similar approach works for Short Message Service (SMS) too since you can email a SMS device.

18.11 Summary

In development environments, the developer can go back, fix the root cause of the exception and move on. Not so in production systems. Exception handling is a very crucial part of enterprise applications. It is the key to quick response from the support team and resolution of the problems in working systems. A delayed or no resolution can leave the customer frustrated. In the internet world, where the competitor is just a click away, the importance of exception handling, logging, reporting and resolving cannot be stressed enough. This chapter gave you the insights into various gotchas on your way, common mistakes and strategies to address them from a web tier perspective.




Reviews and Comments


PMP, CAPM, PMI is a registered certification mark of the Project Management Institute, Inc

Copyright © www.techfaq360.com 2016


About US | Contact US | Privacy Policy | Terms and Conditions  | Website disclaimer  | Cancellation and Refund Policy  | Shipping & Delivery Policy