Chapter 9. Exceptions and Logging

In the previous chapter, we were introduced to interceptors, one of the most powerful aspects of Struts 2. In this chapter, we'll cover something much more mundane, but critical—exception handling and logging. We will explore:

  • The Struts 2 declarative exception handling mechanism

  • Some general exception handling practices that will help us create robust applications

  • Logging configuration and practices to help us take a peek inside our application's execution, and help determine what went wrong when errors occur

Handling exceptions in Struts 2

Struts 2 provides a declarative exception handling mechanism that can be configured globally (for an entire package), or for a specific action. This capability can reduce the amount of exception handling code necessary inside actions under some circumstances, most notably when underlying systems, such as our services, throw runtime exceptions (exceptions that we don't need to wrap in a try/catch or declare that a method throws).

To sum it up, we can map exception classes to Struts 2 results.

Note

The exception handling mechanism depends on the exception interceptor we saw in the previous chapter. If we modify our interceptor stack, we must keep that in mind. In general, removing the exception interceptor isn't preferred.

Global exception mappings

Setting up a global exception handler result is as easy as adding a global exception mapping element to a Struts 2 configuration file package definition and configuring its result. For example, to catch generic runtime exceptions, we could add the following:

<global-exception-mappings>
<exception-mapping result="runtime"
exception="java.lang.RuntimeException"/>
</global-exception-mappings>

This means that if a java.lang.RuntimeException (or a subclass) is thrown, the framework will take us to the runtime result. The runtime result may be declared in a<global-results> element, an action configuration, or both. The most specific result will be used. This implies that an action's result configuration might take precedence over a global exception mapping.

For example, consider the global exception mapping shown in the previous code snippet. If we configure an action as follows, and a RuntimeException is thrown, we'll see the locally defined runtime result, even if there is a global runtime result.

<action name="except1"
class="com.packt.s2wad.ch09.examples.exceptions.Except1">
<result name="runtime">
/WEB-INF/jsps/ch9/exceptions/except1-runtime.jsp
</result>
...

This can occasionally lead to confusion if a result name happens to collide with a result used for an exception. However, this can happen with global results anyway (a case where a naming convention for global results can be handy).

Action-specific exception mappings

In addition to overriding the result used for an exception mapping, we can also override a global exception mapping on a per-action basis. For example, if an action needs to use a result named runtime2 as the destination of a RuntimeException, we can configure an exception mapping specific to that action.

<action name="except2"
class="com.packt.s2wad.ch09.examples.exceptions.Except1">
<exception-mapping result="runtime2"
exception="java.lang.RuntimeException"/>
...

As with our earlier examples, the runtime2 result may be configured either as a global result or as an action-specific result.

Accessing the exception

We have many options regarding how to handle exceptions. We can show the user a generic "Something horrible has happened!" page, we can take the user back and allow them to retry the operation or refill the input form, and so on. The appropriate course of action depends on the application and, most likely, on the type of exception.

We can display exception-specific information as well. The exception interceptor pushes an exception encapsulation object onto the stack with exception and exceptionStack properties. While the stack trace is probably not appropriate for user-level error pages, the exception can be used to help create a useful error message, provide I18N property keys for messages (or values used in messages), suggest possible remedies, and so on.

The simplest example of accessing the exception property from our JSP is to simply display the exception message. For example, if we threw a RuntimeException, we might create it as follows:

throw new
RuntimeException("Runtime thrown from ThrowingAction");

Our exception result page, then, could access the message using the usual property tag (or JSTL, if we're taking advantage of Struts 2's custom request processor):

<s:property value="exception.message"/>

The underlying action is still available on the stack&mdash;it's the next object on the value stack. It can be accessed from the JSP as usual, as long as we're not trying to access properties named exception or exceptionStack, which would be masked by the exception holder. (We can still access an action property named exception using OGNL's immediate stack index notation&mdash;[1].exception.)

Architecting exceptions and exception handling

We have pretty good control over what is displayed for our application exceptions. It's customizable based on exception type, and may be overridden on a per-action basis. However, to make use of this flexibility, we require a well-thought-out exception policy in our application. There are some general principles we can follow to help make this easier.

Checked versus unchecked exceptions

Before we start, let's recall that Java offers two main types of exceptions&mdash;checked and unchecked. Checked exceptions are exceptions we declare with a throws keyword or wrapped in a try/catch block. Unchecked exceptions are runtime exceptions or a subclass.

It isn't always clear what type we should use when writing our code or creating our exceptions. It's been the subject of much debate over the years, but some guidelines have become apparent.

One clear thing about checked exceptions is that they aren't always worth the aggravation they cause, but may be useful when the programmer has a reasonable chance of recovering from the exception.

One issue with checked exceptions is that unless they're caught and wrapped in a more abstract exception (coming up next), we're actually circumventing some of the benefits of encapsulation. One of the benefits being circumvented is that when exceptions are declared as being thrown all the way up a call hierarchy, all of the classes involved are forced to know something about the class throwing the exception. It's relatively rare that this exposure is justifiable.

Application-specific exceptions

One of the more useful exception techniques is to create application-specific exception classes. A compelling feature of providing our own exception classes is that we can include useful diagnostic information in the exception class itself. These classes are like any other Java class. They can contain methods, properties, and constructors.

For example, let's assume a service that throws an exception when the user calling the service doesn't have access rights to the service. One way to create and throw this exception would be as follows:

throw new RuntimeException("User " + user.getId()
+ " does not have access to the 'update' service.");

However, there are some issues with this approach. It's awkward from the Struts 2's standpoint. Because it's a RuntimeException, we have only one option for handling the exception mapping a RuntimeException to a result. Yes, we could map the exception type per-action, but that gets unwieldy. It also doesn't help if we need to map two different types of RuntimeExceptions to two different results.

Another potential issue would arise if we had a process that examined exceptions and did something useful with them. For example, we might send an email with user details based on the above exception. This would amount to parsing the exception message, pulling out the user ID, and using it to get user details for inclusion in the email.

This is where we'd need to create an exception class of our own, subclassed from RuntimeException. The class would have encapsulated exception related information, and a mechanism to differentiate between the different types of exceptions.

A third benefit comes when we wrap lower-level exceptions for example, a Spring-related exception. Rather than create a Spring dependency up the entire call chain, we'd wrap it in our own exception, abstracting the lower-level exception. This allows us to change the underlying implementation and aggregate differing exception types under one (or more) application-specific exception.

One way of creating the above scenario would be to create an exception class that takes a User object and a message as its constructor arguments:

package com.packt.s2wad.ch09.exceptions;
public class UserAccessException extends RuntimeException {
private User user;
private String msg;
public UserAccessException(User user, String msg) {
this.user = user;
this.msg = msg;
}
public String getMessage() {
return "User " + user.getId() + " " + msg;
}
}

We can now create an exception mapping for a UserAccessException (as well as a generic RuntimeException if we need it). In addition, the exception carries along with it the information needed to create useful messages:

throw new UserAccessException(user,
"does not have access to the 'update' service.");

While we'll explore other aspects of "self-documenting" code in Chapter 14, it's worth pointing out that this could be made even safer, in the sense of ensuring that it's only used in the ways in which it is intended. We could add an enum to the class to encapsulate the reasons the exception can be thrown, including the text for each reason. We'll add the following inside our UserAccessException:

public enum Reason {
NO_ROLE("does not have role"),
NO_ACCESS("does not have access");
private String message;
private Reason(String message) {
this.message = message;
}
public String getMessage() { return message; }
};

We'll also modify the constructor and getMessage() method to use the new Reason enumeration.

public UserAccessException(User user, Reason reason) {
this.user = user;
this.reason = reason;
}
public String getMessage() {
return String.format("User %d %s.",
user.getId(), reason.getMessage());
}

Now, when we throw the exception, we explicitly know that we're using the exception class correctly (at least type-wise). The string message for each of the exception reasons is encapsulated within the exception class itself.

throw new UserAccessException(user,
UserAccessException.Reason.NO_ACCESS);

With Java 5's static imports, it might make even more sense to create static helper methods in the exception class, leading to the concise, but understandable code:

throw userHasNoAccess(user);

I've been accused of wanting my entire application to be a single line of code. What I do know is that the easier it is to read a chunk of code, the better the application is. There are limits on how far this should be taken&mdash;the static helper method may be beyond the point of usefulness, but the idea is sound.

Note

Reading our code out loud is often a useful indicator of how "fluent" it is (this will be covered in Chapter 14). However, it can be an enlightening exercise.

Abstracting underlying exceptions

Another useful technique also involves creating application-specific exceptions whose purpose is largely to encapsulate and abstract underlying exceptions.

For example, consider a service object that may throw one of several vendor-specific checked exceptions that don't share a common subclass. Rather than configure Struts 2 to handle each exception separately (particularly when we don't care about the specifics at the application level, beyond the fact that there was a general service failure), we can configure a single application-specific exception that encapsulates both the underlying cause, and provides more contextual information as compared to the underlying exception.

We might have several implementations of a given service class. We might have an implementation that uses Hibernate to perform actual database access and another that uses hard-coded test data for testing failure modes. If the service object is designed to return a general-purpose service exception, we need to only configure that exception, leaving our application free of implementation details regarding the underlying service.

Our actual service implementation might look like the following, where SpecificDaoException is something thrown from a deeper layer, such as Hibernate:

public void aServiceMethod(...) {
try {
ourDao.daoOperation(...);
} catch (SpecificDaoException e) {
throw new ApplicationSpecificServiceException(
"Exception handling the operation");
}
}

On the other hand, a test service implementation, might just throw an exception while we're testing failure modes (we'll cover this further in the testing chapter):

public void aServiceMethod(...) {
throw new ApplicationSpecificServiceException(
"Testing failure mode foo");
}

In addition, the general-purpose, application-specific exception could provide a constructor accepting a Throwable. This means the underlying exception information isn't lost and can be logged for later examination. (Yes, exception chaining can lead to mile-long stack traces, and is not always appropriate.)

Handling exceptions

Have you ever seen code like this?

try {
doSomething();
} catch (Exception e) {
}

Don't ever write that. If you see it, fix it. It's assuming several things, the worst of which is that the thrown exception is harmless. It might be harmless, but it might also be an EndOfTheWorldException we actually care about. It also assumes that the action taken during the catch is appropriate for all thrown exceptions&mdash;again, possible, but unlikely.

As mentioned in the earlier sections, it might make sense to convert the underlying exception to an application-specific exception. For example, if our imaginary database layer threw a UninitializedCollectionException while retrieving a recipe's ingredients, we might wrap it in a RecipeServiceException. We might even wrap it in an even more generic DatabaseException, making sure we pass the UninitializedCollectionException in its constructor to preserve the context of the original exception.

Logging

Most Struts 2 systems provide detailed logging information. Logging can be configured on several levels to control what systems are logged, and how much information each system logs.

Most of the systems use the Commons Logging library, a thin wrapper around various logging libraries. XWork uses its own log wrapper (and there are valid reasons not to use Commons Logging), but it is similarly configurable.

We'll take a look at how to configure Log4J, a popular logging library, and one of the logging systems Commons Logging works with. The only requirement for using Log4J is to provide the Log4J library on our classpath&mdash;Commons Logging knows how to initialize it.

Introduction to logging

There are two main things we should keep in mind about logging:

  1. There are several logging levels that control logging verbosity.

  2. Log types and levels are configurable for package hierarchies (including specific classes), and the most specific log configuration wins.

Log4J (and Commons Logging) defines six logging levels, ordered here by verbosity level (TRACE is the most verbose):

  1. TRACE

  2. DEBUG

  3. INFO

  4. WARN

  5. ERROR

  6. FATAL

Java's java.util.logging.Level defines its own set of standard logging levels: ALL, FINEST, FINER, FINE, CONFIG, INFO, WARNING, SEVERE, and OFF. Which level we target depends on the logging library chosen and/or our runtime environment.

Each of our log messages, then, will exist in one of these levels. For example, exceptions might be logged at the ERROR level, whereas detailed program flow might be logged at the TRACE level, which would normally be seen only during development.

Logging is configured at the package and class level. First, we'll look at how to use logging in our Java code, and then we'll look at logging configuration.

Using the loggers

In general, we'll use Log objects in our classes. Logs accept a Java class argument and this is probably the most common usage pattern. For example, an action class might declare a log as follows:

private static final Log LOG = LogFactory.getLog(ThisAction.class);

We can then use the logger throughout our code, calling the various methods corresponding to the logging levels we saw in the previous section. The following pseudocode shows several logging levels and how they might be used:

public String execute() throws Exception {
log.trace("Enter.");
if (StringUtils.isBlank(name)) {
return SUCCESS;
}
Exception ex = null;
try {
log.debug(" Constructing " + name + "...");
ex = (Exception) Class.forName(name).newInstance();
} catch (Exception e) {
log.error("Exception during instantiation: "
+ e.getMessage(), e);
}
if (ex != null) {
log.trace(" Throwing " + name + "...");
throw ex;
}
log.debug(" Could not create instance; returning...");
return SUCCESS;
}

We see three different log levels&mdash;TRACE, DEBUG, and ERROR. TRACE is used for the lowest-level diagnostic information possible, and is used for very fine-grained execution tracing. DEBUG is used for showing information useful to debugging. Finally ERROR is used for showing actual errors. Actually, it's arguable that the ERROR-level log statement above really belongs at the WARN level, which is a little less serious. INFO level messages (not used above) are for bits of information that will generally be displayed. For example, this might include configuration details.

The styles we use for the contents of our log messages are many and varied. We might indent our log messages based on the nesting inside the method and use periods at the end of statements that are complete in and of themselves. We might also use ellipses (...) for statements made before an operation occurs and also before statements that are a continuation of a chain of events. These aren't hard-and-fast rules&mdash;just conventions. Developing a consistent set of conventions can help while wading through large log files.

There are also log methods for determining if a specific log level is set. For example, if we had a collection we wanted to log if the DEBUG level was active, we would write the following:

if (log.isDebugEnabled()) {
for (Object baz : bazzes) {
log.debug(baz);
}
}

It's true that we don't need the test to see if the DEBUG level is enabled. However, by wrapping the entire loop inside the check, we avoid looping through the entire collection and not printing anything out. If a debug message is unusually expensive to construct, for whatever reason, the same method can be used. For example, we might use String.format() to create a log message as shown here:

log.debug(String.format(" Throwing %s...", throwsClassname));

If it was an expensive format, it might be worth the extra "noise" to surround it if an isDebugEnabled() is called to avoid a potentially unnecessary call to String.format().

Configuring the loggers

We can configure Log4J by using either XML or a properties file. We'll briefly look at the properties file configuration, and then configure various Struts 2 subsystems and our application to log at whichever level we prefer. Further configuration options can be found in the Log4J documentation or in the documentation for whatever logging implementation we decide to use.

We'll break up the properties file into chunks and discuss each one individually to help make things a bit more clear. When I refer to properties, I'll leave the log4j prefix as seen here:

log4j.rootLogger=INFO, A1

The rootLogger property defines the broadest level of logger. Any logging not specifically configured will be handled by the rootLogger definition. Here, we've set the log level to INFO and listed a single log appender A1.

Appenders determine how the logging occurs. For example, log statements can be directed to the console (as shown below), a file, a rolling file that is limited in size but keeps a specified number of historic log files, a database, and so on. These options are detailed in the Log4J documentation. We'll examine how to set up a console log appender. This is particularly useful when developing using an IDE with a console view, such as Eclipse, or for production servers.

log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%d %-5p %c.%M:%L - %m%n

In the example above, we define the appender as a Log4J ConsoleAppender class, which writes log messages to the console. Note that the appender A1, which we named in our rootLogger, is used after the appender property name. This lets Log4J know which appender we're defining.

We define the layout of log statements using the layout property, again appended to the property name. The PatternLayout creates a log statement template, where each character following the % character represents some specific information (for example­, %d is the date).

log4j.logger.com.packt.s2wad=DEBUG
log4j.logger.org.apache.struts2=INFO
log4j.logger.com.opensymphony.xwork2=INFO
log4j.logger.ognl=INFO
log4j.logger.org.apache.struts2.util.StrutsTypeConverter=DEBUG

The remainder of our Log4J configuration file consists of setting the log level for package- and class-specific logs. The logger property defines the log level for whatever package (and optionally classname) follows it in the property name.

We just now configured the DEBUG level for our entire application, as all our Java classes are in the com.packt.s2wad package. All classes in that package will be configured to log DEBUG-level messages and higher-priority messages (such as INFO or ERROR).

We configured Struts 2 itself (and XWork and OGNL) to log only INFO-level messages (and above). However, we have configured the StrutsTypeConverter class at the DEBUG level. This means that everything in Struts 2, except the StrutsTypeConverter class, will log at the INFO level, also including other classes in the org.apache.struts.util package.

The ability to turn on DEBUG-level logging with this granularity can be very helpful. It allows us to keep all of our log statements in our application, but still choose the level at which to run logging in a single location. During development, we'll generally run at the TRACE or DEBUG levels. Once we move into production, it's more likely that we'd run at the INFO or WARN levels.

Turning up the logging levels can often produce gigantic log files, especially at the DEBUG and TRACE levels. Different libraries produce different amounts of logging at different levels. However, it is very instructive to crank up the log levels at regular intervals, particularly when first learning a library, system, or technology. This is required to get a feel of some of what's going on "under the hood". Turning up log levels can also help track down configuration issues, particularly at startup.

Summary

This chapter introduces us to the Struts 2 declarative exception handling mechanism, showing us how we can define both global- and action-specific exception mappings and results. The chapter also looks at ways to make our exceptions more useful by encapsulating exception-specific data in the exception class.

We also get an overview of how to configure logging for our Struts 2 application, including how to set the log levels for each of the major Struts 2 subsystems&mdash;XWork, OGNL, and Struts 2 itself.

In the next chapter, we'll move away from Struts 2 a bit and examine everybody's favorite web application language&mdash;JavaScript. Really, it's been misunderstood and abused for too long. Despite some warts, it's a capable, dynamic language that can be used to a wonderful effect, as long as it's written well. And yes, it is possible to write clean and safe JavaScript!

References

A reader can refer to the following:

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.217.147.193