diff --git a/projects/samskivert/src/java/com/samskivert/webmacro/ContextPopulator.java b/projects/samskivert/src/java/com/samskivert/webmacro/ContextPopulator.java
new file mode 100644
index 00000000..9e4e7845
--- /dev/null
+++ b/projects/samskivert/src/java/com/samskivert/webmacro/ContextPopulator.java
@@ -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;
+}
diff --git a/projects/samskivert/src/java/com/samskivert/webmacro/DataValidationException.java b/projects/samskivert/src/java/com/samskivert/webmacro/DataValidationException.java
new file mode 100644
index 00000000..6d85dc58
--- /dev/null
+++ b/projects/samskivert/src/java/com/samskivert/webmacro/DataValidationException.java
@@ -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
+ * "invalid_data". 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);
+ }
+}
diff --git a/projects/samskivert/src/java/com/samskivert/webmacro/DispatcherServlet.java b/projects/samskivert/src/java/com/samskivert/webmacro/DispatcherServlet.java
new file mode 100644
index 00000000..6ed70ef5
--- /dev/null
+++ b/projects/samskivert/src/java/com/samskivert/webmacro/DispatcherServlet.java
@@ -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:
+ *
+ *
URI to servlet mapping
+ * The mapping process allows the webmacro 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 Dispatcher servlet for all requests
+ * ending in that extension. For Apache/JServ this looks something like
+ * this:
+ *
+ *
+ * ApJServAction .wm /servlets/com.samskivert.webmacro.Dispatcher + *+ * + * 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: + * + *
+ * applications=whowhere + * whowhere.base_uri=/whowhere + * whowhere.base_pkg=whowhere.servlets + *+ * + * 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 populator classname based on the URI and the
+ * base_uri parameter.
+ *
+ * Now let's look at a sample request to determine how the populator + * 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 populator and to
+ * convert them into friendly error messages that are inserted into the
+ * web 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 ExceptionMap 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";
+}
diff --git a/projects/samskivert/src/java/com/samskivert/webmacro/DummyPopulator.java b/projects/samskivert/src/java/com/samskivert/webmacro/DummyPopulator.java
new file mode 100644
index 00000000..8fcf555a
--- /dev/null
+++ b/projects/samskivert/src/java/com/samskivert/webmacro/DummyPopulator.java
@@ -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.
+ }
+}
diff --git a/projects/samskivert/src/java/com/samskivert/webmacro/ExceptionMap.java b/projects/samskivert/src/java/com/samskivert/webmacro/ExceptionMap.java
index 915eec37..ff5afbee 100644
--- a/projects/samskivert/src/java/com/samskivert/webmacro/ExceptionMap.java
+++ b/projects/samskivert/src/java/com/samskivert/webmacro/ExceptionMap.java
@@ -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;
import java.io.*;
import java.util.*;
+import com.samskivert.util.ConfigUtil;
import com.samskivert.util.StringUtil;
/**
- * The exception map is used to load the exception to error message
- * mapping information and to look up the appropriate response for a given
- * exception instance.
+ * The exception map is used to map exceptions to error messages based on
+ * a static, server-wide configuration.
*
- * @see FriendlyServlet
+ *
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.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.
+ *
+ *
+ * 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 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
+ * "error") 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
{
@@ -33,13 +69,8 @@ public class ExceptionMap
}
// first try loading the properties file without a leading slash
- InputStream config = getStream(PROPS_NAME);
- if (config == null) {
- // some JVMs require the leading slash, some don't
- config = getStream("/" + PROPS_NAME);
- }
-
- // still no props, then we complain
+ ClassLoader cld = ExceptionMap.class.getClassLoader();
+ InputStream config = ConfigUtil.getStream(PROPS_NAME, cld);
if (config == null) {
Log.log.warning("Unable to load " + PROPS_NAME +
" from CLASSPATH.");
@@ -111,19 +142,6 @@ public class ExceptionMap
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)
{
ExceptionMap map = new ExceptionMap();
@@ -137,6 +155,6 @@ public class ExceptionMap
static { init(); }
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}";
}
diff --git a/projects/samskivert/src/java/com/samskivert/webmacro/FormUtil.java b/projects/samskivert/src/java/com/samskivert/webmacro/FormUtil.java
new file mode 100644
index 00000000..d9ef799f
--- /dev/null
+++ b/projects/samskivert/src/java/com/samskivert/webmacro/FormUtil.java
@@ -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);
+ }
+ }
+}
diff --git a/projects/samskivert/src/java/com/samskivert/webmacro/FriendlyException.java b/projects/samskivert/src/java/com/samskivert/webmacro/FriendlyException.java
index 867446f7..d739008c 100644
--- a/projects/samskivert/src/java/com/samskivert/webmacro/FriendlyException.java
+++ b/projects/samskivert/src/java/com/samskivert/webmacro/FriendlyException.java
@@ -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;
@@ -11,7 +11,7 @@ package com.samskivert.webmacro;
* construct a friendly exception with the desired error message and throw
* it during the call to populateContext.
*
- * @see FriendlyServlet
+ * @see DispatcherServlet
*/
public class FriendlyException extends Exception
{
diff --git a/projects/samskivert/src/java/com/samskivert/webmacro/FriendlyServlet.java b/projects/samskivert/src/java/com/samskivert/webmacro/FriendlyServlet.java
deleted file mode 100644
index 0a545f79..00000000
--- a/projects/samskivert/src/java/com/samskivert/webmacro/FriendlyServlet.java
+++ /dev/null
@@ -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 populateContext method (used instead of the
- * handle method) and to convert them into friendly error
- * messages that are inserted into the context with the key
- * "error" for easy display to the user in the resulting web
- * page.
- *
- *
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. - * - *
The process of mapping exceptions to friendly error messages is
- * done through a configuration file 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.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.
- *
- *
- * 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 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.
- *
- * Note: 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
- * "error") 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 selectTemplate 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";
-}
diff --git a/projects/samskivert/src/java/com/samskivert/webmacro/Makefile b/projects/samskivert/src/java/com/samskivert/webmacro/Makefile
index 3087464c..7a6bc4a6 100644
--- a/projects/samskivert/src/java/com/samskivert/webmacro/Makefile
+++ b/projects/samskivert/src/java/com/samskivert/webmacro/Makefile
@@ -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 = ../../..
SRCS = \
+ ContextPopulator.java \
+ DataValidationException.java \
+ DispatcherServlet.java \
+ DummyPopulator.java \
ExceptionMap.java \
+ FormUtil.java \
FriendlyException.java \
- FriendlyServlet.java \
Log.java \
include $(ROOT)/build/Makefile.java