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. + + +