Modified things to make handling of message resource bundles easier. We

now have an actual application class that's used by dispatched
applications. The message manager provides an easy interface into
localizing messages and MsgTool has a way to get at the message manager in
effect for whatever application is handling a request.


git-svn-id: https://samskivert.googlecode.com/svn/trunk@88 6335cc39-0255-0410-8fd6-9bcaacd3b74c
This commit is contained in:
mdb
2001-03-04 06:15:39 +00:00
parent f96b4cc237
commit 81710f70ec
4 changed files with 360 additions and 181 deletions
@@ -0,0 +1,145 @@
//
// $Id: MessageManager.java,v 1.1 2001/03/04 06:15:39 mdb Exp $
package com.samskivert.servlet;
import java.text.MessageFormat;
import java.util.*;
import javax.servlet.http.HttpServletRequest;
import com.samskivert.Log;
/**
* The message manager handles the translation messages for a web
* application. The webapp should construct the message manager with the
* name of its message properties file and it can then make use of the
* message manager to generate locale specific messages for a request.
*/
public class MessageManager
{
/**
* Constructs a message manager with the specified bundle path. The
* message manager will be instantiating a <code>ResourceBundle</code>
* with the supplied path, so it should conform to the naming
* conventions defined by that class.
*
* @see java.util.ResourceBundle
*/
public MessageManager (String bundlePath)
{
// keep this for later
_bundlePath = bundlePath;
}
/**
* Looks up the message with the specified path in the resource bundle
* most appropriate for the locales described as preferred by the
* request.
*/
public String getMessage (HttpServletRequest req, String path)
{
try {
// use the locale preferred by the client if possible
ResourceBundle bundle = resolveBundle(req);
if (bundle != null) {
return bundle.getString(path);
}
// if we couldn't find a bundle, things are way wacked out,
// but we've already complained about it so we just fall
// through and return the path back to the caller
} catch (MissingResourceException mre) {
// if there's no translation for this path, complain about it
Log.warning("Missing translation message [path=" + path + "].");
}
return path;
}
/**
* Looks up the message with the specified path in the resource bundle
* most appropriate for the locales described as preferred by the
* request, then substitutes the supplied arguments into that message
* using a <code>MessageFormat</code> object.
*
* @see java.text.MessageFormat
*/
public String getMessage (HttpServletRequest req, String path,
Object[] args)
{
try {
// use the locale preferred by the client if possible
ResourceBundle bundle = resolveBundle(req);
if (bundle != null) {
String msg = bundle.getString(path);
// we may cache message formatters later, but for now just
// use the static convenience function
return MessageFormat.format(msg, args);
}
// if we couldn't find a bundle, things are way wacked out,
// but we've already complained about it so we just fall
// through and return the path back to the caller
} catch (MissingResourceException mre) {
// if there's no translation for this path, complain about it
Log.warning("Missing translation message [path=" + path + "].");
}
return path;
}
/**
* Finds the closest matching resource bundle for the locales
* specified as preferred by the client in the supplied http request.
*/
protected ResourceBundle resolveBundle (HttpServletRequest req)
throws MissingResourceException
{
Enumeration locales = req.getLocales();
while (locales.hasMoreElements()) {
Locale locale = (Locale)locales.nextElement();
try {
// java caches resource bundles, so we don't need to
// reinvent the wheel here. however, java also falls back
// from a specific bundle to a more general one if it
// can't find a specific bundle. that's real nice of it,
// but we want first to see whether or not we have exact
// matches on any of the preferred locales specified by
// the client. if we don't, then we can rely on java's
// fallback mechanisms
ResourceBundle bundle =
ResourceBundle.getBundle(_bundlePath, locale);
// if it's an exact match, off we go
if (bundle.getLocale().equals(locale)) {
return bundle;
}
} catch (MissingResourceException mre) {
// no need to freak out quite yet, see if we have
// something for one of the other preferred locales
}
}
try {
// if we were unable to find an exact match for any of the
// user's preferred locales, take their most preferred and let
// java perform it's fallback logic on that one
return ResourceBundle.getBundle(_bundlePath, req.getLocale());
} catch (MissingResourceException mre) {
// if we were unable even to find a default bundle, we've got
// real problems. time to freak out
Log.warning("Unable to resolve any message bundle " +
"[bundlePath=" + _bundlePath +
", locale=" + req.getLocale() + "].");
return null;
}
}
protected String _bundlePath;
}
@@ -0,0 +1,103 @@
//
// $Id: Application.java,v 1.1 2001/03/04 06:15:39 mdb Exp $
package com.samskivert.webmacro;
import javax.servlet.ServletContext;
import com.samskivert.servlet.MessageManager;
import com.samskivert.util.StringUtil;
/**
* The servlet API defines the concept of a web application and associates
* certain attributes with it like document root and so on. This
* application class extends that concept by providing a base class that
* represents the web application. The application class is responsible
* for initializing services that will be used by the application's logic
* objects as well as cleaning them up when the application is shut down.
*/
public class Application
{
/**
* This should be overridden by the application implementation to
* perform any necessary initialization.
*/
public void init (ServletContext context)
{
}
/**
* This should be overridden by the application implementation to
* perform any necessary cleanup.
*/
public void shutdown ()
{
}
/**
* If an application wishes to make use of the translation facilities
* provided by the message manager, it can instantiate one and make it
* available through this member function. This allows framework
* components like the <code>MsgTool</code> to make use of the
* application's message bundles.
*/
public MessageManager getMessageManager ()
{
return null;
}
/**
* The default application implementation takes the base URI and base
* package as defined in the application declaration in the servlet
* configuration and uses those to determine whether or not a given
* URI maps to this application.
*/
public void setConfig (String baseURI, String basePkg)
{
// remove any trailing slash
if (baseURI.endsWith("/")) {
_baseURI = baseURI.substring(0, baseURI.length()-1);
} else {
_baseURI = baseURI;
}
// remove any trailing dot
if (basePkg.endsWith(".")) {
_basePkg = basePkg.substring(0, basePkg.length()-1);
} else {
_basePkg = basePkg;
}
}
/**
* Returns true if the supplied URI should be handled by this
* application.
*/
public boolean matches (String uri)
{
return uri.startsWith(_baseURI);
}
/**
* Given a request URI this generates the classname of the logic class
* that should handle the request.
*/
public String generateClass (String uri)
{
// remove the base URI
uri = uri.substring(_baseURI.length());
// convert slashes to dots
uri = StringUtil.replace(uri, "/", ".");
// remove the trailing file extension
uri = uri.substring(0, uri.length() - FILE_EXTENSION.length());
// prepend the base package and we're all set
return _basePkg + uri;
}
protected String _baseURI;
protected String _basePkg;
/**
* This is the default file extension.
*/
protected static final String FILE_EXTENSION = ".wm";
}
@@ -1,5 +1,5 @@
//
// $Id: DispatcherServlet.java,v 1.6 2001/03/03 21:20:13 mdb Exp $
// $Id: DispatcherServlet.java,v 1.7 2001/03/04 06:15:39 mdb Exp $
package com.samskivert.webmacro;
@@ -17,7 +17,9 @@ import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import org.webmacro.*;
import org.webmacro.servlet.*;
import org.webmacro.servlet.HandlerException;
import org.webmacro.servlet.WMServlet;
import org.webmacro.servlet.WebContext;
import com.samskivert.Log;
import com.samskivert.servlet.RedirectException;
@@ -169,14 +171,31 @@ public class DispatcherServlet extends WMServlet
continue;
}
Application app = new Application(baseURI, basePkg);
// if an application class was specified, initialize it
// instantiate the specified application class
Application app;
try {
// if a custom application class was specified,
// instantiate one of those. otherwise use the default
if (appcl != null) {
app.init(appcl, getServletContext());
Class appclass = Class.forName(appcl);
app = (Application)appclass.newInstance();
} else {
app = new Application();
}
// construct an application object and add it to our list
// now initialize the applicaiton
app.setConfig(baseURI, basePkg);
app.init(getServletContext());
// finally add it to our list
_apps.add(app);
} catch (Throwable t) {
Log.warning("Error instantiating custom application " +
"[class=" + appcl + "].");
Log.logStackTrace(t);
}
}
}
}
@@ -205,24 +224,33 @@ public class DispatcherServlet extends WMServlet
// then we populate the context with data
try {
Logic logic = selectLogic(ctx);
// if no logic is matched, we simply execute the template
// directly with the default information in the context (tools
// and other WebMacro services can be used by the template to
// do their WebMacro thing)
String path = cleanupURI(ctx.getRequest().getRequestURI());
// select the proper application for the request
Application app = selectApplication(path);
if (app != null) {
// insert the application into the web context in case the
// logic or a tool wishes to make use of it
ctx.put(APPLICATION_KEY, app);
// resolve the appropriate logic class for this URI and
// execute it if it exists
Logic logic = resolveLogic(app, path);
if (logic != null) {
logic.invoke(ctx);
}
}
} catch (RedirectException re) {
try {
ctx.getResponse().sendRedirect(re.getRedirectURL());
return null;
} catch (IOException ioe) {
throw new HandlerException("Unable to send redirect: " + ioe);
}
} catch (DataValidationException dve) {
ctx.put(DV_ERROR_KEY, ExceptionMap.getMessage(dve));
} catch (FriendlyException fe) {
ctx.put(ERROR_KEY, fe.getMessage());
} catch (Exception e) {
ctx.put(ERROR_KEY, ExceptionMap.getMessage(e));
@@ -232,6 +260,18 @@ public class DispatcherServlet extends WMServlet
return tmpl;
}
/**
* Returns the reference to the application that is handling this
* request.
*
* @return The application in effect for this request or null if no
* application was selected to handle the request.
*/
public static Application getApplication (WebContext context)
{
return (Application)context.get(APPLICATION_KEY);
}
/**
* This method is called to select the appropriate template for this
* request. The default implementation simply loads the template using
@@ -252,39 +292,37 @@ public class DispatcherServlet extends WMServlet
}
/**
* This method is called to select the appropriate logic for this
* request. The dispatcher configuration described in this class's
* documentation is consulted to map the URI to a logic class which is
* then instantiated and a single instance used to process all
* matching requests.
* Selects and returns the matching application for this request.
*
* @param ctx The context of this request.
*
* @return The logic to be used in generating the response or null if
* no logic could be matched.
* @return the application that should handle this request or null if
* no matching application could be found.
*/
protected Logic selectLogic (WebContext ctx)
protected Application selectApplication (String path)
{
String path = cleanupURI(ctx.getRequest().getRequestURI());
String lclass = null;
// Log.info("Loading logic [path=" + path + "].");
// try to locate an application that matches this URI
for (int i = 0; i < _apps.size(); i++) {
Application app = (Application)_apps.get(i);
if (app.matches(path)) {
lclass = app.generateClass(path);
break;
return app;
}
}
// if we didn't find a matching application, we can stop now
if (lclass == null) {
return null;
}
// otherwise look for a cached logic instance
/**
* This method is called to select the appropriate logic for this
* request URI.
*
* @return The logic to be used in generating the response or null if
* no logic could be matched.
*/
protected Logic resolveLogic (Application app, String path)
{
// look for a cached logic instance
String lclass = app.generateClass(path);
Logic logic = (Logic)_logic.get(lclass);
if (logic == null) {
try {
Class pcl = Class.forName(lclass);
@@ -337,107 +375,6 @@ public class DispatcherServlet extends WMServlet
System.out.println(cleanupURI("//samskivert.com/is/this/even/legal"));
}
protected static class Application
{
public Application (String baseURI, String basePkg)
{
// remove any trailing slash
if (baseURI.endsWith("/")) {
_baseURI = baseURI.substring(0, baseURI.length()-1);
} else {
_baseURI = baseURI;
}
// remove any trailing dot
if (basePkg.endsWith(".")) {
_basePkg = basePkg.substring(0, basePkg.length()-1);
} else {
_basePkg = basePkg;
}
}
public boolean matches (String uri)
{
return uri.startsWith(_baseURI);
}
public String generateClass (String uri)
{
// remove the base URI
uri = uri.substring(_baseURI.length());
// convert slashes to dots
uri = StringUtil.replace(uri, "/", ".");
// remove the trailing file extension
uri = uri.substring(0, uri.length() - FILE_EXTENSION.length());
// prepend the base package and we're all set
return _basePkg + uri;
}
public void init (String appclass, ServletContext context)
{
// keep track of this for later
_appclass = appclass;
try {
// get the application class
Class appcl = Class.forName(appclass);
// look up the init method
Class[] ptypes = new Class[] { ServletContext.class };
Method initmeth = appcl.getDeclaredMethod("init", ptypes);
// make sure it's static
if ((initmeth.getModifiers() & Modifier.STATIC) == 0) {
Log.warning("Application init() method not static " +
"[app=" + appclass + "].");
return;
}
// look up the shutdown method
_shutdownMethod =
appcl.getDeclaredMethod("shutdown", new Class[0]);
// make sure it's static
if ((_shutdownMethod.getModifiers() & Modifier.STATIC) == 0) {
Log.warning("Application shutdown() method not static " +
"[app=" + appclass + "].");
return;
}
// invoke the init method
Object[] args = new Object[] { context };
initmeth.invoke(null, args);
} catch (NoSuchMethodError nsme) {
Log.warning("Application class missing init() method " +
"[app=" + appclass + "].");
} catch (Throwable t) {
Log.warning("Error initializing application [app=" + appclass +
", error=" + t + "].");
}
}
public void shutdown ()
{
if (_shutdownMethod != null) {
try {
_shutdownMethod.invoke(null, null);
} catch (Throwable t) {
Log.warning("Error shutting down application " +
"[app=" + _appclass + ", error=" + t + "].");
Log.logStackTrace(t);
}
}
}
protected String _baseURI;
protected String _basePkg;
protected String _appclass;
protected Method _shutdownMethod;
}
protected ArrayList _apps = new ArrayList();
protected HashMap _logic = new HashMap();
@@ -445,13 +382,8 @@ public class DispatcherServlet extends WMServlet
protected static final String ERROR_KEY = "error";
/**
* This is the key used in the context for data validation error
* messages.
* This is the key used to store a reference back to the dispatcher
* servlet in our web context.
*/
protected static final String DV_ERROR_KEY = "invalid_data";
/**
* This is the default file extension.
*/
protected static final String FILE_EXTENSION = ".wm";
protected static final String APPLICATION_KEY = "%_app_%";
}
@@ -1,18 +1,15 @@
//
// $Id: MsgTool.java,v 1.1 2001/03/03 21:21:28 mdb Exp $
// $Id: MsgTool.java,v 1.2 2001/03/04 06:15:39 mdb Exp $
package com.samskivert.webmacro;
import java.text.MessageFormat;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import org.webmacro.*;
import org.webmacro.servlet.WebContext;
import org.webmacro.util.PropertyObject;
import org.webmacro.util.PropertyMethod;
import com.samskivert.Log;
import com.samskivert.servlet.MessageManager;
/**
* The message tool maps a set of appliation messages (translation
@@ -30,19 +27,27 @@ public class MsgTool implements ContextTool
try {
WebContext wctx = (WebContext)ctx;
// first fetch our messages resource bundle
final ResourceBundle messages =
ResourceBundle.getBundle(MESSAGE_FILE_NAME);
// get a handle on the application in effect for this request
Application app = DispatcherServlet.getApplication(wctx);
if (app == null) {
String err = "No application in effect for this request. " +
"Can't resolve messages without an application.";
throw new InvalidContextException(err);
}
// get the message manager from the application
MessageManager msgmgr = app.getMessageManager();
if (msgmgr == null) {
String err = "Application did not provide a message " +
"manager. Can't resolve messages without one.";
throw new InvalidContextException(err);
}
// then create a wrapper that allows webmacro to access it
return new Wrapper(messages);
} catch (MissingResourceException mre) {
Log.warning("Unable to load message bundle: " + mre);
return new Wrapper(null);
return new Wrapper(msgmgr);
} catch (ClassCastException cce) {
throw new InvalidContextException("error.requires_webcontext");
throw new InvalidContextException("MsgTool requires a WebContext");
}
}
@@ -53,9 +58,9 @@ public class MsgTool implements ContextTool
public static class Wrapper implements PropertyObject
{
public Wrapper (ResourceBundle bundle)
public Wrapper (MessageManager msgmgr)
{
_bundle = bundle;
_msgmgr = msgmgr;
}
public Object getProperty (Context context, Object[] names, int offset)
@@ -64,6 +69,7 @@ public class MsgTool implements ContextTool
StringBuffer path = new StringBuffer();
boolean bogus = false;
int lastidx = names.length-1;
WebContext wctx = (WebContext)context;
// reconstruct the path to the property
for (int i = offset; i < names.length; i++) {
@@ -95,21 +101,20 @@ public class MsgTool implements ContextTool
"path: " + path);
}
// look up the translated message
String msg = _bundle.getString(path.toString());
// if the last component is a property method, we want to
// substitute it's arguments into the returned string using a
// message formatter
// if the last component is a property method, we want to use
// it's arguments when looking up the message
if (names[lastidx] instanceof PropertyMethod) {
PropertyMethod pm = (PropertyMethod)names[lastidx];
// we may cache message formatters later, but for now just
// use the static convenience function
Object[] args = pm.getArguments(context);
msg = MessageFormat.format(msg, args);
}
return _msgmgr.getMessage(wctx.getRequest(),
path.toString(), args);
return msg;
} else {
// otherwise just look up the path
return _msgmgr.getMessage(wctx.getRequest(), path.toString());
}
}
public boolean setProperty (Context context, Object[] names, int offset,
@@ -120,12 +125,6 @@ public class MsgTool implements ContextTool
"is not supported!");
}
protected ResourceBundle _bundle;
protected MessageManager _msgmgr;
}
/**
* The file name of the resource file that contains our translation
* strings.
*/
protected static final String MESSAGE_FILE_NAME = "messages";
}