KeyedMessage provides an automatically derived i18n message key based on its static instance definition and can be
used as comparable constant too.
Purpose
With a KeyedMessage a named constant message (format) can be statically defined which automatically translate
themselves for a specific locale using an automatically derived ResourceBundle or even a specified one.
Key derivation
Because KeyedMessages are created with a default message (format), even if no ResourceBundle or its key is defined or
can't be found, message translation is still possible.
A KeyedMessage automatically derives the ResourceBundle lookup key from its (statically defined) instance field name
using the following format:
<containingClass.name>.<staticInstanceField.name>
The containingClass is derived at construction time by analyzing the StackTraceElements of a thrown exception. This
requires the instance to be defined as a public static field!
At first access, the key is resolved by inspecting the derived containingClass for the declared field
defining this instance.
If the KeyedMessage instance wasn't defined as public static field, the key can't be resolved and
message translation using a ResourceBundle won't be possible. Translation using the default message will still work
though. Furthermore, this instance can't be used as comparable named constant as the
KeyedMessage.equals(Object) method
will always return false in this case.
Default ResourceBundle name derivation
When the key of a KeyedMessage is resolved, the default ResourceBundle name for message translation is retrieved from
the defined public static String field named
KeyedMessage.KEYED_MESSAGE_BUNDLE_FIELD_NAME "KEYED_MESSAGE_BUNDLE" defined
in its containingClass or one of its superClasses or interfaces.
If this field cannot be found, the fully qualified name of the containingClass is used.
ResourceBundle names are cached in a Map for each containingClass and only derived for the first KeyedMessage defined
in a containingClass.
Again: only resolved instances can use a ResourceBundle for message translation.
Default Locale lookup
When a message is translated without a specified Locale,
CurrentLocale.get is used to determine the default
Locale for the current Thread.
In Jetspeed, the LocalizationValve initializes the
CurrentLocale on each request.
KeyedMessages accessed within the context of an Jetspeed request therefore will always be translated using the
current user Locale with the
KeyedMessage.getMessage() or
KeyedMessage.toString() methods.
Default ResourceBundle lookup
If a message translation is done using the default ResourceBundle name the ResourceBundle is retrieved using the
ClassLoader of the containingClass. This means the bundle(s) must be provided in the same context as from where the
containingClass is loaded. Usually (and preferably), this will be from the shared classpath of the webserver.
MessageFormat parameters
MessageFormat patterns can also be used for a KeyedMessage.
With the
KeyedMessage.create(Object[]) method a specialized copy of a KeyedMessage instance can be created containing the
arguments to be used during message translation.
This new copy remains
equals(Object) to its source and can still be used for named constant comparison.
For simplified usage, three
KeyedMessage.create(Object) ,
KeyedMessage.create(Object,Object) and
KeyedMessage.create(Object,Object,Object) methods are provided which delegate to
KeyedMessage.create(Object[]) with their
argument(s) transformed into an Object array.
Extending KeyedMessage
An statically defined KeyedMessage can be used as a "simple" named constant. If additional metadata is required
like some kind of status, level or type indication, the KeyedMessage class can easily be extended by providing a
specialized version of the
KeyedMessage.create(KeyedMessage,Object[]) copy factory.
Usage
KeyedMessage has been used to replace the hardcoded
SecurityException String constants. The
ResourceBundle name used is defined by
JetspeedException.KEYED_MESSAGE_BUNDLE which is the superClass of
SecurityException .
For a different ResourceBundle to be used for SecurityException messages a KEYED_MESSAGE_BUNDLE field can be defined
in
SecurityException too, overriding the one in
JetspeedException .
Example:
public class JetspeedException extends Exception {
public static final String KEYED_MESSAGE_BUNDLE = "org.apache.jetspeed.exception.JetspeedExceptionMessages";
...
public String getMessage() {
if ( keyedMessage != null ) {
return keyedMessage.getMessage(); // translated using current Locale and default ResourceBundle
}
return super.getMessage();
}
}
public class SecurityException extends JetspeedException {
public static final KeyedMessage USER_DOES_NOT_EXIST = new KeyedMessage("The user {0} does not exist.");
...
}
// resource file: org.apache.jetspeed.exception.JetspeedExceptionMessages_nl.properties
org.apache.jetspeed.security.SecurityException.USER_DOES_NOT_EXIST = De gebruiker {0} bestaat niet.
...
public class UserManagerImpl implements UserManager {
public User getUser(String username) throws SecurityException {
...
if (null == userPrincipal) {
throw new SecurityException(SecurityException.USER_DOES_NOT_EXIST.create(username));
}
...
}
...
}
// example get User
try {
User user = userManager.getUser(userName);
} catch (SecurityException sex) {
if ( SecurityException.USER_DOES_NOT_EXISTS.equals(sex.getKeyedMessage()) {
// handle USER_DOES_NOT_EXISTS error
}
}
author: Ate Douma version: $Id: KeyedMessage.java 516448 2007-03-09 16:25:47Z ate $ |