diff --git a/projects/samskivert/src/java/com/samskivert/servlet/MessageManager.java b/projects/samskivert/src/java/com/samskivert/servlet/MessageManager.java
new file mode 100644
index 00000000..928f611b
--- /dev/null
+++ b/projects/samskivert/src/java/com/samskivert/servlet/MessageManager.java
@@ -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 ResourceBundle
+ * 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 MessageFormat 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;
+}
diff --git a/projects/samskivert/src/java/com/samskivert/webmacro/Application.java b/projects/samskivert/src/java/com/samskivert/webmacro/Application.java
new file mode 100644
index 00000000..bf1d5ab6
--- /dev/null
+++ b/projects/samskivert/src/java/com/samskivert/webmacro/Application.java
@@ -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 MsgTool 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";
+}
diff --git a/projects/samskivert/src/java/com/samskivert/webmacro/DispatcherServlet.java b/projects/samskivert/src/java/com/samskivert/webmacro/DispatcherServlet.java
index 43d6d893..65e8e561 100644
--- a/projects/samskivert/src/java/com/samskivert/webmacro/DispatcherServlet.java
+++ b/projects/samskivert/src/java/com/samskivert/webmacro/DispatcherServlet.java
@@ -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
- if (appcl != null) {
- app.init(appcl, getServletContext());
- }
+ // instantiate the specified application class
+ Application app;
- // construct an application object and add it to our list
- _apps.add(app);
+ try {
+ // if a custom application class was specified,
+ // instantiate one of those. otherwise use the default
+ if (appcl != null) {
+ Class appclass = Class.forName(appcl);
+ app = (Application)appclass.newInstance();
+ } else {
+ app = new Application();
+ }
+
+ // 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)
- if (logic != null) {
- logic.invoke(ctx);
+ 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;
- }
+ 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_%";
}
diff --git a/projects/samskivert/src/java/com/samskivert/webmacro/MsgTool.java b/projects/samskivert/src/java/com/samskivert/webmacro/MsgTool.java
index 3ad90837..7ded2b40 100644
--- a/projects/samskivert/src/java/com/samskivert/webmacro/MsgTool.java
+++ b/projects/samskivert/src/java/com/samskivert/webmacro/MsgTool.java
@@ -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";
}