From 57953b968a649a0e7810895b127f2e9d919617fb Mon Sep 17 00:00:00 2001 From: mdb Date: Wed, 31 Oct 2001 09:44:22 +0000 Subject: [PATCH] Converted my web application framework to work with Velocity and cleaned up some other stuff (removed multiple application support because it was pointless). Moved some general purpose things into servlet.util (still need to modify the WebMacro version of the framework to use the general purpose versions). git-svn-id: https://samskivert.googlecode.com/svn/trunk@397 6335cc39-0255-0410-8fd6-9bcaacd3b74c --- .../servlet/util/DataValidationException.java | 35 ++ .../samskivert/servlet/util/ExceptionMap.java | 174 ++++++++ .../servlet/util/FriendlyException.java | 34 ++ .../com/samskivert/servlet/util/HTMLUtil.java | 47 +++ .../servlet/util/ParameterUtil.java | 165 ++++++++ .../com/samskivert/velocity/Application.java | 167 ++++++++ .../velocity/DispatcherServlet.java | 374 ++++++++++++++++++ .../com/samskivert/velocity/DummyLogic.java | 35 ++ .../velocity/InvocationContext.java | 77 ++++ .../java/com/samskivert/velocity/Logic.java | 50 +++ .../samskivert/velocity/MessageResolver.java | 140 +++++++ .../java/com/samskivert/velocity/package.html | 54 +++ 12 files changed, 1352 insertions(+) create mode 100644 projects/samskivert/src/java/com/samskivert/servlet/util/DataValidationException.java create mode 100644 projects/samskivert/src/java/com/samskivert/servlet/util/ExceptionMap.java create mode 100644 projects/samskivert/src/java/com/samskivert/servlet/util/FriendlyException.java create mode 100644 projects/samskivert/src/java/com/samskivert/servlet/util/HTMLUtil.java create mode 100644 projects/samskivert/src/java/com/samskivert/servlet/util/ParameterUtil.java create mode 100644 projects/samskivert/src/java/com/samskivert/velocity/Application.java create mode 100644 projects/samskivert/src/java/com/samskivert/velocity/DispatcherServlet.java create mode 100644 projects/samskivert/src/java/com/samskivert/velocity/DummyLogic.java create mode 100644 projects/samskivert/src/java/com/samskivert/velocity/InvocationContext.java create mode 100644 projects/samskivert/src/java/com/samskivert/velocity/Logic.java create mode 100644 projects/samskivert/src/java/com/samskivert/velocity/MessageResolver.java create mode 100644 projects/samskivert/src/java/com/samskivert/velocity/package.html 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. + +

Related Documentation

+ + Velocity website. + + +