git-svn-id: https://samskivert.googlecode.com/svn/trunk@2021 6335cc39-0255-0410-8fd6-9bcaacd3b74c
This commit is contained in:
mdb
2006-12-22 00:49:12 +00:00
parent 701540f332
commit 44452a0431
@@ -31,26 +31,23 @@ import com.samskivert.text.MessageUtil;
import com.samskivert.util.StringUtil; import com.samskivert.util.StringUtil;
/** /**
* The message manager handles the translation messages for a web * The message manager handles the translation messages for a web application. The webapp should
* application. The webapp should construct the message manager with the * construct the message manager with the name of its message properties file and it can then make
* name of its message properties file and it can then make use of the * use of the message manager to generate locale specific messages for a request.
* message manager to generate locale specific messages for a request.
*/ */
public class MessageManager public class MessageManager
{ {
/** /**
* Constructs a message manager with the specified bundle path. The * Constructs a message manager with the specified bundle path. The message manager will be
* message manager will be instantiating a <code>ResourceBundle</code> * instantiating a <code>ResourceBundle</code> with the supplied path, so it should conform to
* with the supplied path, so it should conform to the naming * the naming conventions defined by that class.
* conventions defined by that class.
* *
* @param siteIdent an optional site identifier that can be used to * @param siteIdent an optional site identifier that can be used to identify the site via which
* identify the site via which an http request was made. * an http request was made.
* *
* @see java.util.ResourceBundle * @see java.util.ResourceBundle
*/ */
public MessageManager (String bundlePath, Locale deflocale, public MessageManager (String bundlePath, Locale deflocale, SiteIdentifier siteIdent)
SiteIdentifier siteIdent)
{ {
// keep these for later // keep these for later
_bundlePath = bundlePath; _bundlePath = bundlePath;
@@ -59,19 +56,16 @@ public class MessageManager
} }
/** /**
* If the message manager is to be used in a multi-site environment, * If the message manager is to be used in a multi-site environment, it can be configured to
* it can be configured to load site-specific message resources in * load site-specific message resources in addition to the default application message
* addition to the default application message resources. It must be * resources. It must be configured with the facilities to load site-specific resources by a
* configured with the facilities to load site-specific resources by a
* call to this function. * call to this function.
* *
* @param siteBundlePath the path to the site-specific message * @param siteBundlePath the path to the site-specific message resources.
* resources. * @param siteLoader a site-specific resource loader, properly configured with a site
* @param siteLoader a site-specific resource loader, properly * identifier.
* configured with a site identifier.
*/ */
public void activateSiteSpecificMessages (String siteBundlePath, public void activateSiteSpecificMessages (String siteBundlePath, SiteResourceLoader siteLoader)
SiteResourceLoader siteLoader)
{ {
_siteBundlePath = siteBundlePath; _siteBundlePath = siteBundlePath;
_siteLoader = siteLoader; _siteLoader = siteLoader;
@@ -86,9 +80,8 @@ public class MessageManager
} }
/** /**
* Looks up the message with the specified path in the resource bundle * Looks up the message with the specified path in the resource bundle most appropriate for the
* most appropriate for the locales described as preferred by the * locales described as preferred by the request. Always reports missing paths.
* request. Always reports missing paths.
*/ */
public String getMessage (HttpServletRequest req, String path) public String getMessage (HttpServletRequest req, String path)
{ {
@@ -96,31 +89,27 @@ public class MessageManager
} }
/** /**
* Looks up the message with the specified path in the resource bundle * Looks up the message with the specified path in the resource bundle most appropriate for the
* most appropriate for the locales described as preferred by the * locales described as preferred by the request, then substitutes the supplied arguments into
* request, then substitutes the supplied arguments into that message * that message using a <code>MessageFormat</code> object.
* using a <code>MessageFormat</code> object.
* *
* @see java.text.MessageFormat * @see java.text.MessageFormat
*/ */
public String getMessage (HttpServletRequest req, String path, public String getMessage (HttpServletRequest req, String path, Object[] args)
Object[] args)
{ {
String msg = getMessage(req, path, true); String msg = getMessage(req, path, true);
// we may cache message formatters later, but for now just use the // we may cache message formatters later, but for now just use the static convenience
// static convenience function // function
return MessageFormat.format(MessageUtil.escape(msg), args); return MessageFormat.format(MessageUtil.escape(msg), args);
} }
/** /**
* Looks up the message with the specified path in the resource bundle * Looks up the message with the specified path in the resource bundle most appropriate for the
* most appropriate for the locales described as preferred by the * locales described as preferred by the request. If requested it will log a missing path and
* request. If requested it will log a missing path and return the * return the path as the translation (which should make it obvious in the servlet that the
* path as the translation (which should make it obvious in the * translation is missing) otherwise it returns null.
* servlet that the translation is missing) otherwise it returns null.
*/ */
protected String getMessage (HttpServletRequest req, String path, protected String getMessage (HttpServletRequest req, String path, boolean reportMissing)
boolean reportMissing)
{ {
if (path == null) { if (path == null) {
return "[null message key]"; return "[null message key]";
@@ -139,8 +128,8 @@ public class MessageManager
String[] args = StringUtil.split(argstr, "|"); String[] args = StringUtil.split(argstr, "|");
// unescape and translate the arguments // unescape and translate the arguments
for (int i = 0; i < args.length; i++) { for (int i = 0; i < args.length; i++) {
// if the argument is tainted, do no further translation // if the argument is tainted, do no further translation (it might contain |s or
// (it might contain |s or other fun stuff) // other fun stuff)
if (args[i].startsWith(MessageUtil.TAINT_CHAR)) { if (args[i].startsWith(MessageUtil.TAINT_CHAR)) {
args[i] = MessageUtil.unescape(args[i].substring(1)); args[i] = MessageUtil.unescape(args[i].substring(1));
} else { } else {
@@ -150,9 +139,9 @@ public class MessageManager
return getMessage(req, key, args); return getMessage(req, key, args);
} }
// load up the matching resource bundles (the array will contain the // load up the matching resource bundles (the array will contain the site-specific
// site-specific resources first and the application resources second); // resources first and the application resources second); use the locale preferred by the
// use the locale preferred by the client if possible // client if possible
ResourceBundle[] bundles = resolveBundles(req); ResourceBundle[] bundles = resolveBundles(req);
String message = null; String message = null;
if (bundles != null) { if (bundles != null) {
@@ -163,8 +152,7 @@ public class MessageManager
message = bundles[i].getString(path); message = bundles[i].getString(path);
} }
} catch (MissingResourceException mre) { } catch (MissingResourceException mre) {
// no complaints, just try the bundle in the enclosing // no complaints, just try the bundle in the enclosing scope
// scope
} }
} }
} }
@@ -182,13 +170,11 @@ public class MessageManager
// avoid trivial infinite recursion // avoid trivial infinite recursion
if (ref.equals(path)) { if (ref.equals(path)) {
throw new IllegalStateException( throw new IllegalStateException(
"Illegal self-referential message " + path + " = " + "Illegal self-referential message " + path + " = " + message + ".");
message + ".");
} }
if (ref.length() > 0 && !Character.isDigit(ref.charAt(0))) { if (ref.length() > 0 && !Character.isDigit(ref.charAt(0))) {
String refmsg = getMessage(req, ref, true); String refmsg = getMessage(req, ref, true);
message = message.substring(0, oidx) + message = message.substring(0, oidx) + refmsg + message.substring(cidx+1);
refmsg + message.substring(cidx+1);
oidx += refmsg.length(); oidx += refmsg.length();
} }
} }
@@ -206,13 +192,12 @@ public class MessageManager
} }
/** /**
* Finds the closest matching resource bundle for the locales * Finds the closest matching resource bundle for the locales specified as preferred by the
* specified as preferred by the client in the supplied http request. * client in the supplied http request.
*/ */
protected ResourceBundle[] resolveBundles (HttpServletRequest req) protected ResourceBundle[] resolveBundles (HttpServletRequest req)
{ {
// first look to see if we've cached the bundles for this request // first look to see if we've cached the bundles for this request in the request object
// in the request object
ResourceBundle[] bundles = null; ResourceBundle[] bundles = null;
if (req != null) { if (req != null) {
bundles = (ResourceBundle[])req.getAttribute(getBundleCacheName()); bundles = (ResourceBundle[])req.getAttribute(getBundleCacheName());
@@ -245,15 +230,14 @@ public class MessageManager
if (siteLoader != null) { if (siteLoader != null) {
bundles[0] = resolveBundle(req, _siteBundlePath, siteLoader, false); bundles[0] = resolveBundle(req, _siteBundlePath, siteLoader, false);
} else if (siteString != null) { } else if (siteString != null) {
// or just try prefixing the site string to the normal bundle path // or just try prefixing the site string to the normal bundle path and see if that
// and see if that turns something up // turns something up
bundles[0] = resolveBundle(req, siteString + "_" + _bundlePath, bundles[0] = resolveBundle(req, siteString + "_" + _bundlePath,
getClass().getClassLoader(), true); getClass().getClassLoader(), true);
} }
// then from the default classloader // then from the default classloader
bundles[1] = resolveBundle( bundles[1] = resolveBundle(req, _bundlePath, getClass().getClassLoader(), false);
req, _bundlePath, getClass().getClassLoader(), false);
// if we found either or both bundles, cache 'em // if we found either or both bundles, cache 'em
if (bundles[0] != null || bundles[1] != null && req != null) { if (bundles[0] != null || bundles[1] != null && req != null) {
@@ -264,12 +248,11 @@ public class MessageManager
} }
/** /**
* Resolves the default resource bundle based on the locale * Resolves the default resource bundle based on the locale information provided in the
* information provided in the supplied http request object. * supplied http request object.
*/ */
protected ResourceBundle resolveBundle ( protected ResourceBundle resolveBundle (HttpServletRequest req, String bundlePath,
HttpServletRequest req, String bundlePath, ClassLoader loader, ClassLoader loader, boolean silent)
boolean silent)
{ {
ResourceBundle bundle = null; ResourceBundle bundle = null;
@@ -279,16 +262,13 @@ public class MessageManager
Locale locale = (Locale)locales.nextElement(); Locale locale = (Locale)locales.nextElement();
try { try {
// java caches resource bundles, so we don't need to // java caches resource bundles, so we don't need to reinvent the wheel here.
// reinvent the wheel here. however, java also falls back // however, java also falls back from a specific bundle to a more general one
// from a specific bundle to a more general one if it // if it can't find a specific bundle. that's real nice of it, but we want
// can't find a specific bundle. that's real nice of it, // first to see whether or not we have exact matches on any of the preferred
// but we want first to see whether or not we have exact // locales specified by the client. if we don't, then we can rely on java's
// matches on any of the preferred locales specified by
// the client. if we don't, then we can rely on java's
// fallback mechanisms // fallback mechanisms
bundle = ResourceBundle.getBundle( bundle = ResourceBundle.getBundle(bundlePath, locale, loader);
bundlePath, locale, loader);
// if it's an exact match, off we go // if it's an exact match, off we go
if (bundle.getLocale().equals(locale)) { if (bundle.getLocale().equals(locale)) {
@@ -296,28 +276,25 @@ public class MessageManager
} }
} catch (MissingResourceException mre) { } catch (MissingResourceException mre) {
// no need to freak out quite yet, see if we have // no need to freak out quite yet, see if we have something for one of the
// something for one of the other preferred locales // other preferred locales
} }
} }
} }
// if we were unable to find an exact match for any of the user's // if we were unable to find an exact match for any of the user's preferred locales, take
// preferred locales, take their most preferred and let java // their most preferred and let java perform it's fallback logic on that one
// perform it's fallback logic on that one
if (bundle == null) { if (bundle == null) {
Locale locale = (req == null) ? _deflocale : req.getLocale(); Locale locale = (req == null) ? _deflocale : req.getLocale();
try { try {
bundle = ResourceBundle.getBundle(bundlePath, locale, loader); bundle = ResourceBundle.getBundle(bundlePath, locale, loader);
} catch (MissingResourceException mre) { } catch (MissingResourceException mre) {
if (!silent) { if (!silent) {
// if we were unable even to find a default bundle, we may // if we were unable even to find a default bundle, we may want to log a
// want to log a warning // warning
Log.warning("Unable to resolve any message bundle " + Log.warning("Unable to resolve any message bundle [req=" + getURL(req) +
"[req=" + getURL(req) + ", locale=" + locale + ", locale=" + locale + ", bundlePath=" + bundlePath +
", bundlePath=" + bundlePath + ", classLoader=" + loader + ", siteBundlePath=" + _siteBundlePath +
", classLoader=" + loader +
", siteBundlePath=" + _siteBundlePath +
", siteLoader=" + _siteLoader + "]."); ", siteLoader=" + _siteLoader + "].");
} }
} }
@@ -341,23 +318,20 @@ public class MessageManager
/** The path, relative to the classpath, to our resource bundles. */ /** The path, relative to the classpath, to our resource bundles. */
protected String _bundlePath; protected String _bundlePath;
/** The path to the site-specific message bundles, fetched via the /** The path to the site-specific message bundles, fetched via the site-specific resource
* site-specific resource loader. */ * loader. */
protected String _siteBundlePath; protected String _siteBundlePath;
/** The resource loader with which to fetch our site-specific message /** The resource loader with which to fetch our site-specific message bundles. */
* bundles. */
protected SiteResourceLoader _siteLoader; protected SiteResourceLoader _siteLoader;
/** The site identifier we use to determine through which site a /** The site identifier we use to determine through which site a request was made. */
* request was made. */
protected SiteIdentifier _siteIdent; protected SiteIdentifier _siteIdent;
/** The locale to use if we are accessed without an HTTP request. */ /** The locale to use if we are accessed without an HTTP request. */
protected Locale _deflocale; protected Locale _deflocale;
/** The attribute name that we use for caching resource bundles in /** The attribute name that we use for caching resource bundles in request objects. */
* request objects. */
protected static final String BUNDLE_CACHE_PREFIX = protected static final String BUNDLE_CACHE_PREFIX =
"com.samskivert.servlet.MessageManager:CachedResourceBundle:"; "com.samskivert.servlet.MessageManager:CachedResourceBundle:";
} }