The Java library smartics exception has been released with version 0.11.3.
This release fixes class loader problems on application servers. Prior to this release, in some situations, classes had no access to resource files and therefore failed to construct localized exception messages.
For details on this library please visit the project's homepage, for changes since the last version, please consult the release report.
This is a small example that shows how the same message bean implementation is used in a logging and in an exception handling context.
This post is part of a series of articles that provide information about smartics Exceptions, an exception library for Java.
For a short introduction to smartics Exceptions please refer to our blog post What is smartics-exceptions all about?.
Quick and Dirty
Here is a code sippet that creates an instance of a service via Java’s ServiceLoader
:
public final T create() throws ResourceException { final Iterator<T> iterator = ServiceLoader.load(type).iterator(); final StringBuilder buffer = new StringBuilder(64); T instance = null; while (iterator.hasNext()) { try { if (instance != null) { final String implementation = iterator.next().getClass().getName(); buffer .append("\nDuplicated implementation rejected: ") .append(implementation); continue; } instance = iterator.next(); } catch (final ServiceConfigurationError e) { buffer .append("\nError encountered: ") .append(e.getMessage()); } } if (buffer.length() > 0) { LOG.warn( "Problems encountered while fetching implementations of '" + type.getName() + "':" + buffer); } if (instance == null) { throw new ResourceException( "Cannot create instance of implementation of '" + type.getName() + "'."); } return instance; }
The implementation is straight forward: text messages are coded as string literals for logging (LOG.warn(...)
) and providing information about an exception context (throw new ResourceException(...)
). To move these text messages to resource bundles, smartics Exceptions can help.
Using smartics-exceptions
The code using smartics-exceptions is like this:
public final T create() throws ResourceException { final Iterator<T> iterator = ServiceLoader.load(type).iterator(); final TypeProblemMessageBean.Builder builder = new TypeProblemMessageBean.Builder().with(type); T instance = null; while (iterator.hasNext()) { try { if (instance != null) { final String implementation = iterator.next().getClass().getName(); builder.withDuplicate(implementation); continue; } instance = iterator.next(); } catch (final ServiceConfigurationError e) { builder.withError(e.getMessage()); } } if (builder.hasReportedProblems()) { LOG.warn(builder.toMessage()); } if (instance == null) { throw new ResourceException(new TypeProblemMessageBean(type)); } return instance; }
As you can see the code uses a message bean implementation to collect information about a logging event. The same implementation is used to provide information to a raised exception.
Moving the texts out of the code requires some more work than shown in the above snippet. You have to write a message bean implementation (in this case even with a builder that replaces the string buffer from the quick and dirty example) and add the resource bundle. Please refer to Message-based I18N exceptions for details on this.
In this article, part of the series to introduce the Java library smartics Exceptions, we want to give an overview over the differences using messages beans or following the traditional path of adding exception context information to the exception instance. With this information it will be easier to decide which way to go.
For a short introduction to smartics Exceptions please refer to our blog post What is smartics-exceptions all about?.
Message Bean
A message bean is an implementation of MessageBean
. It provides information about a context. A context references information that is relevant to a given runtime situation. A message bean may be information for an event to be logged or for an exception to be raised.
This is a sample implementation of a simple message bean:
@ParentMessageParam("cause=causeMessage:message") public class ImportMessageBean extends AbstractMessageBean { private static final long serialVersionUID = 1L; @MessageParam private final String sourceId; public ImportMessageBean(final String sourceId) { this(null, sourceId); } public ImportMessageBean( final Throwable cause, final String sourceId) { super(ResourceCode.IMPORT_FAILED, cause); this.sourceId = sourceId; } public static ImportMessageBean failure( final Throwable cause, final String sourceId) { return new ImportMessageBean(cause, sourceId); } }
The message bean extends AbstractMessageBean
and therefore concentrates on providing the context information it specializes on: a source ID in form of a String.
The factory method is optional and simply shortens the statement to throw the exception. We will provide an example for using this method below.
For details on codes, message param annotations, etc. please refer to What is smartics-exceptions all about? or the project’s home page, especially Quickstart and How to write I18N exceptions.
Exception Type
Writing a message bean based exception type is quite simple:
public class ImportException extends AbstractMessageRuntimeException { private static final long serialVersionUID = 1L; public ImportException(final ImportMessageBean messageBean) { super(messageBean); } }
In contrast, instead of one simple constructor expecting an instance of the message bean, exceptions not based on message beans, require constructors that allow to pass in the exception context information. This is the traditional approach to declare exceptions with Java:
@ParentMessageParam("cause=causeMessage:message") public class ImportException extends AbstractLocalizedRuntimeException { private static final long serialVersionUID = 1L; @MessageParam private final String sourceId; public ImportException(final String sourceId) { this(null, sourceId); } public ImportException( final Throwable cause, final String sourceId) { super(cause, ResourceCode.IMPORT_FAILED); this.sourceId = sourceId; } }
Exception Code and Message Bundle
The exception code enumeration and message bundle (a properties file) are not shown, since the implementation is the same for both approaches.
Throwing Exceptions
With message beans, the act of raising an exception requires a little more effort. You have to create two instances, one exception and one message bean, either directly ...
catch (final IOException e) { throw new ImportException( new ImportMessageBean(e, locationString)); }
... or via a factory method:
catch (final IOException e) { throw new ImportException(failure(e, locationString)); }
In case of none-message-based exceptions, only the exception instance has to be created:
catch (final IOException e) { throw new ImportException(e, locationString); }
Conclusion
It is usually easier to use exceptions that provide the context information by themselves. The additional effort usually pays off, if context information is to be used by exceptions of different class hierarchies (especially runtime and checked exceptions), or for logging and exception events alike.