Various enhancements to our webmacro extensions.

git-svn-id: https://samskivert.googlecode.com/svn/trunk@60 6335cc39-0255-0410-8fd6-9bcaacd3b74c
This commit is contained in:
mdb
2001-03-01 21:06:22 +00:00
parent 65300ebc10
commit c0be9ea48d
8 changed files with 265 additions and 99 deletions
@@ -1,30 +0,0 @@
//
// $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;
}
@@ -1,5 +1,5 @@
//
// $Id: DispatcherServlet.java,v 1.2 2001/02/16 03:27:54 mdb Exp $
// $Id: DispatcherServlet.java,v 1.3 2001/03/01 21:06:22 mdb Exp $
package com.samskivert.webmacro;
@@ -9,7 +9,11 @@ import java.util.HashMap;
import java.util.Properties;
import java.util.StringTokenizer;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import com.samskivert.util.ConfigUtil;
@@ -22,12 +26,12 @@ import org.webmacro.servlet.*;
* 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 defines the notion of a logic object which populates the
* context with data to be used to satisfy a particular request. The logic
* 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
@@ -60,7 +64,7 @@ import org.webmacro.servlet.*;
* <pre>
* applications=whowhere
* whowhere.base_uri=/whowhere
* whowhere.base_pkg=whowhere.servlets
* whowhere.base_pkg=whowhere.logic
* </pre>
*
* This defines an application identified as <code>whowhere</code>. An
@@ -69,10 +73,10 @@ import org.webmacro.servlet.*;
* <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
* construct the logic 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
* <p> Now let's look at a sample request to determine how the logic
* classname is resolved. Consider the following request URI:
*
* <pre>
@@ -102,16 +106,16 @@ import org.webmacro.servlet.*;
*
* <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
* design is to catch any exceptions thrown by the logic 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 Logic
* @see ExceptionMap
*/
public class DispatcherServlet extends WMServlet
@@ -148,6 +152,7 @@ public class DispatcherServlet extends WMServlet
String appid = tok.nextToken();
String baseURI = props.getProperty(appid + ".base_uri");
String basePkg = props.getProperty(appid + ".base_pkg");
String appcl = props.getProperty(appid + ".application");
// make sure we're not missing anything
if (baseURI == null) {
@@ -161,8 +166,14 @@ public class DispatcherServlet extends WMServlet
continue;
}
Application app = new Application(baseURI, basePkg);
// if an application class was specified, initialize it
if (appcl != null) {
app.init(appcl, getServletContext());
}
// construct an application object and add it to our list
_apps.add(new Application(baseURI, basePkg));
_apps.add(app);
}
}
}
@@ -177,19 +188,18 @@ public class DispatcherServlet extends WMServlet
throw new HandlerException("Unable to load template: " + e);
}
// assume an HTML response unless otherwise massaged by the data
// populator
// assume an HTML response unless otherwise massaged by the logic
ctx.getResponse().setContentType("text/html");
// then we populate the context with data
try {
ContextPopulator pop = selectPopulator(ctx);
// if no populator is matched, we simply execute the template
Logic logic = selectLogic(ctx);
// if no logic is matched, we simply execute the template
// directly with the default information in the context (tools
// and other WebMacro services can be used by the template to
// do their WebMacro thing)
if (pop != null) {
pop.populate(ctx);
if (logic != null) {
logic.invoke(ctx);
}
} catch (DataValidationException dve) {
@@ -197,6 +207,7 @@ public class DispatcherServlet extends WMServlet
} catch (Exception e) {
ctx.put(ERROR_KEY, ExceptionMap.getMessage(e));
Log.logStackTrace(e);
}
return tmpl;
@@ -222,57 +233,57 @@ public class DispatcherServlet extends WMServlet
}
/**
* 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.
* This method is called to select the appropriate logic for this
* request. The dispatcher configuration described in this class's
* documentation is consulted to map the URI to a logic class which is
* then instantiated and a single instance used to process all
* matching requests.
*
* @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.
* @return The logic to be used in generating the response or null if
* no logic could be matched.
*/
protected ContextPopulator selectPopulator (WebContext ctx)
protected Logic selectLogic (WebContext ctx)
{
String path = cleanupURI(ctx.getRequest().getRequestURI());
String pclass = null;
// Log.info("Loading populator [path=" + path + "].");
String lclass = null;
// Log.info("Loading logic [path=" + path + "].");
// try to locate an application that matches this URI
for (int i = 0; i < _apps.size(); i++) {
Application app = (Application)_apps.get(i);
if (app.matches(path)) {
pclass = app.generateClass(path);
lclass = app.generateClass(path);
break;
}
}
// if we didn't find a matching application, we can stop now
if (pclass == null) {
if (lclass == null) {
return null;
}
// otherwise look for a cached populator instance
ContextPopulator pop = (ContextPopulator)_pops.get(pclass);
if (pop == null) {
// otherwise look for a cached logic instance
Logic logic = (Logic)_logic.get(lclass);
if (logic == null) {
try {
Class pcl = Class.forName(pclass);
pop = (ContextPopulator)pcl.newInstance();
Class pcl = Class.forName(lclass);
logic = (Logic)pcl.newInstance();
} catch (Throwable t) {
Log.warning("Unable to instantiate populator for " +
Log.warning("Unable to instantiate logic for " +
"matching application [path=" + path +
", pclass=" + pclass + ", error=" + t + "].");
", lclass=" + lclass + ", 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();
// the proper logic class
logic = new DummyLogic();
}
_pops.put(pclass, pop);
_logic.put(lclass, logic);
}
return pop;
return logic;
}
/**
@@ -343,12 +354,73 @@ public class DispatcherServlet extends WMServlet
return _basePkg + uri;
}
public void init (String appclass, ServletContext context)
{
// keep track of this for later
_appclass = appclass;
try {
// get the application class
Class appcl = Class.forName(appclass);
// look up the init method
Class[] ptypes = new Class[] { ServletContext.class };
Method initmeth = appcl.getDeclaredMethod("init", ptypes);
// make sure it's static
if ((initmeth.getModifiers() & Modifier.STATIC) == 0) {
Log.warning("Application init() method not static " +
"[app=" + appclass + "].");
return;
}
// look up the shutdown method
_shutdownMethod =
appcl.getDeclaredMethod("shutdown", new Class[0]);
// make sure it's static
if ((_shutdownMethod.getModifiers() & Modifier.STATIC) == 0) {
Log.warning("Application shutdown() method not static " +
"[app=" + appclass + "].");
return;
}
// invoke the init method
Object[] args = new Object[] { context };
initmeth.invoke(null, args);
} catch (NoSuchMethodError nsme) {
Log.warning("Application class missing init() method " +
"[app=" + appclass + "].");
} catch (Throwable t) {
Log.warning("Error initializing application [app=" + appclass +
", error=" + t + "].");
}
}
public void shutdown ()
{
if (_shutdownMethod != null) {
try {
_shutdownMethod.invoke(null, null);
} catch (Throwable t) {
Log.warning("Error shutting down application " +
"[app=" + _appclass + ", error=" + t + "].");
Log.logStackTrace(t);
}
}
}
protected String _baseURI;
protected String _basePkg;
protected String _appclass;
protected Method _shutdownMethod;
}
protected ArrayList _apps = new ArrayList();
protected HashMap _pops = new HashMap();
protected HashMap _logic = new HashMap();
/** This is the key used in the context for error messages. */
protected static final String ERROR_KEY = "error";
@@ -0,0 +1,19 @@
//
// $Id: DummyLogic.java,v 1.1 2001/03/01 21:06:22 mdb Exp $
package com.samskivert.webmacro;
import org.webmacro.servlet.WebContext;
/**
* 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 (WebContext context) throws Exception
{
// we're such a dummy that we do absolutely nothing.
}
}
@@ -1,19 +0,0 @@
//
// $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,8 +1,13 @@
//
// $Id: FormUtil.java,v 1.1 2001/02/15 01:44:34 mdb Exp $
// $Id: FormUtil.java,v 1.2 2001/03/01 21:06:22 mdb Exp $
package com.samskivert.webmacro;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import com.samskivert.util.StringUtil;
import org.webmacro.servlet.WebContext;
/**
@@ -28,4 +33,87 @@ public class FormUtil
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 (WebContext context, String name,
String missingDataMessage)
throws DataValidationException
{
String value = context.getForm(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 (WebContext context, String name,
String invalidDataMessage)
throws DataValidationException
{
String value = context.getForm(name);
Date date = null;
try {
if (value != null) {
date = _dparser.parse(value);
}
} catch (ParseException pe) {
// fall through with date == null
Log.info("Date parsing failed: " + pe);
}
// freak out if we failed to parse the date for some reason
if (date == null) {
throw new DataValidationException(invalidDataMessage);
}
return date;
}
/**
* Returns true if the specified parameter is set in the request
* context.
*
* @return true if the specified parameter is set in the request
* context, false otherwise.
*/
public static boolean isSet (WebContext context, String name)
{
return !StringUtil.blank(context.getForm(name));
}
/**
* Returns true if the specified parameter is equal to the supplied
* value. If the parameter is not set in the request context, false is
* returned.
*
* @param context The request context.
* @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 equals (WebContext context, String name,
String value)
{
return value.equals(context.getForm(name));
}
/** We use this to parse dates in requireDateParameter(). */
protected static SimpleDateFormat _dparser =
new SimpleDateFormat("yyyy-MM-dd");
}
@@ -1,5 +1,5 @@
//
// $Id: Log.java,v 1.2 2001/02/16 03:27:54 mdb Exp $
// $Id: Log.java,v 1.3 2001/03/01 21:06:22 mdb Exp $
package com.samskivert.webmacro;
@@ -29,4 +29,10 @@ public class Log
{
log.warning(message);
}
/** Convenience function. */
public static void logStackTrace (Throwable t)
{
log.logStackTrace(com.samskivert.util.Log.WARNING, t);
}
}
@@ -0,0 +1,30 @@
//
// $Id: Logic.java,v 1.1 2001/03/01 21:06:22 mdb Exp $
package com.samskivert.webmacro;
import org.webmacro.servlet.WebContext;
/**
* 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 context The WebMacro context in scope for this request.
*
* @see ExceptionMap
*/
public void invoke (WebContext context) throws Exception;
}
@@ -1,16 +1,16 @@
#
# $Id: Makefile,v 1.3 2001/02/15 01:44:34 mdb Exp $
# $Id: Makefile,v 1.4 2001/03/01 21:06:22 mdb Exp $
ROOT = ../../..
SRCS = \
ContextPopulator.java \
DataValidationException.java \
DispatcherServlet.java \
DummyPopulator.java \
DummyLogic.java \
ExceptionMap.java \
FormUtil.java \
FriendlyException.java \
Log.java \
Logic.java \
include $(ROOT)/build/Makefile.java