Table of Contents
The main goal of this user's guide is to tell, how to quickly introduce robust and diagnostic exception handling and reporting into a Java software system.
These hints are easier to follow, if you are writing a software system from scratch, but can be applied onto existing software, as well.
You should firstly introduce central exception reporting, secondly assure exception propagation to the highest possible level, thirdly provide all exceptions with diagnostic parameters, and lastly provide natural message texts for all execptions.
When you are using MulTEx, the main paradigm for error handling and reporting are Java exceptions. Any misfunction will be passed to the caller of a method by the standard Java means, an exception. This will be propagated to the upmost level, where it still can be caught, in order to report it.
Depending on the type of user interface, there are different places, where you can catch all propagated exceptions. Sometimes it is not simple, to find this place in the UI documentation. Sometimes you will not be able to find it, and must use a workaround in order to assure central exception reporting.
In a Java application, invoked as a command line, you should report, both the exception messages,
and its stack trace, as the command line application typically does not have a button
for requesting the details of the stack trace. The destination for the exception reporting
usually will be System.err.
If you specify the main method as
public static void main(final String[] i_args) throws Exception {...}
, you will already have a centralized exception reporting, but the Java Runtime system will
report only the stack trace without giving nice textual error messages.
In order to get the textual message chain, and the stack trace, you must write your main method as follows:
public static void main(final String[] i_args) {
... //check and report argument violations
try{
_doTheWork(i_args);
}catch(final Exception e){
multex.Msg.printReport(e);
}
}
This will report to System.err the exception chain messages using for localization
the resource bundle MsgText in the actual working directory, followed by the
stack trace. If you want to use another resource bundle for localization,
then use the variant
printReport(StringBuffer, Throwable, ResourceBundle), instead, and print out the filled
StringBuffer, afterwards.
In a Java Swing application, you should report all exceptions by a variant of the static multex.Swing.report() methods.
These report firstly a window with the messages of the exception causal chain.
Then pressing on the button Details will expand the dialog and show the
stack trace of the exception chain, too.
Usually the variant of method multex.Swing.report() with an owner
component should be used, as it will block further input to the owner component or its parent window,
until the exception message dialog has been closed by the user.
In order to report all uncaught exceptions in a Swing application, which occur
during the execution of an UI-event-triggered action, it is sufficient to install
one central exception handler using the undocumented system property
sun.awt.exception.handler,
see more in a JGuru discussion.
This undocumented behaviour is wrapped by a MulTEx service.
You can implement the interface multex.AwtExceptionHandler, and install
this class by the method multex.Awt.setAwtExceptionHandlerClass(...).
Then any exception propagating up to the AWT/Swing event-dispatch thread will be reported
by the handler's method handle(Throwable).
In this method you should call a multex.Swing.report() method.
In order to block input to the GUI application while reporting an
exception, it is necessary to locate the application's frame and use it in the
central exception handler's call to report(...) as the ownerHook.
If an application has several frames, and it is not possible to determine automatically, which UI component must be blocked during exception reporting, then there is a more work-intensive way to central exception reporting.
You should write your own class, e.g. UiAction as
a subclass of javax.swing.AbstractAction:
public class UiAction extends javax.swing.AbstractAction {
final void actionPerformed(ActionEvent ev){
try{actionPerformedWithThrows(ev);
}catch(Exception ex){multex.Swing.report(ev.getSource(), ex);
}
}
}
This class gets the UI component causing the event, uses its parent chain to get the owner frame,
and will block it during exception reporting. When using UiAction
instead of javax.swing.AbstractAction
you must extend UiAction redefining the method
actionPerformedWithThrows
instead of actionPerformed.
Note: Not yet tested by me personally, but used in a project known to me.
In a Java Server Pages application, you have several places, where you should report exceptions. These include firstly a JSP error page, which will be called by any unhandled exception. Secondly each JSP page with an input form should contain an error message, if something went wrong during execution of its action. Thirdly, depending on the UI framework used, there is the possibility to report an exception near to the form field, which caused it.
In order to report all uncaught exceptions in a JSP/servlet application, which occur during the execution of an UI-event-triggered action, it is sufficient to install one central error page. See section “2.4.2 Request Time Processing Errors” of the Sun Java Server Pages Specification about this.
In Tomcat you can do this in the deployment descriptor web.xml by an
“error-page” directive. E.g.:
<error-page>
<exception-type>java.lang.Throwable</exception-type>
<location>/system/errorPage.jsp</location>
</error-page>
This means, that any exception, including Throwable,
will be reported by forwarding to the JSP page /system/errorPage.jsp.
The error page itself must be marked as such by setting the page
directive’s isErrorPage attribute to true, e.g.:
<%@page contentType="text/html" isErrorPage="true" %>
In such an error page you should report not only the message texts of the exception chain, but also its stack trace and diagnostically useful attributes of the request, session, and application.
TODO: Give examples.
As for Struts I did not find a possibility to set a central exception handler,
you must use the approach with subclassing the Struts Action class.
You should write your own class, e.g. UiAction as
a subclass of org.apache.struts.action.Action:
public class UiAction extends org.apache.struts.action.Action {
public ActionForward execute(
final ActionMapping mapping,
final ActionForm form,
final HttpServletRequest request,
final HttpServletResponse response
){
try{return executeWithThrows(mapping, form, request, response);
}catch(Exception ex){... //report(ex)
}
}
}
This class's execute method will be invoked by Struts. It calls itself the
method executeWithThrows of its subclass, catching all Exception-s of it.
This will report the caught exception
by storing it in the session attribute “EXCEPTION” and its
message chain by Struts' saveErrors(...), and then forwarding to
either the JSP input page, if specified in the struts-config.xml,
or to the errorPage.jsp as last-resort.
Note: The report action in the catch-block is quite complex, so you should look at the example running on the webserver http://fb6.tfh-berlin.de/, which can be obtained from the author, in the moment at Examples for Central Exception Reporting.
One student group even implemented centralized exception reporting for
form field related exceptions.
In Struts it is possible to saveErrors oder saveMessages
indicating the name of the offending field of the form working on.
This will place the message in the appropriately marked position in the form.
In the central exception handler for Struts you can specifically handle exceptions,
which are related to a form field. E.g. a FieldvalueExc
could port the identifier of the related field, so that UiAction could
put the exception message into the good position.
Another idea is to find the place in Struts, where any exception occurring
in a setXxx() method of a form bean can be caught and reported into the form
near to the field xxx.
Here is described the solution of the student project TriplePlay in SS 2005.
Der JavaServer Faces Lebenszyklus (siehe http://java.sun.com/j2ee/1.4/docs/tutorial/doc/images/jsfIntro-lifecycle.gif) wird gesteuert von einer Implementation der Schnitstelle Lifecycle (im Paket javax.faces.lifecycle, Refenrenzimplementation von sun unter com.sun.faces.lifecycle.LifecycleImpl).
Beim Start der Webapplikation wird eine LifecycleFactory (ebenfalls im Paket javax.faces.lifecycle, Refenrenzimplementation von sun unter com.sun.faces.lifecycle.LifecycleFactoryImpl) aufgefordert, eine Lifecycle zu liefern. Normalerweise wird die Standardimplementierung genommen. Man hat aber die Möglichkeit, die LifecycleFactory auszutauschen uns somit seine eigene Lifecycle zurück zu liefern.
Werden wir konkret: Hier die zwei Klassen, die wir geschrieben haben. Man hat die Möglichkeit, die von der Referenzimplementation gelieferte Klasse zu spezialisieren, oder die Schnittstelle komplett selbst zu implementieren. Wir wählten die erste Möglichkeit.
package myPackage; //MyLifecycleFactoryImpl.java
public class MyLifecycleFactoryImpl extends
com.sun.faces.lifecycle.LifecycleFactoryImpl {
import javax.faces.FacesException;
import javax.faces.lifecycle.Lifecycle;
public class MyLifecycleFactoryImpl extends
com.sun.faces.lifecycle.LifecycleFactoryImpl {
public Lifecycle getLifecycle(final String i_lifecycleId) throws FacesException {
return new MyLifecycleImpl();
}
}
/////////////////////////////////////////////////////////////////////
package myPackage; //MyLifecycleImpl.java
import javax.faces.FacesException;
import javax.faces.context.FacesContext;
public class MyLifecycleImpl extends com.sun.faces.lifecycle.LifecycleImpl
{
public void execute(final FacesContext io_facesContext) {
try {
super.execute(io_facesContext);
}
catch(final FacesException e) {
// handleExceptions here
}
}
}
Nun muss man JavaServer Faces noch mitteilen, dass man seine eigene LifecycleFactory-Implementation statt der Standardimplementation verwenden will. Das geschieht über die faces-config.xml (eine Konfigurationsdatei ähnlich wie struts-config.xml)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE faces-config PUBLIC
"-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"
"http://java.sun.com/dtd/web-facesconfig_1_0.dtd">
<faces-config>
...
<factory>
<lifecycle-factory>myPackage.MyLifecycleFactoryImpl</lifecycle-factory>
</factory>
...
</faces-config>
Damit wird unsere Implementation der LifecycleFactory verwendet, die wiederum in der Methode getLifecycle (s.o.) unsere eigene Lifecycle-Implementation zurückliefert.
Falls noch Fragen sind, werde ich gerne versuchen, diese zu beantworten. Ansonsten können Sie gerne meine email-adresse zu diesem kleinen HowTo angeben, damit sich künftige Studenten im Fall der Fälle auch an mich wenden können.
Siamak Haschemi, mail: siamak.h (bei) gmx.de
If the run() method of a thread terminates due to an uncaught exception,
then by default, this thread will die and the stack trace of this exception
will be printed to System.err.
This default behaviour is not satisfactory. At first, we would like to see the exception message chain, too. And secondly, we would really like to see it in the application's user interface, instead of in a may-be suppressed console output.
In order to report all uncaught exceptions of all background threads,
you must place all the threads in your own ThreadGroup.
For this ThreadGroup you redefine the method
void uncaughtException(Thread t, Throwable e). In this method you can report the exception chain in the format, and to the destination, you want. You should try to report to the user interface (UI), if the thread was created or started by an UI event, e.g. if it implements an action, which would last too long for the user to await its completion.
But for a long-living thread, usually serving commands or alike, and not triggered by
a user action.
we cannot tolerate to stop the thread.
Thus such a server thread should catch in its command loop in the run() method
any exception and report it to a logging destination.
Also here it would be useful to inform the user, if any exists, that a required service failed, and where is the appropriate logfile.
Until it will be described here, it is quite well described in german language in the technical paper about MulTEx.
The most comfortable way to get an internationalizable message text pattern associated with each exception is the following, usable from MulTEx version 7.1.
You define the message text pattern for each concrete subclass of Throwable of which you have the source code. You place the text pattern as the main comment into the Javadoc comment of the exception class before any @tags. So the text pattern serves 1ly as documentation for this exception, and 2ly as text pattern for internationalization.
For example you could define an exception CreditLimitExc with message text pattern and two parameters as follows.
package org.company.project;
class Account {
...
/**Taking {0} units from your account will leave less than your credit limit {1} units on it.*/
public static final class CreditLimitExc extends multex.Exc {
public CreditLimitExc(final long amount, final long creditLimit){
super(null, new Integer(amount), new Integer(creditLimit));
}
}
}
You can use the doclet multex.tool.ExceptionMessagesDoclet to collect all exception
message text patterns from the exceptions. You must specify the sources to scan as usually for a doclet.
You must pass the output .properties file name to the doclet option -out.
By executing the doclet on the upper example the following property definition will be appended to the given -out file:
org.company.project.Account$CreditLimitExc = Taking {0} units from your account will leave less than your creditLimit {1} units on it.
<target name="exception" depends="compile" description="Collect all exception message texts in the LAR software version6 into one file">
<delete quiet="true">
<fileset file="exception.properties"/>
</delete>
<javadoc
access="private"
classpathref="larLibraries"
doclet="multex.tool.ExceptionMessagesDoclet"
docletpathref="checker.classpath"
source="1.4"
useexternalfile="yes"
>
<arg value="-out"/>
<arg file="exception.properties"/> <!--Message texts will be appended to this file.-->
<fileset refid="larSourceFiles"/>
</javadoc>
</target>