diff --git a/projects/samskivert/src/java/com/samskivert/servlet/util/DataValidationException.java b/projects/samskivert/src/java/com/samskivert/servlet/util/DataValidationException.java new file mode 100644 index 00000000..bc39835f --- /dev/null +++ b/projects/samskivert/src/java/com/samskivert/servlet/util/DataValidationException.java @@ -0,0 +1,35 @@ +// +// $Id: DataValidationException.java,v 1.1 2001/10/31 09:44:22 mdb Exp $ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2001 Michael Bayne +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.samskivert.servlet.util; + +/** + * A data validation exception is thrown when a value supplied in a form + * element is not valid. + * + * @see ParameterUtil + */ +public class DataValidationException extends FriendlyException +{ + public DataValidationException (String message) + { + super(message); + } +} diff --git a/projects/samskivert/src/java/com/samskivert/servlet/util/ExceptionMap.java b/projects/samskivert/src/java/com/samskivert/servlet/util/ExceptionMap.java new file mode 100644 index 00000000..79a79e77 --- /dev/null +++ b/projects/samskivert/src/java/com/samskivert/servlet/util/ExceptionMap.java @@ -0,0 +1,174 @@ +// +// $Id: ExceptionMap.java,v 1.1 2001/10/31 09:44:22 mdb Exp $ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2001 Michael Bayne +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.samskivert.servlet.util; + +import java.io.*; +import java.util.*; + +import com.samskivert.Log; +import com.samskivert.util.ConfigUtil; +import com.samskivert.util.StringUtil; + +/** + * The exception map is used to map exceptions to error messages based on + * a static, server-wide configuration. + * + *
The configuration file is loaded via the classpath. The file should
+ * be named exceptionmap.properties and placed in the
+ * classpath of the JVM in which the servlet is executed. The file should
+ * contain colon-separated mappings from exception classes to friendly
+ * error messages. For example:
+ *
+ *
+ * # Exception mappings (lines beginning with # are ignored)
+ * com.samskivert.servlet.util.FriendlyException: An error occurred while \
+ * processing your request: {m}
+ *
+ * # lines ending with \ are continued on the next line
+ * java.sql.SQLException: The database is currently unavailable. Please \
+ * try your request again later.
+ *
+ * java.lang.Exception: An unexpected error occurred while processing \
+ * your request. Please try again later.
+ *
+ *
+ * The message associated with the exception will be substituted into the
+ * error string in place of {m}. The exceptions should be
+ * listed in order of most to least specific, as the first mapping for
+ * which the exception to report is an instance of the listed exception
+ * will be used.
+ *
+ * Note: These exception mappings will generally be used for
+ * all requests (perhaps some day only for requests associated with a
+ * particular application). Regardless, this error handling mechanism
+ * should not be used for request specific errors. For example, an SQL
+ * exception reporting a duplicate key should probably be caught and
+ * reported specifically by the appropriate populator (it can still
+ * leverage the pattern of inserting the error message into the context as
+ * "error") rather than relying on the default SQL exception
+ * error message which is not likely to be meaningful for such a
+ * situation.
+ */
+public class ExceptionMap
+{
+ /**
+ * Searches for the exceptionmap.properties file in the
+ * classpath and loads it. If the file could not be found, an error is
+ * reported and a default set of mappings is used.
+ */
+ public static synchronized void init ()
+ {
+ // only initialize ourselves once
+ if (_keys != null) {
+ return;
+ } else {
+ _keys = new ArrayList();
+ _values = new ArrayList();
+ }
+
+ // first try loading the properties file without a leading slash
+ ClassLoader cld = ExceptionMap.class.getClassLoader();
+ InputStream config = ConfigUtil.getStream(PROPS_NAME, cld);
+ if (config == null) {
+ Log.warning("Unable to load " + PROPS_NAME + " from CLASSPATH.");
+
+ } else {
+ // otherwise process ye old config file.
+ try {
+ // we'll do some serious jiggery pokery to leverage the
+ // parsing implementation provided by
+ // java.util.Properties. god bless method overloading
+ Properties loader = new Properties() {
+ public Object put (Object key, Object value)
+ {
+ _keys.add(key);
+ _values.add(value);
+ return key;
+ }
+ };
+ loader.load(config);
+
+ // now cruise through and resolve the exceptions named as
+ // keys and throw out any that don't appear to exist
+ for (int i = 0; i < _keys.size(); i++) {
+ String exclass = (String)_keys.get(i);
+ try {
+ Class cl = Class.forName(exclass);
+ // replace the string with the class object
+ _keys.set(i, cl);
+
+ } catch (Throwable t) {
+ Log.warning("Unable to resolve exception class. " +
+ "[class=" + exclass +
+ ", error=" + t + "].");
+ _keys.remove(i);
+ _values.remove(i);
+ i--; // back on up a notch
+ }
+ }
+
+ } catch (IOException ioe) {
+ Log.warning("Error reading exception mapping file: " + ioe);
+ }
+ }
+ }
+
+ /**
+ * Looks up the supplied exception in the map and returns the most
+ * specific error message available for exceptions of that class.
+ *
+ * @param ex The exception to resolve into an error message.
+ *
+ * @return The error message to which this exception maps (properly
+ * populated with the message associated with this exception
+ * instance).
+ */
+ public static String getMessage (Throwable ex)
+ {
+ String msg = DEFAULT_ERROR_MSG;
+
+ for (int i = 0; i < _keys.size(); i++) {
+ Class cl = (Class)_keys.get(i);
+ if (cl.isInstance(ex)) {
+ msg = (String)_values.get(i);
+ break;
+ }
+ }
+
+ return StringUtil.replace(msg, MESSAGE_MARKER, ex.getMessage());
+ }
+
+ public static void main (String[] args)
+ {
+ ExceptionMap map = new ExceptionMap();
+ System.out.println(map.getMessage(new Exception("Test error")));
+ }
+
+ protected static List _keys;
+ protected static List _values;
+
+ // initialize ourselves
+ static { init(); }
+
+ protected static final String PROPS_NAME = "exceptionmap.properties";
+ protected static final String DEFAULT_ERROR_MSG = "Error: {m}";
+ protected static final String MESSAGE_MARKER = "{m}";
+}
diff --git a/projects/samskivert/src/java/com/samskivert/servlet/util/FriendlyException.java b/projects/samskivert/src/java/com/samskivert/servlet/util/FriendlyException.java
new file mode 100644
index 00000000..a3505728
--- /dev/null
+++ b/projects/samskivert/src/java/com/samskivert/servlet/util/FriendlyException.java
@@ -0,0 +1,34 @@
+//
+// $Id: FriendlyException.java,v 1.1 2001/10/31 09:44:22 mdb Exp $
+//
+// samskivert library - useful routines for java programs
+// Copyright (C) 2001 Michael Bayne
+//
+// This library is free software; you can redistribute it and/or modify it
+// under the terms of the GNU Lesser General Public License as published
+// by the Free Software Foundation; either version 2.1 of the License, or
+// (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+package com.samskivert.servlet.util;
+
+/**
+ * The friendly exception provides a mechanism by which a servlet or
+ * underlying code can abort its processing and report a human readable
+ * error to the servlet framework.
+ */
+public class FriendlyException extends Exception
+{
+ public FriendlyException (String message)
+ {
+ super(message);
+ }
+}
diff --git a/projects/samskivert/src/java/com/samskivert/servlet/util/HTMLUtil.java b/projects/samskivert/src/java/com/samskivert/servlet/util/HTMLUtil.java
new file mode 100644
index 00000000..e7c7b66c
--- /dev/null
+++ b/projects/samskivert/src/java/com/samskivert/servlet/util/HTMLUtil.java
@@ -0,0 +1,47 @@
+//
+// $Id: HTMLUtil.java,v 1.1 2001/10/31 09:44:22 mdb Exp $
+//
+// samskivert library - useful routines for java programs
+// Copyright (C) 2001 Michael Bayne
+//
+// This library is free software; you can redistribute it and/or modify it
+// under the terms of the GNU Lesser General Public License as published
+// by the Free Software Foundation; either version 2.1 of the License, or
+// (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+package com.samskivert.servlet.util;
+
+import com.samskivert.util.StringUtil;
+
+/**
+ * HTML related utility functions.
+ */
+public class HTMLUtil
+{
+ /**
+ * Converts instances of <, >, & and " into their
+ * entified equivalents: <, >, & and ".
+ * These characters are mentioned in the HTML spec as being common
+ * candidates for entification.
+ *
+ * @return the entified string.
+ */
+ public static String entify (String text)
+ {
+ // this could perhaps be done more efficiently, but this function
+ // is not likely to be called on large quantities of text
+ text = StringUtil.replace(text, "&", "&");
+ text = StringUtil.replace(text, "<", "<");
+ text = StringUtil.replace(text, ">", ">");
+ return StringUtil.replace(text, "\"", """);
+ }
+}
diff --git a/projects/samskivert/src/java/com/samskivert/servlet/util/ParameterUtil.java b/projects/samskivert/src/java/com/samskivert/servlet/util/ParameterUtil.java
new file mode 100644
index 00000000..c7b6b67f
--- /dev/null
+++ b/projects/samskivert/src/java/com/samskivert/servlet/util/ParameterUtil.java
@@ -0,0 +1,165 @@
+//
+// $Id: ParameterUtil.java,v 1.1 2001/10/31 09:44:22 mdb Exp $
+//
+// samskivert library - useful routines for java programs
+// Copyright (C) 2001 Michael Bayne
+//
+// This library is free software; you can redistribute it and/or modify it
+// under the terms of the GNU Lesser General Public License as published
+// by the Free Software Foundation; either version 2.1 of the License, or
+// (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+package com.samskivert.servlet.util;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.servlet.http.HttpServletRequest;
+
+import com.samskivert.Log;
+import com.samskivert.util.StringUtil;
+
+/**
+ * Utility functions for fetching and manipulating request parameters
+ * (form fields).
+ */
+public class ParameterUtil
+{
+ /**
+ * Fetches the supplied parameter from the request. If the parameter
+ * does not exist, either null or the empty string will be returned
+ * depending on the value of the returnNull parameter.
+ */
+ public static String getParameter (
+ HttpServletRequest req, String name, boolean returnNull)
+ {
+ String value = req.getParameter(name);
+ if (returnNull || !StringUtil.blank(value)) {
+ return value;
+ } else {
+ return "";
+ }
+ }
+
+ /**
+ * Fetches the supplied parameter from the request and converts it to
+ * an integer. If the parameter does not exist or is not a well-formed
+ * integer, a data validation exception is thrown with the supplied
+ * message.
+ */
+ public static int requireIntParameter (
+ HttpServletRequest req, String name, String invalidDataMessage)
+ throws DataValidationException
+ {
+ String value = getParameter(req, name, false);
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException nfe) {
+ throw new DataValidationException(invalidDataMessage);
+ }
+ }
+
+ /**
+ * Fetches the supplied parameter from the request. If the parameter
+ * does not exist, a data validation exception is thrown with the
+ * supplied message.
+ */
+ public static String requireParameter (
+ HttpServletRequest req, String name, String missingDataMessage)
+ throws DataValidationException
+ {
+ String value = req.getParameter(name);
+ if (StringUtil.blank(value)) {
+ throw new DataValidationException(missingDataMessage);
+ }
+ return value;
+ }
+
+ /**
+ * Fetches the supplied parameter from the request and converts it to
+ * a date. The value of the parameter should be a date formatted like
+ * so: 2001-12-25. If the parameter does not exist or is not a
+ * well-formed date, a data validation exception is thrown with the
+ * supplied message.
+ */
+ public static Date requireDateParameter (
+ HttpServletRequest req, String name, String invalidDataMessage)
+ throws DataValidationException
+ {
+ return parseDateParameter(getParameter(req, name, false),
+ invalidDataMessage);
+ }
+
+ /**
+ * Fetches the supplied parameter from the request and converts it to
+ * a date. The value of the parameter should be a date formatted like
+ * so: 2001-12-25. If the parameter does not exist, null is
+ * returned. If the parameter is not a well-formed date, a data
+ * validation exception is thrown with the supplied message.
+ */
+ public static Date getDateParameter (
+ HttpServletRequest req, String name, String invalidDataMessage)
+ throws DataValidationException
+ {
+ String value = getParameter(req, name, false);
+ if (StringUtil.blank(value)) {
+ return null;
+ }
+ return parseDateParameter(value, invalidDataMessage);
+ }
+
+ protected static
+ Date parseDateParameter (String value, String invalidDataMessage)
+ throws DataValidationException
+ {
+ try {
+ return _dparser.parse(value);
+ } catch (ParseException pe) {
+ throw new DataValidationException(invalidDataMessage);
+ }
+ }
+
+ /**
+ * Returns true if the specified parameter is set in the request.
+ *
+ * @return true if the specified parameter is set in the request
+ * context, false otherwise.
+ */
+ public static boolean isSet (HttpServletRequest req, String name)
+ {
+ return !StringUtil.blank(req.getParameter(name));
+ }
+
+ /**
+ * Returns true if the specified parameter is equal to the supplied
+ * value. If the parameter is not set in the request, false is
+ * returned.
+ *
+ * @param name The parameter whose value should be compared with the
+ * supplied value.
+ * @param value The value to which the parameter may be equal. This
+ * should not be null.
+ *
+ * @return true if the specified parameter is equal to the supplied
+ * parameter, false otherwise.
+ */
+ public static boolean parameterEquals (
+ HttpServletRequest req, String name, String value)
+ {
+ return value.equals(getParameter(req, name, false));
+ }
+
+ /** We use this to parse dates in requireDateParameter(). */
+ protected static SimpleDateFormat _dparser =
+ new SimpleDateFormat("yyyy-MM-dd");
+}
diff --git a/projects/samskivert/src/java/com/samskivert/velocity/Application.java b/projects/samskivert/src/java/com/samskivert/velocity/Application.java
new file mode 100644
index 00000000..5a6b9c24
--- /dev/null
+++ b/projects/samskivert/src/java/com/samskivert/velocity/Application.java
@@ -0,0 +1,167 @@
+//
+// $Id: Application.java,v 1.1 2001/10/31 09:44:22 mdb Exp $
+//
+// samskivert library - useful routines for java programs
+// Copyright (C) 2001 Michael Bayne
+//
+// This library is free software; you can redistribute it and/or modify it
+// under the terms of the GNU Lesser General Public License as published
+// by the Free Software Foundation; either version 2.1 of the License, or
+// (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+package com.samskivert.velocity;
+
+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 ()
+ {
+ }
+
+ /**
+ * Returns the message manager in effect for this application, if one
+ * is in effect.
+ */
+ public MessageManager getMessageManager ()
+ {
+ return _msgmgr;
+ }
+
+ /**
+ * If an application wishes to make use of the translation facilities
+ * provided by the message manager, it need only provide the path to
+ * its message resource bundle via this member function. Using a
+ * message manager allows framework components like the
+ * MsgTool to make use of the application's message
+ * bundles.
+ */
+ protected String getMessageBundlePath ()
+ {
+ return null;
+ }
+
+ /**
+ * A convenience function for translating messages.
+ */
+ public final String translate (InvocationContext ctx, String msg)
+ {
+ return _msgmgr.getMessage(ctx.getRequest(), msg);
+ }
+
+ /**
+ * A convenience function for translating messages.
+ */
+ public final String translate (InvocationContext ctx, String msg,
+ Object arg)
+ {
+ return _msgmgr.getMessage(ctx.getRequest(), msg,
+ new Object[]{ arg });
+ }
+
+ /**
+ * A convenience function for translating messages.
+ */
+ public final String translate (InvocationContext ctx, String msg,
+ Object arg1, Object arg2)
+ {
+ return _msgmgr.getMessage(ctx.getRequest(), msg,
+ new Object[]{ arg1, arg2 });
+ }
+
+ /**
+ * A convenience function for translating messages.
+ */
+ public final String translate (InvocationContext ctx, String msg,
+ Object arg1, Object arg2, Object arg3)
+ {
+ return _msgmgr.getMessage(ctx.getRequest(), msg,
+ new Object[]{ arg1, arg2, arg3 });
+ }
+
+ /**
+ * A convenience function for translating messages.
+ */
+ public final String translate (InvocationContext ctx, String msg,
+ Object[] args)
+ {
+ return _msgmgr.getMessage(ctx.getRequest(), msg, args);
+ }
+
+ /**
+ * Performs initializations common to all applications. We could put
+ * this in init() and require application derived classes to call
+ * super.init() but people might forget to do so and be confused.
+ *
+ * @param logicPkg the base package for all of the logic
+ * implementations for this application.
+ */
+ public void preInit (String logicPkg)
+ {
+ // remove any trailing dot
+ if (logicPkg.endsWith(".")) {
+ _logicPkg = logicPkg.substring(0, logicPkg.length()-1);
+ } else {
+ _logicPkg = logicPkg;
+ }
+
+ // instantiate our message manager if the application wants one
+ String bpath = getMessageBundlePath();
+ if (bpath != null) {
+ _msgmgr = new MessageManager(bpath);
+ }
+ }
+
+ /**
+ * Given the servlet path (the part of the URI after the context path)
+ * this generates the classname of the logic class that should handle
+ * the request.
+ */
+ public String generateClass (String path)
+ {
+ // remove the trailing file extension
+ int ldidx = path.lastIndexOf(".");
+ if (ldidx != -1) {
+ path = path.substring(0, ldidx);
+ }
+ // convert slashes to dots
+ path = StringUtil.replace(path, "/", ".");
+ // prepend the base logic package and we're all set
+ return _logicPkg + path;
+ }
+
+ protected String _logicPkg;
+ protected MessageManager _msgmgr;
+}
diff --git a/projects/samskivert/src/java/com/samskivert/velocity/DispatcherServlet.java b/projects/samskivert/src/java/com/samskivert/velocity/DispatcherServlet.java
new file mode 100644
index 00000000..56fd562a
--- /dev/null
+++ b/projects/samskivert/src/java/com/samskivert/velocity/DispatcherServlet.java
@@ -0,0 +1,374 @@
+//
+// $Id: DispatcherServlet.java,v 1.1 2001/10/31 09:44:22 mdb Exp $
+//
+// samskivert library - useful routines for java programs
+// Copyright (C) 2001 Michael Bayne
+//
+// This library is free software; you can redistribute it and/or modify it
+// under the terms of the GNU Lesser General Public License as published
+// by the Free Software Foundation; either version 2.1 of the License, or
+// (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+package com.samskivert.velocity;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Properties;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.velocity.Template;
+import org.apache.velocity.context.Context;
+import org.apache.velocity.exception.ParseErrorException;
+import org.apache.velocity.exception.ResourceNotFoundException;
+import org.apache.velocity.servlet.VelocityServlet;
+
+import com.samskivert.Log;
+
+import com.samskivert.servlet.MessageManager;
+import com.samskivert.servlet.RedirectException;
+import com.samskivert.servlet.util.ExceptionMap;
+
+import com.samskivert.util.ConfigUtil;
+import com.samskivert.util.StringUtil;
+
+/**
+ * The dispatcher servlet builds upon Velocity's architecture. It does so
+ * in the following ways:
+ *
+ *
URI to servlet mapping
+ * The mapping process allows the Velocity framework to be invoked for all
+ * requests ending in a particular file extension (usually
+ * .wm). It is necessary to instruct your servlet engine of
+ * choice to invoke the DispatcherServlet for all requests
+ * ending in that extension. For Apache/JServ this looks something like
+ * this:
+ *
+ *
+ * ApJServAction .wm /servlets/com.samskivert.velocity.Dispatcher + *+ * + * The request URI then defines the path of the template that will be used + * to satisfy the request. To understand how code is selected to go along + * with the request, let's look at an example. Consider the following + * configuration: + * + *
+ * applications=whowhere + * whowhere.base_uri=/whowhere + * whowhere.base_pkg=whowhere.logic + *+ * + * This defines an application identified as
whowhere. An
+ * application is defined by three parameters, the application identifier,
+ * the base_uri, and the base_pkg. The
+ * base_uri defines the prefix shared by all pages served by
+ * the application and which serves to identify which application to
+ * invoke when processing a request. The base_pkg is used to
+ * construct the logic classname based on the URI and the
+ * base_uri parameter.
+ *
+ * Now let's look at a sample request to determine how the logic + * classname is resolved. Consider the following request URI: + * + *
+ * /whowhere/view/trips.wm + *+ * + * It begins with
/whowhere which tells the dispatcher that
+ * it's part of the whowhere application. That application's
+ * base_uri is then stripped from the URI leaving
+ * /view/trips.wm. The slashes are converted into periods to
+ * map directories to packages, giving us view.trips.wm.
+ * Finally, the base_pkg is prepended and the trailing
+ * .wm extension removed.
+ *
+ * Thus the class invoked to populate the context for this request is
+ * whowhere.servlets.view.trips (note that the classname
+ * is lowercase which is an intentional choice in resolving
+ * conflicting recommendations that classnames should always start with a
+ * capital letter and URLs should always be lowercase).
+ *
+ *
The template used to generate the result is loaded based on the
+ * full URI, essentially with a call to
+ * getTemplate("/whowhere/view/trips.wm") in this example.
+ * This is the place where more sophisticated cobranding support could be
+ * inserted in the future (ie. if I ever want to use this to develop a
+ * cobranded web site).
+ *
+ *
Error handling
+ * The dispatcher servlet provides a common error handling mechanism. The
+ * design is to catch any exceptions thrown by the logic and to convert
+ * them into friendly error messages that are inserted into the invocation
+ * context with the key "error" for easy display in the
+ * resulting web page.
+ *
+ *
The process of mapping exceptions to friendly error messages is + * done using the {@link ExceptionMap} class. Consult its documentation + * for an explanation of how it works. + * + * @see Logic + * @see ExceptionMap + */ +public class DispatcherServlet extends VelocityServlet +{ + /** + * We load our velocity properties from the classpath rather than from + * a file. + */ + protected Properties loadConfiguration (ServletConfig config) + throws IOException + { + String propsPath = config.getInitParameter(INIT_PROPS_KEY); + // config util loads properties files from the classpath + return ConfigUtil.loadProperties(propsPath); + } + + /** + * Initialize ourselves and our application. + */ + public void init (ServletConfig config) + throws ServletException + { + super.init(config); + + // Log.info("Initializing dispatcher servlet."); + + // load up our application configuration + try { + String appcl = config.getInitParameter(APP_CLASS_PROPS_KEY); + if (appcl == null) { + _app = new Application(); + } else { + Class appclass = Class.forName(appcl); + _app = (Application)appclass.newInstance(); + } + + // now initialize the applicaiton + String logicPkg = config.getInitParameter(LOGIC_PKG_PROPS_KEY); + if (StringUtil.blank(logicPkg)) { + logicPkg = ""; + } + _app.preInit(logicPkg); + _app.init(getServletContext()); + + } catch (Throwable t) { + Log.warning("Error instantiating application."); + Log.logStackTrace(t); + } + } + + /** + * Clean up after ourselves and our application. + */ + public void destroy () + { + super.destroy(); + // shutdown our application + _app.shutdown(); + } + + /** + * Loads up the template appropriate for this request, locates and + * invokes any associated logic class and finally returns the prepared + * template which will be merged with the prepared context. + */ + public Template handleRequest (HttpServletRequest req, + HttpServletResponse rsp, + Context ctx) throws Exception + { + InvocationContext ictx = (InvocationContext)ctx; + String errmsg = null; + + // first we select the template + Template tmpl = selectTemplate(ictx); + + // assume an HTML response unless otherwise massaged by the logic + rsp.setContentType("text/html"); + + try { + // insert the application into the context in case the + // logic or a tool wishes to make use of it + ictx.put(APPLICATION_KEY, _app); + + // if the application provides a message manager, we want + // to put a message resolver in the context as well + MessageManager msgmgr = _app.getMessageManager(); + if (msgmgr != null) { + MessageResolver mrslv = new MessageResolver(msgmgr); + ictx.put(MSGRESOLVER_KEY, mrslv); + } + + // resolve the appropriate logic class for this URI and + // execute it if it exists + String path = req.getServletPath(); + Logic logic = resolveLogic(path); + if (logic != null) { + logic.invoke(_app, ictx); + } + + } catch (RedirectException re) { + ictx.getResponse().sendRedirect(re.getRedirectURL()); + return null; + + } catch (FriendlyException fe) { + // grab the error message, we'll deal with it shortly + errmsg = fe.getMessage(); + + } catch (Exception e) { + errmsg = ExceptionMap.getMessage(e); + Log.logStackTrace(e); + } + + // if we have an error message, insert it into the template + if (errmsg != null) { + // try using the application to localize the error message + // before we insert it + MessageManager msgmgr = _app.getMessageManager(); + if (msgmgr != null) { + errmsg = msgmgr.getMessage(req, errmsg); + } + ictx.put(ERROR_KEY, errmsg); + } + + 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 (InvocationContext context) + { + return (Application)context.get(APPLICATION_KEY); + } + + /** + * We override this to create a context of our own devising. + */ + protected Context createContext (HttpServletRequest req, + HttpServletResponse rsp) + { + return new InvocationContext(req, rsp); + } + + /** + * This method is called to select the appropriate template for this + * request. The default implementation simply loads the template using + * Velocity's default template loading services based on the URI + * provided in the request. + * + * @param ctx The context of this request. + * + * @return The template to be used in generating the response. + */ + protected Template selectTemplate (InvocationContext ctx) + throws ResourceNotFoundException, ParseErrorException, Exception + { + String path = ctx.getRequest().getServletPath(); + // Log.info("Loading template [path=" + path + "]."); + return getTemplate(path); + } + + /** + * 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 (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); + logic = (Logic)pcl.newInstance(); + + } catch (ClassNotFoundException cnfe) { + // nothing interesting to report + + } catch (Throwable t) { + Log.warning("Unable to instantiate logic for application " + + "[path=" + path + ", lclass=" + lclass + + ", error=" + t + "]."); + } + + // if something failed, use a dummy in it's place so that we + // don't sit around all day freaking out about our inability + // to instantiate the proper logic class + if (logic == null) { + logic = new DummyLogic(); + } + + // cache the resolved logic instance + _logic.put(lclass, logic); + } + + return logic; + } + + /** The application being served by this dispatcher servlet. */ + protected Application _app; + + /** A table of resolved logic instances. */ + protected HashMap _logic = new HashMap(); + + /** This is the key used in the context for error messages. */ + protected static final String ERROR_KEY = "error"; + + /** + * This is the key used to store a reference back to the dispatcher + * servlet in our invocation context. + */ + protected static final String APPLICATION_KEY = "%_app_%"; + + /** + * This is the key used to store the message resolver in the context. + */ + protected static final String MSGRESOLVER_KEY = "i18n"; + + /** The servlet parameter key specifying the application class. */ + protected static final String APP_CLASS_PROPS_KEY = "app_class"; + + /** The servlet parameter key specifying the base logic package. */ + protected static final String LOGIC_PKG_PROPS_KEY = "logic_package"; +} diff --git a/projects/samskivert/src/java/com/samskivert/velocity/DummyLogic.java b/projects/samskivert/src/java/com/samskivert/velocity/DummyLogic.java new file mode 100644 index 00000000..862b0dbf --- /dev/null +++ b/projects/samskivert/src/java/com/samskivert/velocity/DummyLogic.java @@ -0,0 +1,35 @@ +// +// $Id: DummyLogic.java,v 1.1 2001/10/31 09:44:22 mdb Exp $ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2001 Michael Bayne +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.samskivert.velocity; + +/** + * The dummy logic is used as a placeholder for URIs that match no normal + * logic or for which the matching logic could not be instantiated. It + * does nothing. + */ +public class DummyLogic implements Logic +{ + public void invoke (Application app, InvocationContext context) + throws Exception + { + // we're such a dummy that we do absolutely nothing. + } +} diff --git a/projects/samskivert/src/java/com/samskivert/velocity/InvocationContext.java b/projects/samskivert/src/java/com/samskivert/velocity/InvocationContext.java new file mode 100644 index 00000000..17b6e5b2 --- /dev/null +++ b/projects/samskivert/src/java/com/samskivert/velocity/InvocationContext.java @@ -0,0 +1,77 @@ +// +// $Id: InvocationContext.java,v 1.1 2001/10/31 09:44:22 mdb Exp $ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2001 Michael Bayne +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.samskivert.velocity; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.velocity.Template; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.runtime.RuntimeSingleton; + +/** + * The invocation context provides access to request related information + * as well as being the place where objects are placed to make them + * available to the template. + */ +public class InvocationContext extends VelocityContext +{ + /** + * Constructs a new invocation context instance with the supplied http + * request and response objects. + */ + public InvocationContext (HttpServletRequest req, HttpServletResponse rsp) + { + _req = req; + _rsp = rsp; + } + + /** + * Returns the http request object associated with this invocation. + */ + public HttpServletRequest getRequest () + { + return _req; + } + + /** + * Returns the http response object associated with this invocation. + */ + public HttpServletResponse getResponse () + { + return _rsp; + } + + /** + * Fetches a Velocity template that can be used for later formatting. + * + * @exception Exception thrown if an error occurs loading or parsing + * the template. + */ + public Template getTemplate (String path) + throws Exception + { + return RuntimeSingleton.getTemplate(path); + } + + protected HttpServletRequest _req; + protected HttpServletResponse _rsp; +} diff --git a/projects/samskivert/src/java/com/samskivert/velocity/Logic.java b/projects/samskivert/src/java/com/samskivert/velocity/Logic.java new file mode 100644 index 00000000..26b38d1b --- /dev/null +++ b/projects/samskivert/src/java/com/samskivert/velocity/Logic.java @@ -0,0 +1,50 @@ +// +// $Id: Logic.java,v 1.1 2001/10/31 09:44:22 mdb Exp $ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2001 Michael Bayne +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.samskivert.velocity; + +import com.samskivert.servlet.util.ExceptionMap; + +/** + * The logic class is called upon to populate the WebMacro web context, + * prior to invoking a particular WebMacro template upon it to generate a + * response page. The logic takes the place of the servlet in the standard + * WebMacro architecture and should perform all of the logic involved in + * handling a particular request. + * + * @see DispatcherServlet + */ +public interface Logic +{ + /** + * Perform any necessary computation and populate the context with + * data for this request. Any exceptions that are thrown will be + * converted into friendly error messages using the exception mapping + * services. + * + * @param app The application that generated this logic instance (used + * to access application-wide resources). + * @param context The invocation context in scope for this request. + * + * @see ExceptionMap + */ + public void invoke (Application app, InvocationContext context) + throws Exception; +} diff --git a/projects/samskivert/src/java/com/samskivert/velocity/MessageResolver.java b/projects/samskivert/src/java/com/samskivert/velocity/MessageResolver.java new file mode 100644 index 00000000..c3af829e --- /dev/null +++ b/projects/samskivert/src/java/com/samskivert/velocity/MessageResolver.java @@ -0,0 +1,140 @@ +// +// $Id: MessageResolver.java,v 1.1 2001/10/31 09:44:22 mdb Exp $ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2001 Michael Bayne +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.samskivert.velocity; + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; + +import org.apache.velocity.runtime.parser.node.ASTIdentifier; +import org.apache.velocity.runtime.parser.node.ASTMethod; +import org.apache.velocity.runtime.parser.node.Node; +import org.apache.velocity.runtime.parser.node.ReferenceResolver; + +import com.samskivert.Log; +import com.samskivert.servlet.MessageManager; + +/** + * Maps a set of appliation messages (translation strings) into the + * context so that they are available to templates that wish to display + * localized text. + * + *
If a message resolver is mapped into the context as
+ * i18n, then it might be accessed as follows:
+ *
+ *
+ * $i18n.main.intro($user.username) + *+ * + * where
main.intro might be defined in the message resource
+ * file as:
+ *
+ *
+ * main.intro=Hello there {0}, welcome to our site!
+ *
+ *
+ * @see MessageManager
+ */
+public class MessageResolver implements ReferenceResolver
+{
+ /**
+ * Constructs a new message resolver which will use the supplied
+ * message manager to obtain translated strings.
+ */
+ public MessageResolver (MessageManager msgmgr)
+ {
+ _msgmgr = msgmgr;
+ }
+
+ /**
+ * Concatenates the remaining reference nodes into a single message
+ * name and looks up the matching translation string. If the last node
+ * is an {@link ASTMethod}, then the arguments from that are
+ * substituted into the translated message.
+ */
+ public Object resolveReference (
+ InternalContextAdapter ctx, Node[] nodes, int offset)
+ throws MethodInvocationException
+ {
+ StringBuffer path = new StringBuffer();
+ boolean bogus = false;
+ int lastidx = nodes.length-1;
+ Object result = this;
+
+ // reconstruct the path to the property
+ for (int i = offset; i < nodes.length; i++) {
+ // separate components with dots
+ if (path.length() > 0) {
+ path.append(".");
+ }
+
+ // if the node is an ASTMethod, it dang well better be the
+ // last thing in the chain or we've got a problem
+ if (nodes[i] instanceof ASTMethod) {
+ ASTMethod mnode = (ASTMethod)nodes[i];
+ // make a note to freak out if this is anything but
+ // the last component of the path. we'd freak out now
+ // but we want to reconstruct the path so that we can
+ // report it in the exception that's thrown
+ if (i != lastidx) {
+ bogus = true;
+ }
+ path.append(mnode.getMethodName());
+
+ } else if (nodes[i] instanceof ASTIdentifier) {
+ ASTIdentifier inode = (ASTIdentifier)nodes[i];
+ path.append(inode.getIdentifier());
+
+ } else {
+ // what is it man?
+ bogus = true;
+ path.append("?").append(nodes[i]);
+ }
+ }
+
+ // do any pending freaking out
+ if (bogus) {
+ throw new MethodInvocationException(
+ "Invalid message resource path.", null, path.toString());
+ }
+
+ // we need to get the invocation context from our internal context
+ // adapter
+ InvocationContext ictx = (InvocationContext)
+ ctx.getInternalUserContext();
+
+ // if the last component is a property method, we want to use
+ // it's arguments when looking up the message
+ if (nodes[lastidx] instanceof ASTMethod) {
+ ASTMethod mnode = (ASTMethod)nodes[lastidx];
+ // we may cache message formatters later, but for now just
+ // use the static convenience function
+ Object[] args = mnode.getParams(ctx);
+ return _msgmgr.getMessage(ictx.getRequest(),
+ path.toString(), args);
+
+ } else {
+ // otherwise just look up the path
+ return _msgmgr.getMessage(ictx.getRequest(), path.toString());
+ }
+ }
+
+ protected MessageManager _msgmgr;
+}
diff --git a/projects/samskivert/src/java/com/samskivert/velocity/package.html b/projects/samskivert/src/java/com/samskivert/velocity/package.html
new file mode 100644
index 00000000..a140df3e
--- /dev/null
+++ b/projects/samskivert/src/java/com/samskivert/velocity/package.html
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+ Provides parts of a simple web application framework built around the
+ Velocity template engine.
+
+ The web application framework provided by this package is simple + and intended for use in small projects with limited scope. It is not + intended to be all things, nor to evolve into something that makes + toast. It builds upon Velocity, a template engine whose + philosophies + about the separation of markup and code I agree with. + +
Velocity is designed to be used explicitly by servlets in + applications where the design is structured around the servlets + rather than the pages generated by the servlets. This is in contrast + to the JSP approach where the pages themselves dictate the structure + of the application and the servlets are automatically generated to + conform to that structure. This package provides a means by which to + automatically map serlvet-like units of code to templates in a way + akin to JSP's approach but without the combination of code and + markup. + +