Package org.marketcetera.util.except
Nested exception framework with internationalization (i18n) and thread interruption support.
General usage
This package provides classes to represent exceptions whose message is subject to i18n; that is, the exception message is retained as a handle, that is localized at a later time by code that catches the exception. For example:
throw new I18NException(Messages.CANNOT_SET_TIME);
...
catch (I18NException ex) {
// Shows error for inability to set time, translated to a thread-
// specific locale. "Category" is the logging category.
ex.getI18NBoundMessage().error("Category",ex);
}
Nested exceptions are also supported, and localization can be delayed until the wrapping exception is caught. For example:
throw new I18NException(Messages.CANNOT_SET_TIME);
...
catch (I18NException ex) {
throw new I18NException(ex,Messages.NTP_FAILED);
}
...
catch (I18NException ex) {
// Shows error for NTP failure due to its inability to set time.
ex.getI18NBoundMessage().error("Category",ex);
}
When the exception message to be thrown requires arguments, those
are supplied by creating an I18NBoundMessage. For example:
throw new I18NException(new I18NBoundMessage1P
(Messages.CANNOT_FIND_FILE,file.getAbsolutePath());
In addition to the checked exception I18NException, the framework also
provides the runtime exception I18NRuntimeException and the error
I18NError.
With nested exceptions, the JDK follows certain conventions on
how getMessage(), getLocalizedMessage(),
and toString() behave. These conventions do not always
produce user-friendly or complete messages for nested exceptions, so
two methods are provided for that task: getDetail()
and getLocalizedDetail().
Utilities for commons design patterns are also provided in ExceptUtils, such as swallowing
exception after displaying a warning message, or wrapping an exception
in another exception.
Note that, when you use this framework, your unit tests can check that an expected exception contains the right message by checking message handles and parameters, instead of relying on a less reliable locale-specific string comparison. For example:
@Test
public void findWorks()
{
try {
findFile(NAME);
} catch (I18NException ex) {
I18NBoundMessage1P m=(I18NBoundMessage1P)ex.getI18NBoundMessage();
assertEquals
(ex.getDetail(),Messages.CANNOT_FIND_FILE,m.getMessage());
assertEquals(NAME,m.getParam1());
return;
}
fail();
}
Thread interruption
In Java, thread interruption cannot occur without the cooperation of the interrupted thread: a second thread sets the first thread's interrupted flag, and the first thread is supposed to check that flag relatively often, detect this request for interruption, and terminate as soon as possible.
JDK facilities like Thread.sleep(long) throw an
InterruptedException when the thread is
interrupted. This is often mistaken as "the right way" to handle an
interruption, but this is not true. The JDK throws an exception
because Thread.sleep(long) was unable to complete
successfully the task assigned to it, i.e. suspend execution for a
predetermined interval.
The "right" way to handle thread interruption depends on the task
at hand. A method may choose to throw an InterruptedException as soon as it sees that the thread's
interrupted flag is set; this should be done if the method is unable
to complete its assigned task due to that interruption. However, it is
equally reasonable for a method to ignore the interruption altogether,
and continue on with its task, if, for example, the method expects
completion to take place soon. It is also reasonable for a method to
ignore the interruption temporarily while it gracefully abandons its
task and cleans up, and then terminate by throwing an exception.
Similarly, a long-running task is expected to check the thread's
interrupted flag at regular intervals. A long-running task that
involves no blocking I/O or calls to Thread.sleep(long) or other such methods would have no
other way to detect that it has been asked to terminate.
To compound the confusion, when the JDK throws an InterruptedException, it clears the interrupted flag, the
intent being that the caller who catches the exception may proceed
with cleanup without worrying about calls into the JDK (or other
libraries) that would terminate prematurely or refuse to work because
they see the interrupted flag set. APIs added in more recent JDK's,
however, will leave the flag set (e.g. when an ClosedByInterruptException is thrown); the intent
here is any APIs a method calls during cleanup should be able to
refuse to do their work if they believe the task assigned to them
would take too long (the API implementation knows better than the
caller how long it'll need to run).
The approach recommended by our framework is to leave the flag turned on, and have your code turn it off explicitly if it so sees fit. This is because, if the framework turned the flag off for you, it would gives you a false sense of safety: there is nothing that stops another thread from setting the flag asynchronously during your cleanup attempt, and thus derailing it. So it is best to write your code assuming the flag is set (and, if you turn it off, that it can be set again at any time), and take proper steps to handle those cases.
To assist the above code designs, this framework provides the following tools:
- The exception classes
I18NInterruptedExceptionandI18NInterruptedRuntimeException, which can be thrown to indicate unsuccessful task completion due to interruption. These are subclasses ofI18NExceptionandI18NRuntimeExceptionrespectively, and include a default i18n message for interruption. - The wrapping utilities in
ExceptUtilsuse these classes instead of the genericI18NExceptionandI18NRuntimeExceptionwhen the wrapped exception indicates an interruption; these utilities also ensure that, in such a case, the interrupted flag is set. - The exception swallowing utilities in
ExceptUtilsalso set the interrupted flag if the exception being swallowed indicates an interruption. I18NInterruptedExceptionandI18NInterruptedRuntimeExceptionprovide static methods such asI18NInterruptedException.checkInterruption()that make it easy for your code to check if interruption has occurred (i.e. the interrupt flag is set) and, if so, throw the exception on your behalf (while leaving the flag set).ExceptUtilsprovides similar static methods that throw anInterruptedException(the flag remains set).
Here are some samples that use the above utilities:
void rethrowInterruptedIfFailed()
throws I18NException
{
...
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
// An I18NInterruptedException is thrown that wraps ex.
// The thread interrupted flag is set.
throw ExceptUtils.wrap(ex);
}
...
}
void rethrowAnyIfFailed()
throws I18NException
{
...
try {
stream.close();
Thread.sleep(1000);
} catch (Exception ex) {
// If the exception caught is an InterruptedException from the
// sleep, then an I18NInterruptedException is thrown that wraps ex,
// and the thread interrupted flag is set. Otherwise, ex is
// wrapped by an I18NException, which is thrown.
throw ExceptUtils.wrap(ex);
}
...
}
void ignoreAllIfFailed()
throws I18NException
{
...
try {
stream.close();
Thread.sleep(1000);
} catch (Exception ex) {
// A warning message is shown with the exception. If the
// exception thrown is an InterruptedException from the
// sleep, the thread interrupted flag is set.
ExceptUtils.swallow(ex);
}
// Continue on.
...
}
void ignoreAllUntilEnd()
throws I18NException
{
// Perform first half of task.
...
// If the interrupted flag is set, terminate now and throw an
// I18NInterruptedException (and leave the flag set).
I18NInterruptedException.checkInterruption();
// Perform second half of task.
...
}
void ignoreAllUntilEndSignatureForcedBySuperclass()
{
// Perform first half of task.
...
// If the interrupted flag is set, terminate now and throw an
// I18NInterruptedRuntimeException (and leave the flag set).
// An I18NInterruptedRuntimeException is used because the method
// signature cannot be changed (it is set by the superclass).
I18NInterruptedRuntimeException.checkInterruption();
// Perform second half of task.
...
}
-
Interface Summary Interface Description I18NThrowable An internationalized throwable.Messages The internationalization constants used by this package. -
Class Summary Class Description ExceptUtils General-purpose utilities.I18NExceptUtils Utilities supporting message generation. -
Exception Summary Exception Description I18NException An internationalized exception.I18NInterruptedException An internationalized exception indicating interruption.I18NInterruptedRuntimeException An internationalized runtime exception indicating interruption.I18NRuntimeException An internationalized runtime exception. -
Error Summary Error Description I18NError An internationalized error.