Most exceptions that are thrown are unexpected: we don't expect them to happen (especially during production) such as:
NullPointerException
: Didn't I double checked all my source code to avoid NPE's?
CvsParserException
: Why did the user pick a html file when I asked him for a CVS file?
IDidNotKnowThisExistedRuntimeException
: What the ...?
And if you except some of them, you ussually can't really fix the problem, just deal with it:
Log the exception.
Notify the user that whatever he tried didn't work, preferably with an not-technical, exception-specific explanation.
Either shutdown the application or allow the user to continue (and try again).
You could use try-catch during every user action:
protected boolean onFinish() { try { form.getFormModel().commit(); // ... getApplicationContext().publishEvent(new LifecycleApplicationEvent(eventType, getEditingContact())); return true; } catch (Throwable throwable) { handleException(throwable); } }
But this is tedious and error prone:
It's easy to forget to try catch some code, which makes the exception escape to the top layer exception handler.
You could unwillingly eat the exception, not logging it:
If you handle an exception, but forget to log it and/or show it to the user.
If you throw an exception in the catch or finally part, only the last exception bubbles up, effectively hiding the real exception.
In production, this leads to discussions where the user is sure he pushed the button (which he did in this case) and the programmer is sure the user didn't because the system didn't report anything and nothing has changed. If you notice that while you are fixing a issue, an exception is eaten (making it hard to identify the original issue), create a new issue because exceptions are eaten and fix that first.
You are in danger to handle the same exception on 2 different layers, effectively logging it or notifing the user twice.
In some layers or parts of the application, it might not be clear if you need to notify the user (and which user) through a swing dialog or JSP or webservice response.
Spring-richclient's exception handling system uses the top layer exception handling. It expects that all other layers let the exception bubble up.
In the LifecycleAdvisor you can register a RegisterableExceptionHandler:
<bean id="petclinicLifecycleAdvisor" class="....PetClinicLifecycleAdvisor"> <!-- ... --> <property name="registerableExceptionHandler" ref="exceptionHandler" /> </bean> <bean id="exceptionHandler" class="org.springframework.richclient.exceptionhandling.SilentExceptionHandler"/>
When an exception handler is registered, it will ussually register
itself as the UncaughtExceptionHandler
on the threads.
However, the event thread catches a throwable thrown in any event, to
prevent getting killed, so it also registers itself to the event thread
specifically (regretably this is currently sun JRE specific).
TODO: What happens to exceptions thrown in a pooled SwingWorker or Future as they don't kill the thread, nor do they occur in the event thread? They are probably rethrown on the event thread?
Most of these exception handlers are part of the spring-richclient-tiger module.
Logs a throwable but does not notify the user in any way. Normally it is a bad practice not to notify the user if something goes wrong.
You can set a log level on it:
<bean class="org.springframework.richclient.exceptionhandling.SilentExceptionHandler"> <property name="logLevel" value="WARN"/> <!-- ... --> </bean>
This means that any exception handled by this exception handler will be logged at the warn level.
Shows the exception in a dialog to the user (as well as logging it). You can set a log level and the icon of the dialog depends on that log level. The shown dialog has a caption (= dialog title) and description (= dialog content), which are fetched from the i18n messages files. There are 2 ways to resolve those messages: static or dynamic (default).
You can statically set the title and description by setting the
messagesKey
property. However, it's a lot more
powerfull to use the default dynamic behaviour based on the class of the
exception. For example if a NumberFormatException
is
thrown, it will first look for these i18n keys:
java.lang.NumberFormatException.caption=Not a number java.lang.NumberFormatException.description=\ You did not enter a a valid number.\n\ \n\ Please enter a valid number.
If these messages keys don't exist, it will fall back to the
parent class of NumberFormatException
, which is
IllegalArgumentException
:
java.lang.IllegalArgumentException.caption=... java.lang.IllegalArgumentException.description=...
It will continue to fall back up the chain, untill it reaches
Throwable
. This allows you to direct all unexpected
exceptions (for example
IDidNotKnowThisExistedRuntimeException
) to a
MessagesDialogExceptionHandler
that logs them as an
error and shows a generic message. You can even use
{0}
in your i18n message to show the
exception.getMessage()
in the description:
# Double quotes(") need to be escaped (\"), single quotes (') always seem to break the replacing of {0}. java.lang.RuntimeException.caption = Unexpected general bug java.lang.RuntimeException.description = \ The application experienced an unexpected bug,\n\ due to a programming error.\n\ \n\ The application is possibly in an inconsistent state.\n\ It is recommended to reboot the application.\n\ \n\ The exact bug is:\n\ {0}\n\ \n\ Please report this bug. java.lang.Error.caption = Unexpected serious system failure java.lang.Error.description = \ A serious system failure occured.\n\ \n\ The application is possibly in an inconsistent state.\n\ Reboot the application.\n\ \n\ The exact bug is:\n\ {0}\n\ \n\ Please report this bug.
Note that, although this dynamic system is pretty powerfull and
avoids a lot of boilerplate, it's ussually not a replacement for
DelegatingExceptionHandler
, because it doesn't allow
you to assign different log levels, etc.
You can set a shutdown policy on a dialog exception handler:
<bean class="org.springframework.richclient.exceptionhandling.MessagesDialogExceptionHandler"> <property name="shutdownPolicy" value="ASK" /> <!-- ... --> </bean>
This allows you to optionally enforce or propose a
System.exit(1)
.
A special exception handler which can only handle an
InvalidStateException
thrown by Hibernate validator.
It shows the failed validations to a user in a list in a dialog. In most
cases it's inferiour to the HibernateRulesValidator
which validates before the user presses the commit button. But because
the latter forces you to hand code @AssertTrue
's and
it could be working on stale client-side data, it's actually a very nice
backup to also configure this exception handler:
<!-- Inside a delage list of DelegatingExceptionHandler --> <bean class="org.springframework.richclient.exceptionhandling.delegation.SimpleExceptionHandlerDelegate"> <property name="throwableClass"> <value type="java.lang.Class">org.hibernate.validator.InvalidStateException</value> </property> <property name="exceptionHandler"> <bean class="org.springframework.richclient.exceptionhandling.HibernateValidatorDialogExceptionHandler"> <property name="logLevel" value="INFO" /> <property name="shutdownPolicy" value="NONE" /> </bean> </property> </bean>
There are bunch of build-in exception handlers, but ussually there
isn't one exception handler that fits alls exceptions. A
DelegatingExceptionHandler
allows you to delegate an
exception to the right exception handler. It accepts a delegate list and
traverses that list in order. The first delegate that can handle the
exception, has to handle the exception and the rest of the delegate list
is ignored.
For example, here we configure authentication and authorization
exceptions to a WARN MessagesDialogExceptionHandler
,
hibernate validator exceptions to an INFO
HibernateValidatorDialogExceptionHandler
and the rest
to an ERROR MessagesDialogExceptionHandler
.
<bean id="exceptionHandler" class="org.springframework.richclient.exceptionhandling.delegation.DelegatingExceptionHandler"> <property name="delegateList"> <list> <bean class="org.springframework.richclient.exceptionhandling.delegation.SimpleExceptionHandlerDelegate"> <property name="throwableClassList"> <list> <value type="java.lang.Class">org.acegisecurity.AuthenticationException</value> <value type="java.lang.Class">org.acegisecurity.AccessDeniedException</value> </list> </property> <property name="exceptionHandler"> <bean class="org.springframework.richclient.exceptionhandling.MessagesDialogExceptionHandler"> <property name="logLevel" value="WARN" /> <property name="shutdownPolicy" value="NONE" /> </bean> </property> </bean> <bean class="org.springframework.richclient.exceptionhandling.delegation.SimpleExceptionHandlerDelegate"> <property name="throwableClass"> <value type="java.lang.Class">org.hibernate.validator.InvalidStateException</value> </property> <property name="exceptionHandler"> <bean class="org.springframework.richclient.exceptionhandling.HibernateValidatorDialogExceptionHandler"> <property name="logLevel" value="INFO" /> <property name="shutdownPolicy" value="NONE" /> </bean> </property> </bean> <!-- The order is important: if Throwable would be first then the others would be ignored --> <bean class="org.springframework.richclient.exceptionhandling.delegation.SimpleExceptionHandlerDelegate"> <property name="throwableClass" value="java.lang.Throwable" /> <property name="exceptionHandler"> <bean class="org.springframework.richclient.exceptionhandling.MessagesDialogExceptionHandler"> <property name="logLevel" value="ERROR" /> <property name="shutdownPolicy" value="ASK" /> </bean> </property> </bean> </list> </property> </bean>
Processes the exception if it is an instance of throwableClass or the throwableClassList.
In most cases this class is overkill and SimpleExceptionHandlerDelegate with a purger will suffice. However if those don't suffice, read the javadoc of this class.
An exception purger allows you to cream off wrapper exceptions.
This allows you to handle a chained exception in the chain of the
uncaught exception, instead of the uncaught exception itself. Almost all
exception handlers and delegate's support the use of a purger.
DefaultExceptionPurger
supports 2 ways to identify
the depth to cream off: include or exclude based.
A chained exception of the type in the
includeThrowableClassList
is stripped from all it's
wrapper exceptions and handled by the exception handler or evaluated by
the delegate. For example, we want to handle every
MySQLIntegrityConstraintViolationException
even if
it's wrapped:
<bean class="org.springframework.richclient.exceptionhandling.delegation.SimpleExceptionHandlerDelegate"> <property name="exceptionPurger"> <bean class="org.springframework.richclient.exceptionhandling.delegation.DefaultExceptionPurger"> <property name="includeThrowableClassList"> <list> <value type="java.lang.Class">com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException</value> </list> </property> </bean> </property> <property name="throwableClassList"> <list> <value type="java.lang.Class">com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException</value> </list> </property> <property name="exceptionHandler"> <!-- ... --> </property> </bean>
A chained exception of the type in the
excludeThrowableClassList
is stripped together with
all it's wrapper exceptions and it's cause is handled by the exception
handler or evaluated by the delegate. For example the server wraps all
exceptions in an annoying, useless
WrappingServiceCallException
and we want to get rid
of it:
<bean id="exceptionHandler" class="org.springframework.richclient.exceptionhandling.delegation.DelegatingExceptionHandler"> <property name="exceptionPurger"> <bean class="org.springframework.richclient.exceptionhandling.delegation.DefaultExceptionPurger"> <property name="excludeThrowableClassList"> <list> <value type="java.lang.Class">foo.bar.WrappingServiceCallException</value> </list> </property> </bean> </property> <property name="delegateList"> <!-- ... --> </property> </bean>