Changed around the way things are handled. We now map all files with the
.wm extension to the DispatcherServlet and it loads ContextPopulator instances to populate the contexts for the template that is identified by the URI. This page driven invocation scheme works more nicely in my humble opinion and WebMacro certainly benefits from such an extension. git-svn-id: https://samskivert.googlecode.com/svn/trunk@48 6335cc39-0255-0410-8fd6-9bcaacd3b74c
This commit is contained in:
@@ -0,0 +1,30 @@
|
|||||||
|
//
|
||||||
|
// $Id: ContextPopulator.java,v 1.1 2001/02/15 01:44:34 mdb Exp $
|
||||||
|
|
||||||
|
package com.samskivert.webmacro;
|
||||||
|
|
||||||
|
import org.webmacro.servlet.WebContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The context populator is called upon to populate the WebMacro web
|
||||||
|
* context, prior to invoking a particular WebMacro template upon it to
|
||||||
|
* generate a response page. The populator 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 ContextPopulator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 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 context The WebMacro context in scope for this request.
|
||||||
|
*
|
||||||
|
* @see ExceptionMap
|
||||||
|
*/
|
||||||
|
public void populate (WebContext context) throws Exception;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
//
|
||||||
|
// $Id: DataValidationException.java,v 1.1 2001/02/15 01:44:34 mdb Exp $
|
||||||
|
|
||||||
|
package com.samskivert.webmacro;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A data validation exception is thrown when a value supplied in a form
|
||||||
|
* element is not valid. The servlet framework will substitute the message
|
||||||
|
* assosciated with this exception into the context with the key
|
||||||
|
* <code>"invalid_data"</code>. Templates should be structured so that
|
||||||
|
* this is reported to the user along with the filled out form field for
|
||||||
|
* their editing and resubmitting pleasures.
|
||||||
|
*/
|
||||||
|
public class DataValidationException extends FriendlyException
|
||||||
|
{
|
||||||
|
public DataValidationException (String message)
|
||||||
|
{
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,363 @@
|
|||||||
|
//
|
||||||
|
// $Id: DispatcherServlet.java,v 1.1 2001/02/15 01:44:34 mdb Exp $
|
||||||
|
|
||||||
|
package com.samskivert.webmacro;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.StringTokenizer;
|
||||||
|
|
||||||
|
import javax.servlet.ServletConfig;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
|
||||||
|
import com.samskivert.util.ConfigUtil;
|
||||||
|
import com.samskivert.util.StringUtil;
|
||||||
|
import org.webmacro.*;
|
||||||
|
import org.webmacro.servlet.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The dispatcher servlet builds upon WebMacro's architecture. It does so
|
||||||
|
* in the following ways:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li> It defines the notion of a context populator which populates the
|
||||||
|
* context with data to be used to satisfy a particular request. The
|
||||||
|
* context populator is not a servlet and is therefore limited in what it
|
||||||
|
* can do while populating data. Experience dictates that ultimate
|
||||||
|
* flexibility leads to bad design decisions and that this is a place
|
||||||
|
* where that sort of thing can be comfortably nipped in the bud. <br><br>
|
||||||
|
*
|
||||||
|
* <li> It allows .wm files to be referenced directly in the URL while
|
||||||
|
* maintaining the ability to choose a cobranded template based. The URI
|
||||||
|
* is mapped to a servlet based on some simple mapping rules. This
|
||||||
|
* provides template designers with a clearer understanding of the
|
||||||
|
* structure of a web application as well as with an easy way to test
|
||||||
|
* their templates in the absence of an associated servlet. <br><br>
|
||||||
|
*
|
||||||
|
* <li> It provides a common error handling paradigm that simplifies the
|
||||||
|
* task of authoring web applications.
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p><b>URI to servlet mapping</b><br>
|
||||||
|
* The mapping process allows the webmacro framework to be invoked for all
|
||||||
|
* requests ending in a particular file extension (usually
|
||||||
|
* <code>.wm</code>). It is necessary to instruct your servlet engine of
|
||||||
|
* choice to invoke the <code>Dispatcher</code> servlet for all requests
|
||||||
|
* ending in that extension. For Apache/JServ this looks something like
|
||||||
|
* this:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* ApJServAction .wm /servlets/com.samskivert.webmacro.Dispatcher
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* The request URI then defines the path of the webmacro 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:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* applications=whowhere
|
||||||
|
* whowhere.base_uri=/whowhere
|
||||||
|
* whowhere.base_pkg=whowhere.servlets
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* This defines an application identified as <code>whowhere</code>. An
|
||||||
|
* application is defined by three parameters, the application identifier,
|
||||||
|
* the <code>base_uri</code>, and the <code>base_pkg</code>. The
|
||||||
|
* <code>base_uri</code> 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 <code>base_pkg</code> is used to
|
||||||
|
* construct the populator classname based on the URI and the
|
||||||
|
* <code>base_uri</code> parameter.
|
||||||
|
*
|
||||||
|
* <p> Now let's look at a sample request to determine how the populator
|
||||||
|
* classname is resolved. Consider the following request URI:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* /whowhere/view/trips.wm
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* It begins with <code>/whowhere</code> which tells the dispatcher that
|
||||||
|
* it's part of the <code>whowhere</code> application. That application's
|
||||||
|
* <code>base_uri</code> is then stripped from the URI leaving
|
||||||
|
* <code>/view/trips.wm</code>. The slashes are converted into periods to
|
||||||
|
* map directories to packages, giving us <code>view.trips.wm</code>.
|
||||||
|
* Finally, the <code>base_pkg</code> is prepended and the trailing
|
||||||
|
* <code>.wm</code> extension removed.
|
||||||
|
*
|
||||||
|
* <p> Thus the class invoked to populate the context for this request is
|
||||||
|
* <code>whowhere.servlets.view.trips</code> (note that the classname
|
||||||
|
* <em>is</em> 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).
|
||||||
|
*
|
||||||
|
* <p> The template used to generate the result is loaded based on the
|
||||||
|
* full URI, essentially with a call to
|
||||||
|
* <code>getTemplate("/whowhere/view/trips.wm")</code> 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).
|
||||||
|
*
|
||||||
|
* <p><b>Error handling</b><br>
|
||||||
|
* The dispatcher servlet provides a common error handling mechanism. The
|
||||||
|
* design is to catch any exceptions thrown by the populator and to
|
||||||
|
* convert them into friendly error messages that are inserted into the
|
||||||
|
* web context with the key <code>"error"</code> for easy display in the
|
||||||
|
* resulting web page.
|
||||||
|
*
|
||||||
|
* <p> The process of mapping exceptions to friendly error messages is
|
||||||
|
* done using the <code>ExceptionMap</code> class. Consult its
|
||||||
|
* documentation for an explanation of how it works.
|
||||||
|
*
|
||||||
|
* @see ContextPopulator
|
||||||
|
* @see ExceptionMap
|
||||||
|
*/
|
||||||
|
public class DispatcherServlet extends WMServlet
|
||||||
|
{
|
||||||
|
public void start ()
|
||||||
|
throws ServletException
|
||||||
|
{
|
||||||
|
// Log.log.info("Initializing dispatcher servlet.");
|
||||||
|
|
||||||
|
// first load the properties file
|
||||||
|
Properties props = null;
|
||||||
|
try {
|
||||||
|
// load up our configuration
|
||||||
|
ClassLoader cld = DispatcherServlet.class.getClassLoader();
|
||||||
|
props = ConfigUtil.loadProperties("dispatcher.properties", cld);
|
||||||
|
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
Log.log.warning("Failure trying to load " +
|
||||||
|
"'dispatcher.properties': " + ioe);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props == null) {
|
||||||
|
Log.log.warning("Unable to load properties for " +
|
||||||
|
"dispatcher servlet.");
|
||||||
|
Log.log.warning("Make sure 'dispatcher.properties' file " +
|
||||||
|
"exists somewhere in your classpath.");
|
||||||
|
props = new Properties();
|
||||||
|
}
|
||||||
|
|
||||||
|
// now parse the configuration
|
||||||
|
String apps = props.getProperty("applications");
|
||||||
|
if (apps != null) {
|
||||||
|
StringTokenizer tok = new StringTokenizer(apps);
|
||||||
|
while (tok.hasMoreTokens()) {
|
||||||
|
String appid = tok.nextToken();
|
||||||
|
String baseURI = props.getProperty(appid + ".base_uri");
|
||||||
|
String basePkg = props.getProperty(appid + ".base_pkg");
|
||||||
|
|
||||||
|
// make sure we're not missing anything
|
||||||
|
if (baseURI == null) {
|
||||||
|
Log.log.warning("Application '" + appid +
|
||||||
|
"' missing base_uri specification.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (basePkg == null) {
|
||||||
|
Log.log.warning("Application '" + appid +
|
||||||
|
"' missing base_pkg specification.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// construct an application object and add it to our list
|
||||||
|
_apps.add(new Application(baseURI, basePkg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Template handle (WebContext ctx) throws HandlerException
|
||||||
|
{
|
||||||
|
// first we select the template
|
||||||
|
Template tmpl;
|
||||||
|
try {
|
||||||
|
tmpl = selectTemplate(ctx);
|
||||||
|
} catch (NotFoundException e) {
|
||||||
|
throw new HandlerException("Unable to load template: " + e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// then we populate the context with data
|
||||||
|
try {
|
||||||
|
ContextPopulator pop = selectPopulator(ctx);
|
||||||
|
// if no populator is matched, we simply execute the template
|
||||||
|
// directly with the default information in the context (tools
|
||||||
|
// and other WebMacro services can be used by the template to
|
||||||
|
// do their WebMacro thing)
|
||||||
|
if (pop != null) {
|
||||||
|
pop.populate(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (DataValidationException dve) {
|
||||||
|
ctx.put(DV_ERROR_KEY, ExceptionMap.getMessage(dve));
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
ctx.put(ERROR_KEY, ExceptionMap.getMessage(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
return tmpl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called to select the appropriate template for this
|
||||||
|
* request. The default implementation simply loads the template using
|
||||||
|
* WebMacro's default template loading services based on the URI
|
||||||
|
* provided in the request. It is assumed that the document root is
|
||||||
|
* registered in WebMacro's search path.
|
||||||
|
*
|
||||||
|
* @param ctx The context of this request.
|
||||||
|
*
|
||||||
|
* @return The template to be used in generating the response.
|
||||||
|
*/
|
||||||
|
protected Template selectTemplate (WebContext ctx)
|
||||||
|
throws NotFoundException
|
||||||
|
{
|
||||||
|
String path = cleanupURI(ctx.getRequest().getRequestURI());
|
||||||
|
// Log.log.info("Loading template [path=" + path + "].");
|
||||||
|
return getTemplate(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called to select the appropriate context populator
|
||||||
|
* for this request. The dispatcher configuration described in this
|
||||||
|
* class's documentation is consulted to map the URI to a populator
|
||||||
|
* class which is then instantiated and a single instance used to
|
||||||
|
* process all matching requests.
|
||||||
|
*
|
||||||
|
* @param ctx The context of this request.
|
||||||
|
*
|
||||||
|
* @return The populator to be used in generating the response or null
|
||||||
|
* if no populator could be matched.
|
||||||
|
*/
|
||||||
|
protected ContextPopulator selectPopulator (WebContext ctx)
|
||||||
|
{
|
||||||
|
String path = cleanupURI(ctx.getRequest().getRequestURI());
|
||||||
|
String pclass = null;
|
||||||
|
// Log.log.info("Loading populator [path=" + path + "].");
|
||||||
|
|
||||||
|
// try to locate an application that matches this URI
|
||||||
|
for (int i = 0; i < _apps.size(); i++) {
|
||||||
|
Application app = (Application)_apps.get(i);
|
||||||
|
if (app.matches(path)) {
|
||||||
|
pclass = app.generateClass(path);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we didn't find a matching application, we can stop now
|
||||||
|
if (pclass == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise look for a cached populator instance
|
||||||
|
ContextPopulator pop = (ContextPopulator)_pops.get(pclass);
|
||||||
|
if (pop == null) {
|
||||||
|
try {
|
||||||
|
Class pcl = Class.forName(pclass);
|
||||||
|
pop = (ContextPopulator)pcl.newInstance();
|
||||||
|
|
||||||
|
} catch (Throwable t) {
|
||||||
|
Log.log.warning("Unable to instantiate populator for " +
|
||||||
|
"matching application [path=" + path +
|
||||||
|
", pclass=" + pclass + ", error=" + t + "].");
|
||||||
|
// 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 populator class
|
||||||
|
pop = new DummyPopulator();
|
||||||
|
}
|
||||||
|
_pops.put(pclass, pop);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pop;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Because the URI can actually contain the protocol and server name,
|
||||||
|
* though it commonly doesn't, we need to be sure to remove that extra
|
||||||
|
* information before we look at the URI because we only want the path
|
||||||
|
* part of the URI. The JSDK unfortunately doesn't provide an easy
|
||||||
|
* mechanism to request just the path part of the URI.
|
||||||
|
*/
|
||||||
|
protected static String cleanupURI (String uri)
|
||||||
|
{
|
||||||
|
int dsidx = uri.indexOf("//");
|
||||||
|
if (dsidx == -1) {
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sidx = uri.indexOf("/", dsidx + 2);
|
||||||
|
if (sidx == -1) {
|
||||||
|
Log.log.warning("Malformed URI?! [uri=" + uri + "].");
|
||||||
|
return "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
return uri.substring(sidx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main (String[] args)
|
||||||
|
{
|
||||||
|
System.out.println(cleanupURI("/whowhere/viewtrip.wm"));
|
||||||
|
System.out.println(cleanupURI("http://samskivert.com/foo/bar/baz.wm"));
|
||||||
|
System.out.println(cleanupURI("http://samskivert.com"));
|
||||||
|
System.out.println(cleanupURI("http://samskivert.com/"));
|
||||||
|
System.out.println(cleanupURI("//samskivert.com/is/this/even/legal"));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class Application
|
||||||
|
{
|
||||||
|
public Application (String baseURI, String basePkg)
|
||||||
|
{
|
||||||
|
// remove any trailing slash
|
||||||
|
if (baseURI.endsWith("/")) {
|
||||||
|
_baseURI = baseURI.substring(0, baseURI.length()-1);
|
||||||
|
} else {
|
||||||
|
_baseURI = baseURI;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove any trailing dot
|
||||||
|
if (basePkg.endsWith(".")) {
|
||||||
|
_basePkg = basePkg.substring(0, basePkg.length()-1);
|
||||||
|
} else {
|
||||||
|
_basePkg = basePkg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean matches (String uri)
|
||||||
|
{
|
||||||
|
return uri.startsWith(_baseURI);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String generateClass (String uri)
|
||||||
|
{
|
||||||
|
// remove the base URI
|
||||||
|
uri = uri.substring(_baseURI.length());
|
||||||
|
// convert slashes to dots
|
||||||
|
uri = StringUtil.replace(uri, "/", ".");
|
||||||
|
// remove the trailing file extension
|
||||||
|
uri = uri.substring(0, uri.length() - FILE_EXTENSION.length());
|
||||||
|
// prepend the base package and we're all set
|
||||||
|
return _basePkg + uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String _baseURI;
|
||||||
|
protected String _basePkg;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ArrayList _apps = new ArrayList();
|
||||||
|
protected HashMap _pops = 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 in the context for data validation error
|
||||||
|
* messages.
|
||||||
|
*/
|
||||||
|
protected static final String DV_ERROR_KEY = "invalid_data";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the default file extension.
|
||||||
|
*/
|
||||||
|
protected static final String FILE_EXTENSION = ".wm";
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
//
|
||||||
|
// $Id: DummyPopulator.java,v 1.1 2001/02/15 01:44:34 mdb Exp $
|
||||||
|
|
||||||
|
package com.samskivert.webmacro;
|
||||||
|
|
||||||
|
import org.webmacro.servlet.WebContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The dummy populator is used as a placeholder for URIs that match no
|
||||||
|
* normal populator or for which the matching populator could not be
|
||||||
|
* instantiated. It does nothing.
|
||||||
|
*/
|
||||||
|
public class DummyPopulator implements ContextPopulator
|
||||||
|
{
|
||||||
|
public void populate (WebContext context) throws Exception
|
||||||
|
{
|
||||||
|
// we're such a dummy that we do absolutely nothing.
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,55 @@
|
|||||||
//
|
//
|
||||||
// $Id: ExceptionMap.java,v 1.1 2001/02/13 20:00:28 mdb Exp $
|
// $Id: ExceptionMap.java,v 1.2 2001/02/15 01:44:34 mdb Exp $
|
||||||
|
|
||||||
package com.samskivert.webmacro;
|
package com.samskivert.webmacro;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
import com.samskivert.util.ConfigUtil;
|
||||||
import com.samskivert.util.StringUtil;
|
import com.samskivert.util.StringUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The exception map is used to load the exception to error message
|
* The exception map is used to map exceptions to error messages based on
|
||||||
* mapping information and to look up the appropriate response for a given
|
* a static, server-wide configuration.
|
||||||
* exception instance.
|
|
||||||
*
|
*
|
||||||
* @see FriendlyServlet
|
* <p>The configuration file is loaded via the classpath. The file should
|
||||||
|
* be named <code>exceptionmap.properties</code> 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:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* # Exception mappings (lines beginning with # are ignored)
|
||||||
|
* com.samskivert.webmacro.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.
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* The message associated with the exception will be substituted into the
|
||||||
|
* error string in place of <code>{m}</code>. 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.
|
||||||
|
*
|
||||||
|
* <p><em>Note:</em> These exception mappings are 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
|
||||||
|
* <code>"error"</code>) rather than relying on the default SQL exception
|
||||||
|
* error message which is not likely to be meaningful for such a
|
||||||
|
* situation.
|
||||||
|
*
|
||||||
|
* @see DispatcherServlet
|
||||||
*/
|
*/
|
||||||
public class ExceptionMap
|
public class ExceptionMap
|
||||||
{
|
{
|
||||||
@@ -33,13 +69,8 @@ public class ExceptionMap
|
|||||||
}
|
}
|
||||||
|
|
||||||
// first try loading the properties file without a leading slash
|
// first try loading the properties file without a leading slash
|
||||||
InputStream config = getStream(PROPS_NAME);
|
ClassLoader cld = ExceptionMap.class.getClassLoader();
|
||||||
if (config == null) {
|
InputStream config = ConfigUtil.getStream(PROPS_NAME, cld);
|
||||||
// some JVMs require the leading slash, some don't
|
|
||||||
config = getStream("/" + PROPS_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
// still no props, then we complain
|
|
||||||
if (config == null) {
|
if (config == null) {
|
||||||
Log.log.warning("Unable to load " + PROPS_NAME +
|
Log.log.warning("Unable to load " + PROPS_NAME +
|
||||||
" from CLASSPATH.");
|
" from CLASSPATH.");
|
||||||
@@ -111,19 +142,6 @@ public class ExceptionMap
|
|||||||
return StringUtil.replace(msg, MESSAGE_MARKER, ex.getMessage());
|
return StringUtil.replace(msg, MESSAGE_MARKER, ex.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static InputStream getStream (String path)
|
|
||||||
{
|
|
||||||
// first try using the classloader that loaded us
|
|
||||||
Class c = ExceptionMap.class;
|
|
||||||
InputStream in = c.getResourceAsStream(path);
|
|
||||||
if (null == in) {
|
|
||||||
// if that didn't work, try the system classloader
|
|
||||||
c = Class.class;
|
|
||||||
in = c.getResourceAsStream(path);
|
|
||||||
}
|
|
||||||
return in;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main (String[] args)
|
public static void main (String[] args)
|
||||||
{
|
{
|
||||||
ExceptionMap map = new ExceptionMap();
|
ExceptionMap map = new ExceptionMap();
|
||||||
@@ -137,6 +155,6 @@ public class ExceptionMap
|
|||||||
static { init(); }
|
static { init(); }
|
||||||
|
|
||||||
protected static final String PROPS_NAME = "exceptionmap.properties";
|
protected static final String PROPS_NAME = "exceptionmap.properties";
|
||||||
protected static final String DEFAULT_ERROR_MSG = "Error: {m}.";
|
protected static final String DEFAULT_ERROR_MSG = "Error: {m}";
|
||||||
protected static final String MESSAGE_MARKER = "{m}";
|
protected static final String MESSAGE_MARKER = "{m}";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
//
|
||||||
|
// $Id: FormUtil.java,v 1.1 2001/02/15 01:44:34 mdb Exp $
|
||||||
|
|
||||||
|
package com.samskivert.webmacro;
|
||||||
|
|
||||||
|
import org.webmacro.servlet.WebContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The form util class provides handy functions for doing form-related
|
||||||
|
* stuff.
|
||||||
|
*/
|
||||||
|
public class FormUtil
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 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 (WebContext context, String name,
|
||||||
|
String invalidDataMessage)
|
||||||
|
throws DataValidationException
|
||||||
|
{
|
||||||
|
String value = context.getForm(name);
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(value);
|
||||||
|
} catch (NumberFormatException nfe) {
|
||||||
|
throw new DataValidationException(invalidDataMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// $Id: FriendlyException.java,v 1.1 2001/02/13 18:49:41 mdb Exp $
|
// $Id: FriendlyException.java,v 1.2 2001/02/15 01:44:34 mdb Exp $
|
||||||
|
|
||||||
package com.samskivert.webmacro;
|
package com.samskivert.webmacro;
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ package com.samskivert.webmacro;
|
|||||||
* construct a friendly exception with the desired error message and throw
|
* construct a friendly exception with the desired error message and throw
|
||||||
* it during the call to <code>populateContext</code>.
|
* it during the call to <code>populateContext</code>.
|
||||||
*
|
*
|
||||||
* @see FriendlyServlet
|
* @see DispatcherServlet
|
||||||
*/
|
*/
|
||||||
public class FriendlyException extends Exception
|
public class FriendlyException extends Exception
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,112 +0,0 @@
|
|||||||
//
|
|
||||||
// $Id: FriendlyServlet.java,v 1.2 2001/02/13 20:00:28 mdb Exp $
|
|
||||||
|
|
||||||
package com.samskivert.webmacro;
|
|
||||||
|
|
||||||
import org.webmacro.*;
|
|
||||||
import org.webmacro.servlet.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The friendly servlet adds a common error handling paradigm to the basic
|
|
||||||
* webmacro servlet. This paradigm is to catch any exceptions thrown in
|
|
||||||
* the <code>populateContext</code> method (used instead of the
|
|
||||||
* <code>handle</code> method) and to convert them into friendly error
|
|
||||||
* messages that are inserted into the context with the key
|
|
||||||
* <code>"error"</code> for easy display to the user in the resulting web
|
|
||||||
* page.
|
|
||||||
*
|
|
||||||
* <p> To make this work, the process of template selection is separated
|
|
||||||
* from the process of data generation. First the template is selected; if
|
|
||||||
* that process fails, a standard error response is given to the user as
|
|
||||||
* we have no template into which to substitute the template selection
|
|
||||||
* failure error message. Subsequently, the data is generated. If that
|
|
||||||
* fails, we can substitute a friendly error message into the selected
|
|
||||||
* template. Finally, the template is executed, which is a standard
|
|
||||||
* webmacro procedure.
|
|
||||||
*
|
|
||||||
* <p> The process of mapping exceptions to friendly error messages is
|
|
||||||
* done through a configuration file loaded via the classpath. The file
|
|
||||||
* should be named <code>exceptionmap.properties</code> 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:
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* # Exception mappings (lines beginning with # are ignored)
|
|
||||||
* com.samskivert.webmacro.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.
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* The message associated with the exception will be substituted into the
|
|
||||||
* error string in place of <code>{m}</code>. The exceptions should be
|
|
||||||
* listed in order of most specific to least specific, for the first
|
|
||||||
* mapping for which the exception to report is an instance of the
|
|
||||||
* exception in the left hand side will be used.
|
|
||||||
*
|
|
||||||
* <em>Note:</em> These exception mappings are used for all servlets
|
|
||||||
* (perhaps some day only for servlets associated with a particular
|
|
||||||
* application identifier). Regardless, this error handling mechanism
|
|
||||||
* should not be used for servlet specific errors. For example, an SQL
|
|
||||||
* exception reporting a duplicate key should probably be caught and
|
|
||||||
* reported specifically by the servlet (it can still leverage the pattern
|
|
||||||
* of inserting the error message into the context as
|
|
||||||
* <code>"error"</code>) rather than relying on the default SQL exception
|
|
||||||
* error message which is not likely to be meaningful for such a
|
|
||||||
* situation.
|
|
||||||
*/
|
|
||||||
public abstract class FriendlyServlet extends WMServlet
|
|
||||||
{
|
|
||||||
public Template handle (WebContext ctx) throws HandlerException
|
|
||||||
{
|
|
||||||
// first we select the template
|
|
||||||
Template tmpl;
|
|
||||||
try {
|
|
||||||
tmpl = selectTemplate(ctx);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new HandlerException("Unable to load template: " + e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// then we populate the context with data
|
|
||||||
try {
|
|
||||||
populateContext(ctx, tmpl);
|
|
||||||
} catch (Exception e) {
|
|
||||||
ctx.put(ERROR_KEY, ExceptionMap.getMessage(e));
|
|
||||||
}
|
|
||||||
|
|
||||||
return tmpl;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Override this member function with code that selects and loads the
|
|
||||||
* proper template based on whatever criterion appropriate for this
|
|
||||||
* servlet.
|
|
||||||
*
|
|
||||||
* @param ctx The context of this request.
|
|
||||||
*
|
|
||||||
* @return The template to be used in generating the response.
|
|
||||||
*/
|
|
||||||
protected abstract Template selectTemplate (WebContext ctx)
|
|
||||||
throws Exception;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Override this member function with code that populates the context
|
|
||||||
* with whatever data is appropriate for this servlet. Presumably this
|
|
||||||
* is where the real work of the servlet takes place.
|
|
||||||
*
|
|
||||||
* @param ctx The context of this request.
|
|
||||||
* @param tmpl The webmacro template previously selected by the
|
|
||||||
* invocation of the <code>selectTemplate</code> method.
|
|
||||||
*/
|
|
||||||
protected abstract void populateContext (WebContext ctx, Template tmpl)
|
|
||||||
throws Exception;
|
|
||||||
|
|
||||||
/** This is the key used in the context for error messages. */
|
|
||||||
protected static final String ERROR_KEY = "error";
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,16 @@
|
|||||||
#
|
#
|
||||||
# $Id: Makefile,v 1.2 2001/02/13 20:00:28 mdb Exp $
|
# $Id: Makefile,v 1.3 2001/02/15 01:44:34 mdb Exp $
|
||||||
|
|
||||||
ROOT = ../../..
|
ROOT = ../../..
|
||||||
|
|
||||||
SRCS = \
|
SRCS = \
|
||||||
|
ContextPopulator.java \
|
||||||
|
DataValidationException.java \
|
||||||
|
DispatcherServlet.java \
|
||||||
|
DummyPopulator.java \
|
||||||
ExceptionMap.java \
|
ExceptionMap.java \
|
||||||
|
FormUtil.java \
|
||||||
FriendlyException.java \
|
FriendlyException.java \
|
||||||
FriendlyServlet.java \
|
|
||||||
Log.java \
|
Log.java \
|
||||||
|
|
||||||
include $(ROOT)/build/Makefile.java
|
include $(ROOT)/build/Makefile.java
|
||||||
|
|||||||
Reference in New Issue
Block a user