diff --git a/build.xml b/build.xml index 6071f229..d0328dd9 100644 --- a/build.xml +++ b/build.xml @@ -57,8 +57,7 @@ + debug="on" optimize="${build.optimize}" source="1.5" target="1.5" encoding="utf-8"> @@ -73,7 +72,6 @@ - diff --git a/src/java/com/samskivert/jdbc/DatabaseLiaison.java b/src/java/com/samskivert/jdbc/DatabaseLiaison.java index 9b59fa4f..a742d234 100644 --- a/src/java/com/samskivert/jdbc/DatabaseLiaison.java +++ b/src/java/com/samskivert/jdbc/DatabaseLiaison.java @@ -57,23 +57,9 @@ public interface DatabaseLiaison */ public boolean isTransientException (SQLException sqe); - /** - * Returns the value of an AUTO_INCREMENT column for the last row insertion. This - * is MySQL specific, but exists here for now until we sort out a general purpose mechanism for - * assigning unique ids. - * - * Note: More general purpose: {@link #lastInsertedId(Connection, String, String) - * - * @return the most recent ID generated by an insert into an AUTO_INCREMENT table - * or -1 if it could not be obtained. - */ - @Deprecated - public int lastInsertedId (Connection conn) - throws SQLException; - /** * This method initializes the column value auto-generator described in {@link - * #lastInsertedId(Connection, String, String)}. + * #lastInsertedId}. */ public void initializeGenerator ( Connection conn, String table, String column, int first, int step) diff --git a/src/java/com/samskivert/jdbc/DefaultLiaison.java b/src/java/com/samskivert/jdbc/DefaultLiaison.java index 369e5823..d48f1d70 100644 --- a/src/java/com/samskivert/jdbc/DefaultLiaison.java +++ b/src/java/com/samskivert/jdbc/DefaultLiaison.java @@ -47,7 +47,7 @@ public class DefaultLiaison extends BaseLiaison return false; } - // from DatabaseLiaison + @SuppressWarnings("deprecation") // from DatabaseLiaison public int lastInsertedId (Connection conn) throws SQLException { return -1; diff --git a/src/java/com/samskivert/jdbc/JORARepository.java b/src/java/com/samskivert/jdbc/JORARepository.java index 978bbac2..d97beeaf 100644 --- a/src/java/com/samskivert/jdbc/JORARepository.java +++ b/src/java/com/samskivert/jdbc/JORARepository.java @@ -65,7 +65,7 @@ public abstract class JORARepository extends SimpleRepository throws SQLException, PersistenceException { table.insert(conn, object); - return liaison.lastInsertedId(conn); + return liaison.lastInsertedId(conn, table.getName(), "TODO"); } }); } @@ -265,7 +265,7 @@ public abstract class JORARepository extends SimpleRepository { if (table.update(conn, object) == 0) { table.insert(conn, object); - return liaison.lastInsertedId(conn); + return liaison.lastInsertedId(conn, table.getName(), "TODO"); } return -1; } diff --git a/src/java/com/samskivert/jdbc/MySQLLiaison.java b/src/java/com/samskivert/jdbc/MySQLLiaison.java index 66e21c86..210f8a97 100644 --- a/src/java/com/samskivert/jdbc/MySQLLiaison.java +++ b/src/java/com/samskivert/jdbc/MySQLLiaison.java @@ -47,16 +47,9 @@ public class MySQLLiaison extends BaseLiaison public boolean isTransientException (SQLException sqe) { String msg = sqe.getMessage(); - return (msg != null && - (msg.indexOf("Lost connection") != -1 || - msg.indexOf("link failure") != -1 || - msg.indexOf("Broken pipe") != -1)); - } - - // from DatabaseLiaison - public int lastInsertedId (Connection conn) throws SQLException - { - return lastInsertedId(conn, null, null); + return (msg != null && (msg.indexOf("Lost connection") != -1 || + msg.indexOf("link failure") != -1 || + msg.indexOf("Broken pipe") != -1)); } // from DatabaseLiaison diff --git a/src/java/com/samskivert/jdbc/PostgreSQLLiaison.java b/src/java/com/samskivert/jdbc/PostgreSQLLiaison.java index 413027eb..329adfa5 100644 --- a/src/java/com/samskivert/jdbc/PostgreSQLLiaison.java +++ b/src/java/com/samskivert/jdbc/PostgreSQLLiaison.java @@ -49,13 +49,6 @@ public class PostgreSQLLiaison extends BaseLiaison msg.indexOf("An I/O error occured while sending to the backend") != -1); } - // from DatabaseLiaison - public int lastInsertedId (Connection conn) throws SQLException - { - throw new SQLException( - "No backwards-compatible lastInsertedId() support for PostgreSQL in this code."); - } - // from DatabaseLiaison public int lastInsertedId (Connection conn, String table, String column) throws SQLException { diff --git a/src/java/com/samskivert/jdbc/depot/DepotRepository.java b/src/java/com/samskivert/jdbc/depot/DepotRepository.java index c9734b54..d116c100 100644 --- a/src/java/com/samskivert/jdbc/depot/DepotRepository.java +++ b/src/java/com/samskivert/jdbc/depot/DepotRepository.java @@ -27,6 +27,7 @@ import java.sql.SQLException; import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Set; import com.samskivert.io.PersistenceException; import com.samskivert.util.ArrayUtil; @@ -50,7 +51,7 @@ import com.samskivert.jdbc.depot.expression.ValueExp; * Provides a base for classes that provide access to persistent objects. Also defines the * mechanism by which all persistent queries and updates are routed through the distributed cache. */ -public class DepotRepository +public abstract class DepotRepository { /** * Creates a repository with the supplied connection provider and its own private persistence @@ -59,6 +60,7 @@ public class DepotRepository protected DepotRepository (ConnectionProvider conprov) { _ctx = new PersistenceContext(getClass().getName(), conprov); + _ctx.repositoryCreated(this); } /** @@ -67,8 +69,14 @@ public class DepotRepository protected DepotRepository (PersistenceContext context) { _ctx = context; + _ctx.repositoryCreated(this); } + /** + * Adds the persistent classes used by this repository to the supplied set. + */ + protected abstract void getManagedRecords (Set> classes); + /** * Loads the persistent object that matches the specified primary key. */ diff --git a/src/java/com/samskivert/jdbc/depot/PersistenceContext.java b/src/java/com/samskivert/jdbc/depot/PersistenceContext.java index 4d6eeba2..db725910 100644 --- a/src/java/com/samskivert/jdbc/depot/PersistenceContext.java +++ b/src/java/com/samskivert/jdbc/depot/PersistenceContext.java @@ -20,8 +20,11 @@ package com.samskivert.jdbc.depot; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; @@ -393,6 +396,29 @@ public class PersistenceContext listenerSet.add(listener); } + /** + * Iterates over all {@link PersistentRecord} classes managed by this context and initializes + * their {@link DepotMarshaller}. This forces migrations to run and the database schema to be + * created. + */ + public void initializeManagedRecords () + throws PersistenceException + { + for (Class rclass : _managedRecords) { + getMarshaller(rclass); + } + } + + /** + * Called when a depot repository is created. We register all persistent record classes used by + * the repository so that systems that desire it can force the resolution of all database + * tables rather than allowing resolution to happen on demand. + */ + protected void repositoryCreated (DepotRepository repo) + { + repo.getManagedRecords(_managedRecords); + } + /** * Looks up and creates, but does not initialize, the marshaller for the specified Entity * type. @@ -474,4 +500,16 @@ public class PersistenceContext protected Map, DepotMarshaller> _marshallers = new HashMap, DepotMarshaller>(); + + /** + * The set of persistent records for which this context is responsible. This data is used by + * {@link #initializeManagedRecords} to force migration/schema initialization. + */ + protected Set> _managedRecords = + new HashSet>(); + + /** + * A collection of all {@link PersistenceContext} objects in existence. + */ + protected static List _contexts = new ArrayList(); } diff --git a/src/java/com/samskivert/jdbc/depot/tests/TestRepository.java b/src/java/com/samskivert/jdbc/depot/tests/TestRepository.java index 84304c0d..89b7934f 100644 --- a/src/java/com/samskivert/jdbc/depot/tests/TestRepository.java +++ b/src/java/com/samskivert/jdbc/depot/tests/TestRepository.java @@ -22,10 +22,12 @@ package com.samskivert.jdbc.depot.tests; import java.sql.Date; import java.sql.Timestamp; +import java.util.Set; import com.samskivert.jdbc.ConnectionProvider; import com.samskivert.jdbc.StaticConnectionProvider; import com.samskivert.jdbc.depot.DepotRepository; +import com.samskivert.jdbc.depot.PersistentRecord; /** * A test tool for the Depot repository services. @@ -59,4 +61,10 @@ public class TestRepository extends DepotRepository { super(conprov); } + + @Override // from DepotRepository + protected void getManagedRecords (Set> classes) + { + classes.add(TestRecord.class); + } } diff --git a/src/java/com/samskivert/servlet/JDBCTableSiteIdentifier.java b/src/java/com/samskivert/servlet/JDBCTableSiteIdentifier.java index 4132acc1..6a25b790 100644 --- a/src/java/com/samskivert/servlet/JDBCTableSiteIdentifier.java +++ b/src/java/com/samskivert/servlet/JDBCTableSiteIdentifier.java @@ -254,7 +254,7 @@ public class JDBCTableSiteIdentifier implements SiteIdentifier if (1 != stmt.executeUpdate()) { throw new PersistenceException("Not inserted " + site); } - site.siteId = liaison.lastInsertedId(conn); + site.siteId = liaison.lastInsertedId(conn, "sites", "siteId"); } finally { JDBCUtil.close(stmt); diff --git a/src/java/com/samskivert/servlet/user/UserRepository.java b/src/java/com/samskivert/servlet/user/UserRepository.java index 9bd60bf2..5e77b88f 100644 --- a/src/java/com/samskivert/servlet/user/UserRepository.java +++ b/src/java/com/samskivert/servlet/user/UserRepository.java @@ -420,7 +420,7 @@ public class UserRepository extends JORARepository try { _utable.insert(conn, user); // update the userid now that it's known - user.userId = liaison.lastInsertedId(conn); + user.userId = liaison.lastInsertedId(conn, _utable.getName(), "userId"); // nothing to return return null; diff --git a/src/java/com/samskivert/swing/SmartPolygon.java b/src/java/com/samskivert/swing/SmartPolygon.java index d33c5254..2996060d 100644 --- a/src/java/com/samskivert/swing/SmartPolygon.java +++ b/src/java/com/samskivert/swing/SmartPolygon.java @@ -34,6 +34,7 @@ public class SmartPolygon extends Polygon * Returns the internally cached bounds rectangle for this polygon. * Don't modify it! */ + @SuppressWarnings("deprecation") public Rectangle getBoundingBox () { return (bounds == null) ? super.getBoundingBox() : bounds; diff --git a/src/java/com/samskivert/swing/TGraphics2D.java b/src/java/com/samskivert/swing/TGraphics2D.java index d8977a67..5974fddb 100644 --- a/src/java/com/samskivert/swing/TGraphics2D.java +++ b/src/java/com/samskivert/swing/TGraphics2D.java @@ -553,10 +553,11 @@ public class TGraphics2D extends Graphics2D return _primary.toString(); } + @SuppressWarnings("deprecation") public Rectangle getClipRect () { - // getClipRect is deprecated, but getClipBounds is the new way - // to do the same thing. We call that to avoid deprecation warnings. + // getClipRect is deprecated, but getClipBounds is the new way to do the same thing. We + // call that to avoid deprecation warnings. return _primary.getClipBounds(); } diff --git a/src/java/com/samskivert/swing/tests/AdjustTestApp.java b/src/java/com/samskivert/swing/tests/AdjustTestApp.java index 66756712..4989c93b 100644 --- a/src/java/com/samskivert/swing/tests/AdjustTestApp.java +++ b/src/java/com/samskivert/swing/tests/AdjustTestApp.java @@ -62,6 +62,6 @@ public class AdjustTestApp frame.getContentPane().add(RuntimeAdjust.createAdjustEditor(), BorderLayout.CENTER); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.pack(); - frame.show(); + frame.setVisible(true); } } diff --git a/src/java/com/samskivert/swing/tests/LabelDemo.java b/src/java/com/samskivert/swing/tests/LabelDemo.java index 802e002a..fa4dd522 100644 --- a/src/java/com/samskivert/swing/tests/LabelDemo.java +++ b/src/java/com/samskivert/swing/tests/LabelDemo.java @@ -143,7 +143,7 @@ public class LabelDemo extends JPanel LabelDemo demo = new LabelDemo(); frame.getContentPane().add(demo); frame.pack(); - frame.show(); + frame.setVisible(true); } protected Label[] _labels = new Label[10]; diff --git a/src/java/com/samskivert/swing/tests/TestComboButtonBox.java b/src/java/com/samskivert/swing/tests/TestComboButtonBox.java index 65d79bc4..5df172f4 100644 --- a/src/java/com/samskivert/swing/tests/TestComboButtonBox.java +++ b/src/java/com/samskivert/swing/tests/TestComboButtonBox.java @@ -59,6 +59,6 @@ public class TestComboButtonBox ComboButtonBox box = new ComboButtonBox(ComboButtonBox.HORIZONTAL, model); frame.getContentPane().add(box); frame.pack(); - frame.show(); + frame.setVisible(true); } } diff --git a/src/java/com/samskivert/velocity/DispatcherServlet.java b/src/java/com/samskivert/velocity/DispatcherServlet.java index 28341a77..b9a90aec 100644 --- a/src/java/com/samskivert/velocity/DispatcherServlet.java +++ b/src/java/com/samskivert/velocity/DispatcherServlet.java @@ -3,7 +3,7 @@ // // samskivert library - useful routines for java programs // Copyright (C) 2001-2007 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 @@ -21,26 +21,33 @@ package com.samskivert.velocity; import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; import java.util.HashMap; import java.util.Properties; +import java.util.logging.Level; import javax.servlet.ServletConfig; import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServlet; 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.MethodInvocationException; import org.apache.velocity.exception.ParseErrorException; import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.io.VelocityWriter; +import org.apache.velocity.runtime.RuntimeConstants; import org.apache.velocity.runtime.RuntimeSingleton; -import org.apache.velocity.servlet.VelocityServlet; +import org.apache.velocity.util.SimplePool; +import org.apache.velocity.app.Velocity; import org.apache.velocity.app.event.EventCartridge; import org.apache.velocity.app.event.MethodExceptionEventHandler; -import com.samskivert.Log; - import com.samskivert.servlet.HttpErrorException; import com.samskivert.servlet.MessageManager; import com.samskivert.servlet.RedirectException; @@ -51,46 +58,39 @@ import com.samskivert.servlet.util.FriendlyException; import com.samskivert.util.ConfigUtil; import com.samskivert.util.StringUtil; +import static com.samskivert.Log.log; + /** - * The dispatcher servlet builds upon Velocity's architecture. It does so - * in the following ways: + * The dispatcher servlet builds upon Velocity's architecture. It does so in the following ways: * - *
    - *
  • 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.

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

      * - *
    • It allows template files to be referenced directly in the URL - * while maintaining the ability to choose a cobranded template based on - * information in the request. 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.

      + *
    • It allows template files to be referenced directly in the URL while maintaining the ability + * to choose a cobranded template based on information in the request. 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.

      * - *
    • It provides a common error handling paradigm that simplifies the - * task of authoring web applications. + *
    • It provides a common error handling paradigm that simplifies the task of authoring web + * applications. *
    * - *

    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: + *

    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: + * 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
    @@ -98,48 +98,44 @@ import com.samskivert.util.StringUtil;
      * 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. + * 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: + *

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

    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). * * @see Logic */ -public class DispatcherServlet extends VelocityServlet +public class DispatcherServlet extends HttpServlet implements MethodExceptionEventHandler { + /** The HTTP content type context key. */ + public static final String CONTENT_TYPE = "default.contentType"; + /** * Performs various initialization. */ @@ -148,29 +144,6 @@ public class DispatcherServlet extends VelocityServlet { super.init(config); - // determine the character set we'll use - _charset = config.getInitParameter(CHARSET_KEY); - if (_charset == null) { - _charset = "UTF-8"; - } - } - - /** - * Clean up after ourselves and our application. - */ - public void destroy () - { - super.destroy(); - // shutdown our application - _app.shutdown(); - } - - /** - * Initialize ourselves and our application. - */ - protected void initVelocity (ServletConfig config) - throws ServletException - { // load up our application configuration try { String appcl = config.getInitParameter(APP_CLASS_KEY); @@ -187,26 +160,62 @@ public class DispatcherServlet extends VelocityServlet StringUtil.isBlank(logicPkg) ? "" : logicPkg); } catch (Throwable t) { - Log.warning("Error instantiating application."); - Log.logStackTrace(t); + throw new ServletException("Error instantiating Application: " + t, t); } - // now let velocity initialize itself - super.initVelocity(config); + try { + Velocity.init(loadConfiguration(config)); + } catch (Exception e) { + throw new ServletException("Error initializing Velocity: " + e, e); + } + + _defaultContentType = RuntimeSingleton.getString(CONTENT_TYPE, DEFAULT_CONTENT_TYPE); + + // determine the character set we'll use + _charset = config.getInitParameter(CHARSET_KEY); + if (_charset == null) { + _charset = "UTF-8"; + } } /** - * We load our velocity properties from the classpath rather than from - * a file. + * Handles HTTP GET requests by calling {@link #doRequest()}. + */ + public void doGet (HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + doRequest(request, response); + } + + /** + * Handles HTTP POST requests by calling {@link #doRequest()}. + */ + public void doPost (HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + doRequest(request, response); + } + + /** + * Clean up after ourselves and our application. + */ + public void destroy () + { + super.destroy(); + // shutdown our application + _app.shutdown(); + } + + /** + * 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); if (propsPath == null) { - throw new IOException(INIT_PROPS_KEY + " must point to " + - "the velocity properties file in " + - "the servlet configuration."); + throw new IOException(INIT_PROPS_KEY + " must point to the velocity properties file " + + "in the servlet configuration."); } // config util loads properties files from the classpath @@ -230,16 +239,15 @@ public class DispatcherServlet extends VelocityServlet if (props.getProperty("file.resource.loader.path") == null) { SiteResourceLoader siteLoader = _app.getSiteResourceLoader(); if (siteLoader != null) { - Log.info("Velocity loading templates from site loader."); + log.info("Velocity loading templates from site loader."); props.setProperty(RuntimeSingleton.RESOURCE_MANAGER_CLASS, SiteResourceManager.class.getName()); _usingSiteLoading = true; } else { // otherwise use a servlet context resource loader - Log.info("Velocity loading templates from servlet context."); - props.setProperty( - RuntimeSingleton.RESOURCE_MANAGER_CLASS, - ServletContextResourceManager.class.getName()); + log.info("Velocity loading templates from servlet context."); + props.setProperty(RuntimeSingleton.RESOURCE_MANAGER_CLASS, + ServletContextResourceManager.class.getName()); } } @@ -247,21 +255,18 @@ public class DispatcherServlet extends VelocityServlet props.setProperty("userdirective", ImportDirective.class.getName()); // configure the servlet context logger - props.setProperty(RuntimeSingleton.RUNTIME_LOG_LOGSYSTEM_CLASS, - ServletContextLogger.class.getName()); + props.put(RuntimeSingleton.RUNTIME_LOG_LOGSYSTEM, new ServletContextLogger()); // now return our augmented properties return props; } /** - * 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. + * 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) + public Template handleRequest (HttpServletRequest req, HttpServletResponse rsp, Context ctx) throws Exception { InvocationContext ictx = (InvocationContext)ctx; @@ -416,9 +421,8 @@ public class DispatcherServlet extends VelocityServlet public Object methodException (Class clazz, String method, Exception e) throws Exception { - Log.warning("Exception [class=" + clazz.getName() + - ", method=" + method + "]."); - Log.logStackTrace(e); + log.log(Level.WARNING, "Exception [class=" + clazz.getName() + + ", method=" + method + "].", e); return ""; } @@ -435,19 +439,67 @@ public class DispatcherServlet extends VelocityServlet } /** - * We override this to create a context of our own devising. + * Handles all requests (by default). + * + * @param request HttpServletRequest object containing client request. + * @param response HttpServletResponse object for the response. */ - protected Context createContext (HttpServletRequest req, - HttpServletResponse rsp) + protected void doRequest (HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { - return new InvocationContext(req, rsp); + Context context = null; + try { + context = new InvocationContext(request, response); + setContentType(request, response); + + Template template = handleRequest(request, response, context); + if (template != null) { + mergeTemplate(template, context, response); + } + + } catch (Exception e) { + log.log(Level.WARNING, "doRequest failed [uri=" + request.getRequestURI() + "].", e); + response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); + } } /** - * 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. + * Sets the content type of the response, defaulting to {@link #defaultContentType} if not + * overriden. Delegates to {@link #chooseCharacterEncoding(HttpServletRequest)} to select the + * appropriate character encoding. + */ + protected void setContentType (HttpServletRequest request, HttpServletResponse response) + { + String contentType = _defaultContentType; + int index = contentType.lastIndexOf(';') + 1; + if (index <= 0 || (index < contentType.length() && + contentType.indexOf("charset", index) == -1)) { + // append the character encoding which we'd like to use + String encoding = chooseCharacterEncoding(request); + if (!DEFAULT_OUTPUT_ENCODING.equalsIgnoreCase(encoding)) { + contentType += "; charset=" + encoding; + } + } + response.setContentType(contentType); + } + + /** + * Chooses the output character encoding to be used as the value for the "charset=" portion of + * the HTTP Content-Type header (and thus returned by + * response.getCharacterEncoding()). Called by {@link #setContentType} if an + * encoding isn't already specified by Content-Type. By default, chooses the value of + * RuntimeSingleton's output.encoding property. + */ + protected String chooseCharacterEncoding (HttpServletRequest request) + { + return RuntimeSingleton.getString( + RuntimeConstants.OUTPUT_ENCODING, DEFAULT_OUTPUT_ENCODING); + } + + /** + * 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. * @@ -458,20 +510,62 @@ public class DispatcherServlet extends VelocityServlet { String path = ctx.getRequest().getServletPath(); if (_usingSiteLoading) { - // if we're using site resource loading, we need to prefix the path - // with the site identifier + // if we're using site resource loading, we need to prefix the path with the site + // identifier path = siteId + ":" + path; } - // Log.info("Loading template [path=" + path + "]."); + // log.info("Loading template [path=" + path + "]."); return RuntimeSingleton.getTemplate(path); } /** - * This method is called to select the appropriate logic for this - * request URI. + * Merges the template with the context. * - * @return The logic to be used in generating the response or null if - * no logic could be matched. + * @param template template object returned by the {@link #handleRequest} method. + * @param context context created by the {@link #createContext} method. + * @param response servlet reponse (use this to get the output stream or writer). + */ + protected void mergeTemplate (Template template, Context context, HttpServletResponse response) + throws ResourceNotFoundException, ParseErrorException, MethodInvocationException, + IOException, UnsupportedEncodingException, Exception + { + ServletOutputStream output = response.getOutputStream(); + // ASSUMPTION: response.setContentType() has been called. + String encoding = response.getCharacterEncoding(); + + VelocityWriter vw = null; + try { + vw = (VelocityWriter)_writerPool.get(); + if (vw == null) { + vw = new VelocityWriter(new OutputStreamWriter(output, encoding), 4 * 1024, true); + } else { + vw.recycle(new OutputStreamWriter(output, encoding)); + } + template.merge(context, vw); + + } finally { + if (vw != null) { + try { + // flush and put back into the pool don't close to allow us to play nicely with + // others. + vw.flush(); + } catch (IOException e) { + // do nothing + } + + // Clear the VelocityWriter's reference to its internal OutputStreamWriter to allow + // the latter to be GC'd while vw is pooled. + vw.recycle(null); + _writerPool.put(vw); + } + } + } + + /** + * 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) { @@ -488,14 +582,12 @@ public class DispatcherServlet extends VelocityServlet // nothing interesting to report } catch (Throwable t) { - Log.warning("Unable to instantiate logic for application " + - "[path=" + path + ", lclass=" + lclass + "]."); - Log.logStackTrace(t); + log.log(Level.WARNING, "Unable to instantiate logic for application [path=" + path + + ", lclass=" + lclass + "].", 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 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(); } @@ -519,13 +611,20 @@ public class DispatcherServlet extends VelocityServlet /** Set to true if we're using the {@link SiteResourceLoader}. */ protected boolean _usingSiteLoading; + /** Our default content type. */ + protected String _defaultContentType; + + /** A pool of VelocityWriter instances. */ + protected static SimplePool _writerPool = new SimplePool(40); + + /** Describes the location of our properties. */ + protected static final String INIT_PROPS_KEY = "org.apache.velocity.properties"; + /** 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. - */ + /** 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_%"; /** The key used to store the translation tool in the context. */ @@ -551,4 +650,10 @@ public class DispatcherServlet extends VelocityServlet /** The servlet parameter key specifying the default character set. */ protected static final String CHARSET_KEY = "charset"; + + /** The default content type for responses. */ + protected static final String DEFAULT_CONTENT_TYPE = "text/html"; + + /** The default encoding for the output stream. */ + protected static final String DEFAULT_OUTPUT_ENCODING = "ISO-8859-1"; } diff --git a/src/java/com/samskivert/velocity/ImportDirective.java b/src/java/com/samskivert/velocity/ImportDirective.java index 059e8f7d..f6fa6c62 100644 --- a/src/java/com/samskivert/velocity/ImportDirective.java +++ b/src/java/com/samskivert/velocity/ImportDirective.java @@ -40,10 +40,9 @@ import com.samskivert.servlet.SiteIdentifier; import com.samskivert.util.StringUtil; /** - * Pluggable directive that handles the #import() statement in VTL. Import is - * like #parse() except that it creates a compound key - * (siteId:template_path) based on information from the current - * request when fetching the imported template. + * Pluggable directive that handles the #import() statement in VTL. Import is like #parse() except + * that it creates a compound key (siteId:template_path) based on information from the + * current request when fetching the imported template. */ public class ImportDirective extends Directive { @@ -73,14 +72,14 @@ public class ImportDirective extends Directive { // make sure an argument was supplied to the directive if (node.getChild(0) == null) { - rsvc.error("#import() error : null argument"); + rsvc.getLog().error("#import() error : null argument"); return false; } // make sure that argument has a value Object value = node.getChild(0).value(context); if (value == null) { - rsvc.error("#import() error : null argument"); + rsvc.getLog().error("#import() error : null argument"); return false; } @@ -92,9 +91,8 @@ public class ImportDirective extends Directive int maxlen = rsvc.getInt( RuntimeConstants.PARSE_DIRECTIVE_MAXDEPTH, 20); if (templateStack.length >= maxlen) { - rsvc.error("Max recursion depth reached (" + maxlen + "). " + - "File stack: " + - StringUtil.toString(templateStack) + "."); + rsvc.getLog().error("Max recursion depth reached (" + maxlen + "). " + + "File stack: " + StringUtil.toString(templateStack) + "."); return false; } @@ -120,7 +118,7 @@ public class ImportDirective extends Directive try { siteId = ((Integer)siteIdVal).intValue(); } catch (Exception e) { - rsvc.error("#import() error: No siteId information in context."); + rsvc.getLog().error("#import() error: No siteId information in context."); } path = siteId + ":" + path; } @@ -131,22 +129,19 @@ public class ImportDirective extends Directive t = rsvc.getTemplate(path, encoding); } catch (ResourceNotFoundException rnfe) { - rsvc.error("#import(): cannot find template '" + path + - "', called from template " + - context.getCurrentTemplateName() + - " at (" + getLine() + ", " + getColumn() + ")"); + rsvc.getLog().error("#import(): cannot find template '" + path + + "', called from template " + context.getCurrentTemplateName() + + " at (" + getLine() + ", " + getColumn() + ")"); throw rnfe; } catch (ParseErrorException pee) { - rsvc.error("#import(): syntax error in #import()-ed template '" + - path + "', called from template " + - context.getCurrentTemplateName() + - " at (" + getLine() + ", " + getColumn() + ")"); + rsvc.getLog().error("#import(): syntax error in #import()-ed template '" + path + + "', called from template " + context.getCurrentTemplateName() + + " at (" + getLine() + ", " + getColumn() + ")"); throw pee; } catch (Exception e) { - rsvc.error("#import(): Error [path=" + path + - ", error=" + e + "]."); + rsvc.getLog().error("#import(): Error [path=" + path + ", error=" + e + "]."); return false; } @@ -156,7 +151,7 @@ public class ImportDirective extends Directive ((SimpleNode)t.getData()).render(context, writer); } catch (Throwable th) { - rsvc.error("Exception rendering #import(" + path + "): " + th); + rsvc.getLog().error("Exception rendering #import(" + path + "): " + th); // we want to pass method invocation exceptions through if (th instanceof MethodInvocationException) { throw (MethodInvocationException)th; diff --git a/src/java/com/samskivert/velocity/ServletContextLogger.java b/src/java/com/samskivert/velocity/ServletContextLogger.java index 946c7908..5b474ec7 100644 --- a/src/java/com/samskivert/velocity/ServletContextLogger.java +++ b/src/java/com/samskivert/velocity/ServletContextLogger.java @@ -23,12 +23,12 @@ package com.samskivert.velocity; import javax.servlet.ServletContext; import org.apache.velocity.runtime.RuntimeServices; -import org.apache.velocity.runtime.log.LogSystem; +import org.apache.velocity.runtime.log.LogChute; /** * Routes Velocity log messages to the servlet context. */ -public class ServletContextLogger implements LogSystem +public class ServletContextLogger implements LogChute { /** * Constructs a servlet context logger that will obtain its servlet @@ -48,22 +48,21 @@ public class ServletContextLogger implements LogSystem _sctx = sctx; } - // documentation inherited + // from interface LogChute public void init (RuntimeServices rsvc) { // if we weren't constructed with a servlet context, try to obtain // one via the application context if (_sctx == null) { // first look for the servlet context directly - _sctx = (ServletContext) - rsvc.getApplicationAttribute("ServletContext"); + _sctx = (ServletContext)rsvc.getApplicationAttribute("ServletContext"); } // if that didn't work, look for an application if (_sctx == null) { // first look for an application - Application app = (Application)rsvc.getApplicationAttribute( - Application.VELOCITY_ATTR_KEY); + Application app = (Application) + rsvc.getApplicationAttribute(Application.VELOCITY_ATTR_KEY); if (app != null) { _sctx = app.getServletContext(); } @@ -71,22 +70,35 @@ public class ServletContextLogger implements LogSystem // if we still don't have one, complain if (_sctx == null) { - rsvc.warn("ServletContextLogger: servlet context was not " + - "supplied. A user of the servlet context logger must " + - "call Velocity.setApplicationAttribute(" + - "\"ServletContext\", getServletContext())."); + rsvc.getLog().warn("ServletContextLogger: servlet context was not supplied. A user " + + "of the servlet context logger must call " + + "Velocity.setApplicationAttribute(\"ServletContext\", " + + "getServletContext())."); } } - // documentation inherited - public void logVelocityMessage (int level, String message) + // from interface LogChute + public void log (int level, String message) { - // log only warning or above for now - if (level >= LogSystem.WARN_ID) { + if (isLevelEnabled(level)) { _sctx.log(message); } } + // from interface LogChute + public void log (int level, String message, Throwable t) + { + if (isLevelEnabled(level)) { + _sctx.log(message, t); + } + } + + // from interface LogChute + public boolean isLevelEnabled (int level) + { + return (level >= WARN_ID); + } + /** A reference to our servlet context. */ protected ServletContext _sctx; } diff --git a/src/java/com/samskivert/velocity/ServletContextResourceLoader.java b/src/java/com/samskivert/velocity/ServletContextResourceLoader.java index 87466a0d..cbb8bdfc 100644 --- a/src/java/com/samskivert/velocity/ServletContextResourceLoader.java +++ b/src/java/com/samskivert/velocity/ServletContextResourceLoader.java @@ -29,8 +29,7 @@ import org.apache.velocity.runtime.resource.Resource; import org.apache.velocity.runtime.resource.loader.ResourceLoader; /** - * A Velocity resource loader that loads resources from the servlet - * context. + * A Velocity resource loader that loads resources from the servlet context. * * @see ServletContext#getResource * @see ServletContext#getResourceAsStream @@ -38,17 +37,16 @@ import org.apache.velocity.runtime.resource.loader.ResourceLoader; public class ServletContextResourceLoader extends ResourceLoader { /** - * When used with the default Velocity resource manager, we are - * constructed with our zero-argument constructor and later - * initialized via {@link #init}. + * When used with the default Velocity resource manager, we are constructed with our + * zero-argument constructor and later initialized via {@link #init}. */ public ServletContextResourceLoader () { } /** - * When used with the {@link SiteResourceManager} we are constructed - * with our servlet context reference and not later initialized. + * When used with the {@link SiteResourceManager} we are constructed with our servlet context + * reference and not later initialized. */ public ServletContextResourceLoader (ServletContext sctx) { @@ -60,22 +58,14 @@ public class ServletContextResourceLoader extends ResourceLoader */ public void init (ExtendedProperties config) { - // there seems to be a tradition of logging this stuff; surely we - // don't want to buck tradition, do we? - rsvc.info("ServletContextResourceLoader: initialization starting."); - - // the web framework was kind enough to slip this into the runtime - // instance when it started up + // the web framework was kind enough to slip this into the runtime when it started up _sctx = (ServletContext)rsvc.getApplicationAttribute("ServletContext"); if (_sctx == null) { - rsvc.warn("ServletContextResourceLoader: servlet context " + - "was not supplied as application context. A " + - "user of the servlet context resource loader " + - "must call Velocity.setApplicationAttribute(" + - "\"ServletContext\", getServletContext())."); + rsvc.getLog().warn("ServletContextResourceLoader: servlet context was not supplied " + + "as application context. A user of the servlet context resource " + + "loader must call Velocity.setApplicationAttribute(" + + "\"ServletContext\", getServletContext())."); } - - rsvc.info("ServletContextResourceLoader : initialization complete."); } /** diff --git a/src/java/com/samskivert/velocity/ServletContextResourceManager.java b/src/java/com/samskivert/velocity/ServletContextResourceManager.java index e991d030..46924e29 100644 --- a/src/java/com/samskivert/velocity/ServletContextResourceManager.java +++ b/src/java/com/samskivert/velocity/ServletContextResourceManager.java @@ -31,8 +31,7 @@ import org.apache.velocity.runtime.resource.ResourceManagerImpl; import org.apache.velocity.runtime.resource.loader.ResourceLoader; /** - * A resource manager implementation for Velocity that loads resources - * from the servlet context. + * A resource manager implementation for Velocity that loads resources from the servlet context. */ public class ServletContextResourceManager extends ResourceManagerImpl { @@ -40,37 +39,31 @@ public class ServletContextResourceManager extends ResourceManagerImpl throws Exception { super.initialize(rsvc); - rsvc.info("SCRM initializing."); + rsvc.getLog().info("SCRM initializing."); - // the web framework was kind enough to slip this into the runtime - // instance when it started up - Application app = (Application)rsvc.getApplicationAttribute( - Application.VELOCITY_ATTR_KEY); + // the web framework was kind enough to slip this into the runtime when it started up + Application app = (Application)rsvc.getApplicationAttribute(Application.VELOCITY_ATTR_KEY); if (app == null) { - rsvc.warn("SCRM: No application was initialized. A user of the " + - "servlet context resource manager must ensure that " + - "an application is instantiated and initialized."); + rsvc.getLog().warn("SCRM: No application was initialized. A user of the " + + "servlet context resource manager must ensure that " + + "an application is instantiated and initialized."); } // create our resource loader - _contextLoader = new ServletContextResourceLoader( - app.getServletContext()); + _contextLoader = new ServletContextResourceLoader(app.getServletContext()); - // for now, turn caching on with the expectation that new - // resources of any sort will result in the entire web application - // being reloaded and clearing out the cache + // for now, turn caching on with the expectation that new resources of any sort will result + // in the entire web application being reloaded and clearing out the cache _contextLoader.setCachingOn(true); - rsvc.info("SCRM initialization complete."); + rsvc.getLog().info("SCRM initialization complete."); } - protected Resource loadResource( - String resourceName, int resourceType, String encoding) + protected Resource loadResource(String resourceName, int resourceType, String encoding) throws ResourceNotFoundException, ParseErrorException, Exception { // create a blank new resource - Resource resource = - ResourceFactory.getResource(resourceName, resourceType); + Resource resource = ResourceFactory.getResource(resourceName, resourceType); resource.setRuntimeServices(rsvc); resource.setName(resourceName); resource.setEncoding(encoding); @@ -78,8 +71,7 @@ public class ServletContextResourceManager extends ResourceManagerImpl resource.setResourceLoader(_contextLoader); resource.process(); resource.setLastModified(_contextLoader.getLastModified(resource)); - resource.setModificationCheckInterval( - _contextLoader.getModificationCheckInterval()); + resource.setModificationCheckInterval(_contextLoader.getModificationCheckInterval()); resource.touch(); return resource; diff --git a/src/java/com/samskivert/velocity/SiteResourceManager.java b/src/java/com/samskivert/velocity/SiteResourceManager.java index ab090669..03a9cea4 100644 --- a/src/java/com/samskivert/velocity/SiteResourceManager.java +++ b/src/java/com/samskivert/velocity/SiteResourceManager.java @@ -30,13 +30,15 @@ import org.apache.velocity.runtime.resource.ResourceFactory; import org.apache.velocity.runtime.resource.ResourceManagerImpl; import org.apache.velocity.runtime.resource.loader.ResourceLoader; +import com.samskivert.Log; + import com.samskivert.servlet.SiteIdentifier; import com.samskivert.servlet.SiteResourceLoader; /** - * A resource manager implementation for Velocity that first loads site - * specific resources (via the {@link SiteJarResourceLoader}), but falls back - * to default resources if no site-specific resource loader is available. + * A resource manager implementation for Velocity that first loads site specific resources (via the + * {@link SiteJarResourceLoader}), but falls back to default resources if no site-specific resource + * loader is available. */ public class SiteResourceManager extends ResourceManagerImpl { @@ -44,16 +46,15 @@ public class SiteResourceManager extends ResourceManagerImpl throws Exception { super.initialize(rsvc); - rsvc.info("SiteResourceManager initializing."); + Log.log.info("SiteResourceManager initializing."); - // the web framework was kind enough to slip this into the runtime - // instance when it started up + // the web framework was kind enough to slip this into the runtime when it started up Application app = (Application)rsvc.getApplicationAttribute( Application.VELOCITY_ATTR_KEY); if (app == null) { - rsvc.warn("SiteResourceManager: No application was initialized. " + - "A user of the site resource manager must ensure that " + - "an application is instantiated and initialized."); + Log.log.warning("SiteResourceManager: No application was initialized. " + + "A user of the site resource manager must ensure that " + + "an application is instantiated and initialized."); } // get handles on the good stuff @@ -63,33 +64,30 @@ public class SiteResourceManager extends ResourceManagerImpl // make sure the app has a site resource loader SiteResourceLoader loader = app.getSiteResourceLoader(); if (loader == null) { - rsvc.warn("SiteResourceManager: application must be " + - "configured with a site-specific resource loader " + - "that we can use to fetch site-specific resources."); + Log.log.warning("SiteResourceManager: application must be " + + "configured with a site-specific resource loader " + + "that we can use to fetch site-specific resources."); } // create our resource loaders _siteLoader = new SiteJarResourceLoader(loader); _contextLoader = new ServletContextResourceLoader(_sctx); - // for now, turn caching on with the expectation that new - // resources of any sort will result in the entire web application - // being reloaded and clearing out the cache + // for now, turn caching on with the expectation that new resources of any sort will result + // in the entire web application being reloaded and clearing out the cache _siteLoader.setCachingOn(true); _contextLoader.setCachingOn(true); - rsvc.info("SiteResourceManager initialization complete."); + Log.log.info("SiteResourceManager initialization complete."); } - protected Resource loadResource( - String resourceName, int resourceType, String encoding) + protected Resource loadResource(String resourceName, int resourceType, String encoding) throws ResourceNotFoundException, ParseErrorException, Exception { SiteKey skey = new SiteKey(resourceName); // create a blank new resource - Resource resource = - ResourceFactory.getResource(skey.path, resourceType); + Resource resource = ResourceFactory.getResource(skey.path, resourceType); resource.setRuntimeServices(rsvc); resource.setEncoding(encoding); @@ -101,8 +99,7 @@ public class SiteResourceManager extends ResourceManagerImpl // nothing to worry about here } - // then try the servlet context loader if we didn't find a - // site-specific resource + // then try the servlet context loader if we didn't find a site-specific resource if (resource.getData() == null) { resource.setName(skey.path); resolveResource(resource, _contextLoader); @@ -117,8 +114,7 @@ public class SiteResourceManager extends ResourceManagerImpl resource.setResourceLoader(loader); resource.process(); resource.setLastModified(loader.getLastModified(resource)); - resource.setModificationCheckInterval( - loader.getModificationCheckInterval()); + resource.setModificationCheckInterval(loader.getModificationCheckInterval()); resource.touch(); }