You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@roller.apache.org by sn...@apache.org on 2005/06/08 18:06:46 UTC

svn commit: r189602 [19/50] - in /incubator/roller/trunk: ./ contrib/ contrib/lib/ contrib/plugins/ contrib/plugins/src/ contrib/plugins/src/org/ contrib/plugins/src/org/roller/ contrib/plugins/src/org/roller/presentation/ contrib/plugins/src/org/roller/presentation/velocity/ contrib/plugins/src/org/roller/presentation/velocity/plugins/ contrib/plugins/src/org/roller/presentation/velocity/plugins/acronyms/ contrib/plugins/src/org/roller/presentation/velocity/plugins/bookmarks/ contrib/plugins/src/org/roller/presentation/velocity/plugins/email/ contrib/plugins/src/org/roller/presentation/velocity/plugins/jspwiki/ contrib/plugins/src/org/roller/presentation/velocity/plugins/radeox/ contrib/plugins/src/org/roller/presentation/velocity/plugins/readmore/ contrib/plugins/src/org/roller/presentation/velocity/plugins/smileys/ contrib/plugins/src/org/roller/presentation/velocity/plugins/textile/ docs/ docs/images/ docs/installguide/ docs/installguide/old/ docs/userguide/ docs/userguide/images/ docs/userguide/old/ metadata/ metadata/database/ metadata/database/hibernate/ metadata/xdoclet/ personal/ personal/eclipse/ personal/testing/ src/ src/org/ src/org/roller/ src/org/roller/business/ src/org/roller/business/hibernate/ src/org/roller/business/utils/ src/org/roller/model/ src/org/roller/pojos/ src/org/roller/presentation/ src/org/roller/presentation/atom/ src/org/roller/presentation/bookmarks/ src/org/roller/presentation/bookmarks/actions/ src/org/roller/presentation/bookmarks/formbeans/ src/org/roller/presentation/bookmarks/tags/ src/org/roller/presentation/filters/ src/org/roller/presentation/forms/ src/org/roller/presentation/newsfeeds/ src/org/roller/presentation/pagecache/ src/org/roller/presentation/pagecache/rollercache/ src/org/roller/presentation/tags/ src/org/roller/presentation/tags/calendar/ src/org/roller/presentation/tags/menu/ src/org/roller/presentation/velocity/ src/org/roller/presentation/weblog/ src/org/roller/presentation/weblog/actions/ src/org/roller/presentation/weblog/formbeans/ src/org/roller/presentation/weblog/search/ src/org/roller/presentation/weblog/search/operations/ src/org/roller/presentation/weblog/tags/ src/org/roller/presentation/website/ src/org/roller/presentation/website/actions/ src/org/roller/presentation/website/formbeans/ src/org/roller/presentation/website/tags/ src/org/roller/presentation/xmlrpc/ src/org/roller/util/ tests/ tests/org/ tests/org/roller/ tests/org/roller/business/ tests/org/roller/model/ tests/org/roller/persistence/ tests/org/roller/presentation/ tests/org/roller/presentation/atom/ tests/org/roller/presentation/bookmarks/ tests/org/roller/presentation/velocity/ tests/org/roller/presentation/velocity/plugins/ tests/org/roller/presentation/velocity/plugins/smileys/ tests/org/roller/presentation/velocity/plugins/textile/ tests/org/roller/presentation/xmlrpc/ tests/org/roller/util/ tools/ tools/buildtime/ tools/buildtime/mockrunner-0.2.6/ tools/buildtime/mockrunner-0.2.6/lib/ tools/buildtime/tomcat-4.1.24/ tools/buildtime/xdoclet-1.2/ tools/buildtime/xdoclet-1.2/lib/ tools/hibernate-2.1/ tools/hibernate-2.1/lib/ tools/lib/ tools/standard-1.0.3/ tools/standard-1.0.3/lib/ tools/standard-1.0.3/tld/ tools/struts-1.1/ tools/struts-1.1/lib/ web/ web/WEB-INF/ web/WEB-INF/classes/ web/WEB-INF/classes/flavors/ web/WEB-INF/classes/themes/ web/bookmarks/ web/images/ web/images/editor/ web/images/midas/ web/images/preview/ web/images/smileys/ web/tags/ web/templates/ web/theme/ web/theme/images/ web/theme/lavender/ web/theme/scripts/ web/theme/scripts/classes/ web/themes/ web/themes/basic/ web/themes/berkley/ web/themes/berkley/images/ web/themes/cheb/ web/themes/cheb/images/ web/themes/cheb/scripts/ web/themes/clean/ web/themes/currency-i18n/ web/themes/currency-i18n/images/ web/themes/currency/ web/themes/currency/images/ web/themes/grey2/ web/themes/moonshine/ web/themes/pacifica/ web/themes/robot/ web/themes/rolling/ web/themes/rolling/images/ web/themes/sotto/ web/themes/sotto/images/ web/themes/sotto/styles/ web/themes/sunsets/ web/themes/sunsets/images/ web/themes/sunsets/scripts/ web/themes/sunsets/styles/ web/themes/werner/ web/themes/x2/ web/themes/x2/images/ web/themes/x2/scripts/ web/themes/x2/styles/ web/weblog/ web/website/

Added: incubator/roller/trunk/src/org/roller/presentation/filters/IfModifiedFilter.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/filters/IfModifiedFilter.java?rev=189602&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/filters/IfModifiedFilter.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/filters/IfModifiedFilter.java Wed Jun  8 09:06:16 2005
@@ -0,0 +1,192 @@
+/*
+ * Created on Apr 19, 2003
+ */
+package org.roller.presentation.filters;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.roller.RollerException;
+import org.roller.model.Roller;
+import org.roller.model.WeblogManager;
+import org.roller.pojos.UserData;
+import org.roller.presentation.RollerContext;
+import org.roller.presentation.RollerRequest;
+import org.roller.util.LRUCache;
+import org.roller.util.LRUCache2;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Map;
+import java.text.SimpleDateFormat;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Entry point filter for Newsfeed Servlets, this filter 
+ * Handles If-Modified-Since header using per-user and per-category
+ * last weblog pub time. Returns 304 if requested weblog has not been
+ * modified since. Also, sets Last-Modified on outgoing response.
+ *
+ * @web.filter name="IfModifiedFilter"
+ *
+ * @author David M Johnson
+ */
+public class IfModifiedFilter implements Filter
+{
+    private static Log mLogger =
+        LogFactory.getFactory().getInstance(IfModifiedFilter.class);
+
+    private ServletContext mContext;
+    private FilterConfig mConfig;
+
+    // TODO: make cache configurable
+    private static LRUCache2 mDateCache = new LRUCache2(300, 20000);
+    SimpleDateFormat dateFormatter = new SimpleDateFormat("EEE MMM d HH:mm:ss z yyyy");
+
+    public IfModifiedFilter()
+    {
+        super();
+    }
+
+    /**
+     * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
+     */
+    public void init(FilterConfig filterConfig) throws ServletException
+    {
+        // This method is not multithreaded, so we dont need to sync
+        mContext = filterConfig.getServletContext();
+    }
+
+    /**
+     * @see javax.servlet.Filter#doFilter(
+     * javax.servlet.ServletRequest,
+     * javax.servlet.ServletResponse,
+     * javax.servlet.FilterChain)
+     */
+    public void doFilter(
+        ServletRequest req,
+        ServletResponse res,
+        FilterChain chain)
+        throws IOException, ServletException
+    {
+        HttpServletRequest request = (HttpServletRequest) req;
+        HttpServletResponse response = (HttpServletResponse) res;
+        Roller roller = RollerContext.getRoller( request );
+
+        Date updateTime = null;
+        try
+        {
+            updateTime = getLastPublishedDate(request);
+
+            // RSS context loader needs updateTime, so stash it
+            request.setAttribute("updateTime", updateTime);
+
+            // Check the incoming if-modified-since header
+            Date sinceDate =
+                new Date(request.getDateHeader("If-Modified-Since"));
+
+            if (updateTime != null)
+            {
+                 // convert date (JDK 1.5 workaround)
+                 String date = dateFormatter.format(updateTime);
+                 updateTime = new Date(date);
+                 if (updateTime.compareTo(sinceDate) <= 0)
+                 {
+                     response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                     return;
+                 }
+            }
+            mLogger.debug("Not returning 304 for: "+request.getRequestURI());
+        }
+        catch (RollerException e)
+        {
+            // Thrown by getLastPublishedDate if there is a db-type error
+            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+            return;
+        }
+        catch (IllegalArgumentException e)
+        {
+            // Thrown by getDateHeader if not in valid format. This can be
+            // safely ignored, the only consequence is that the NOT MODIFIED
+            // response is not set.
+        }
+
+        // Set outgoing last modified header
+        if (updateTime != null)
+        {
+            response.setDateHeader("Last-Modified", updateTime.getTime());
+        }
+
+        chain.doFilter(request, response);
+    }
+
+    public static Date getLastPublishedDate(HttpServletRequest request)
+	    throws RollerException
+	{
+	    // Get user name without using heavy RollerRequest URL parser
+	    String userName = null;
+	    String pathInfo = request.getPathInfo();
+	    pathInfo = pathInfo != null ? pathInfo : "";
+	    String[] pathInfoArray = StringUtils.split(pathInfo, "/");
+	    if (pathInfoArray.length == 1)
+	    {
+	        userName = pathInfoArray[0];
+	    }
+	    else if (pathInfoArray.length > 1) 
+	    {
+	        // request is for a specific date or anchor, can't return 304
+	        return null;
+	    }
+	
+	    // Get last pub time for specific weblog category requested
+	    String catname =
+	        request.getParameter(RollerRequest.WEBLOGCATEGORYNAME_KEY);
+	
+	    // update times are cached to reduce database queries per request
+	    StringBuffer sb = new StringBuffer();
+        sb.append("zzz_");
+	    sb.append(userName);
+        sb.append("_zzz_");
+	    sb.append(catname);
+	    String key = sb.toString();
+	
+	    Date updateTime = (Date)mDateCache.get(key);
+	    if (updateTime == null)
+	    {
+	        mLogger.debug("Hitting database for update time: "+key);
+	        Roller roller = RollerContext.getRoller(request);
+	        roller.begin();
+	        WeblogManager wmgr = roller.getWeblogManager();
+	        updateTime = wmgr.getWeblogLastPublishTime(userName, catname);	
+	        mDateCache.put(key, updateTime);
+	    }
+	    return updateTime;
+	}
+    
+    public static void purgeDateCache(UserData user) 
+    {
+        String userName = (user != null) ? user.getUserName() : null;
+        StringBuffer sb = new StringBuffer();
+        sb.append("zzz_");
+        sb.append(userName);
+        sb.append("_zzz_");
+        mDateCache.purge(new String[] {sb.toString()});
+    }
+
+    /**
+     * @see javax.servlet.Filter#destroy()
+     */
+    public void destroy()
+    {
+    }
+}

Added: incubator/roller/trunk/src/org/roller/presentation/filters/LoginFilter.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/filters/LoginFilter.java?rev=189602&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/filters/LoginFilter.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/filters/LoginFilter.java Wed Jun  8 09:06:16 2005
@@ -0,0 +1,146 @@
+package org.roller.presentation.filters;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.roller.model.UserManager;
+import org.roller.pojos.UserData;
+import org.roller.presentation.RequestUtil;
+import org.roller.presentation.RollerRequest;
+import org.roller.util.Utilities;
+
+
+/**
+ * <p>Intercepts Login requests for "Remember Me" functionality.</p>
+ *
+ * @author Matt Raible
+ * @version $Revision: 1.1 $ $Date: 2004/03/21 04:33:55 $
+ *
+ * @web.filter display-name="Login Filter" name="loginFilter"
+ * @web.filter-init-param name="enabled" value="true"
+ */
+public final class LoginFilter implements Filter 
+{
+    //~ Instance fields ========================================================
+
+    private Log mLogger = LogFactory.getLog(LoginFilter.class);
+    private FilterConfig mFilterConfig = null;
+    private boolean enabled = true;
+
+    //~ Methods ================================================================
+
+    public void doFilter(ServletRequest req, ServletResponse resp,
+                         FilterChain chain)
+                  throws IOException, ServletException 
+    {
+
+        HttpServletRequest request = (HttpServletRequest) req;
+        HttpServletResponse response = (HttpServletResponse) resp;
+
+        // See if the user has a remember me cookie
+        Cookie c = RequestUtil.getCookie(request, RollerRequest.LOGIN_COOKIE);
+
+        try 
+        {
+            RollerRequest rreq = RollerRequest.getRollerRequest(request);
+            UserManager mgr = rreq.getRoller().getUserManager();
+                
+            // Check to see if the user is logging out, if so, remove all
+            // login cookies
+            if (request.getRequestURL().indexOf("logout") != -1) 
+            {
+                if (mLogger.isDebugEnabled()) 
+                {
+                    mLogger.debug("logging out '" + request.getRemoteUser() + "'");
+                }
+    
+                mgr.removeLoginCookies(request.getRemoteUser());
+                rreq.getRoller().commit();
+                RequestUtil.deleteCookie(response, c, request.getContextPath());
+            } 
+            else if (c != null && enabled) 
+            {
+                String loginCookie = mgr.checkLoginCookie(c.getValue());
+                rreq.getRoller().commit();
+
+                if (loginCookie != null) 
+                {
+                    RequestUtil.setCookie(response, RollerRequest.LOGIN_COOKIE,
+                                          loginCookie,
+                                          request.getContextPath());
+                    loginCookie = Utilities.decodeString(loginCookie);
+
+                    String[] value = StringUtils.split(loginCookie, '|');
+
+                    UserData user = mgr.getUser( value[0] );
+
+                    // authenticate user without displaying login page
+                    String route = "/auth?j_username=" +
+                                   user.getUserName() + "&j_password=" +
+                                   user.getPassword();
+
+                    request.setAttribute("encrypt", "false");
+                    request.getSession().setAttribute("cookieLogin", "true");
+
+                    if (mLogger.isDebugEnabled()) 
+                    {
+                        mLogger.debug("I remember you '" + user.getUserName() +
+                                  "', attempting to authenticate...");
+                    }
+
+                    RequestDispatcher dispatcher =
+                        request.getRequestDispatcher(route);
+                    dispatcher.forward(request, response);
+
+                    return;
+                }
+            }
+                
+        } catch (Exception e) 
+        {
+            // no big deal if cookie-based authentication fails
+            mLogger.warn(e.getMessage());
+        }
+
+        chain.doFilter(req, resp);
+    }
+
+    /**
+     * Initialize controller values of filter.
+     */
+    public void init(FilterConfig config) 
+    {
+        this.mFilterConfig = config;
+
+        String param = config.getInitParameter("enabled");
+        enabled = Boolean.valueOf(param).booleanValue();
+
+        if (mLogger.isDebugEnabled()) 
+        {
+            mLogger.debug("Remember Me enabled: " + enabled);
+        }
+
+        config.getServletContext()
+              .setAttribute("rememberMeEnabled",
+                            config.getInitParameter("enabled"));
+    }
+
+    /**
+     * destroy any instance values other than config *
+     */
+    public void destroy() {
+    }
+}

Added: incubator/roller/trunk/src/org/roller/presentation/filters/RefererFilter.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/filters/RefererFilter.java?rev=189602&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/filters/RefererFilter.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/filters/RefererFilter.java Wed Jun  8 09:06:16 2005
@@ -0,0 +1,121 @@
+package org.roller.presentation.filters;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.roller.model.RefererManager;
+import org.roller.presentation.RollerContext;
+import org.roller.presentation.RollerRequest;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+
+
+
+/**
+ * Keep track of referers.
+ * 
+ * @web.filter name="RefererFilter"
+ * *web.filter-mapping url-pattern="/page/*"
+ * 
+ * @author David M. Johnson
+ */
+public class RefererFilter implements Filter
+{
+    private FilterConfig   mFilterConfig = null;
+    private static Log mLogger = 
+        LogFactory.getFactory().getInstance(RefererFilter.class);
+   
+    /**
+     * destroy
+     */
+    public void destroy()
+    {
+    }
+
+    /**
+     * doFilter
+     */
+    public void doFilter(
+        ServletRequest req, ServletResponse res, FilterChain chain)
+        throws IOException, ServletException
+    {
+        HttpServletRequest request = (HttpServletRequest)req;       
+        try
+        {
+            RollerRequest rreq = RollerRequest.getRollerRequest(request);
+            RollerContext rctx = RollerContext.getRollerContext(
+                mFilterConfig.getServletContext());
+
+            if ( rreq.getUser() != null )
+            {
+                String userName = rreq.getUser().getUserName();
+                
+                String referer = request.getHeader("Referer");
+                
+                // Base page URLs, with and without www.
+                String basePageUrlWWW = 
+                    rctx.getAbsoluteContextUrl(request)+"/page/"+userName;                        
+                String basePageUrl = basePageUrlWWW;          
+                if ( basePageUrlWWW.startsWith("http://www.") )
+                {
+                    // chop off the http://www.
+                    basePageUrl = "http://"+basePageUrlWWW.substring(11);
+                }
+                                 
+                // Base comment URLs, with and without www.  
+                String baseCommentsUrlWWW = 
+                    rctx.getAbsoluteContextUrl(request)+"/page/"+userName;   
+                String baseCommentsUrl = baseCommentsUrlWWW;          
+                if ( baseCommentsUrlWWW.startsWith("http://www.") )
+                {
+                    // chop off the http://www.
+                    baseCommentsUrl= "http://"+baseCommentsUrlWWW.substring(11);
+                }
+                
+                // Don't process hits from same user's blogs as referers by
+                // ignoring Don't process referer from pages that start with base URLs.                
+                if (  referer==null ||
+                      ( 
+                         !referer.startsWith( basePageUrl ) 
+                      && !referer.startsWith( basePageUrlWWW )
+                      && !referer.startsWith( baseCommentsUrl )
+                      && !referer.startsWith( baseCommentsUrlWWW )
+                      )
+                   )
+                {
+                    RefererManager refMgr = 
+                        rreq.getRoller().getRefererManager();
+                    refMgr.processRequest(rreq);
+                }
+                else
+                {
+                    if (mLogger.isDebugEnabled())
+                    {
+                        mLogger.debug("Ignoring referer="+referer);
+                    }
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            mLogger.error("Processing referer",e);
+        }
+                      
+        chain.doFilter(req, res);
+    }
+
+    /**
+     * init
+     */
+    public void init(FilterConfig filterConfig) throws ServletException
+    {
+        mFilterConfig = filterConfig;
+    }
+}

Added: incubator/roller/trunk/src/org/roller/presentation/filters/RequestFilter.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/filters/RequestFilter.java?rev=189602&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/filters/RequestFilter.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/filters/RequestFilter.java Wed Jun  8 09:06:16 2005
@@ -0,0 +1,184 @@
+package org.roller.presentation.filters;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.struts.Globals;
+import org.roller.RollerException;
+import org.roller.model.Roller;
+import org.roller.model.UserManager;
+import org.roller.presentation.RequestUtil;
+import org.roller.presentation.RollerContext;
+import org.roller.presentation.RollerRequest;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.Date;
+import java.util.Locale;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.jsp.jstl.core.Config;
+
+
+/**
+ * Entry point filter for Weblog page and Editor UI, this filter 
+ * creates a RollerRequest object to parse pathinfo and request parameters.
+ * 
+ * @web.filter name="RequestFilter"
+ * 
+ * @author David M. Johnson, Matt Raible
+ */
+public class RequestFilter implements Filter
+{
+    private FilterConfig mFilterConfig = null;
+    private static Log mLogger =
+        LogFactory.getFactory().getInstance(RequestFilter.class);
+
+    /**
+     * destroy
+     */
+    public void destroy()
+    {
+    }
+
+    /**
+     * As the first and last filter in the chain, it is necessary that
+     * RequestFilter releases its Roller resources before it returns.
+     */
+    public void doFilter(
+        ServletRequest req, ServletResponse res, FilterChain chain)
+        throws IOException, ServletException
+    {
+        try
+        {
+            // insure that incoming data is parsed as UTF-8
+            req.setCharacterEncoding("UTF-8");
+        }
+        catch (UnsupportedEncodingException e)
+        {
+            throw new ServletException("Can't set incoming encoding to UTF-8");
+        }
+
+        // keep JSTL and Struts Locale's in sync
+        HttpSession session = ((HttpServletRequest)req).getSession();
+        if (null != session)
+        {
+            Locale locale = (Locale)session.getAttribute(Globals.LOCALE_KEY);
+            if (locale == null)
+            {
+                locale = req.getLocale();
+            }
+            if (req.getParameter("locale") != null)
+            {
+                locale = new Locale(req.getParameter("locale"));
+            }
+            session.setAttribute(Globals.LOCALE_KEY, locale);
+            Config.set(session, Config.FMT_LOCALE, locale);
+        }
+
+        HttpServletRequest request = (HttpServletRequest)req;
+        HttpServletResponse response = (HttpServletResponse)res;
+        Roller roller = RollerContext.getRoller( request );
+        RollerRequest rreq = null;
+        try
+        {
+            if (request.getAttribute("roller.begun") == null)
+            {
+                roller.begin();
+            }
+            else 
+            {
+                mLogger.debug("Came through filter again on URL="+request.getRequestURL());    
+            }
+        }
+        catch (Throwable t)
+        {
+            mLogger.error("ERROR creating persistence session", t);
+            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+        }
+
+        try 
+        {
+            rreq = RollerRequest.getRollerRequest(
+                request, mFilterConfig.getServletContext());
+
+            // if user wants to be remembered, create a remember me cookie
+            // TODO: Figure out a better place to put this - so it will
+            // only be called when the user initially logs in
+            String username = request.getRemoteUser();
+
+            if (username != null)
+            {
+                if (session.getAttribute(RollerRequest.LOGIN_COOKIE) != null)
+                {
+                    session.removeAttribute(RollerRequest.LOGIN_COOKIE);
+
+                    UserManager mgr = rreq.getRoller().getUserManager();
+
+                    String loginCookie = mgr.createLoginCookie(username);
+                    rreq.getRoller().commit();
+                    RequestUtil.setCookie(response, RollerRequest.LOGIN_COOKIE,
+                                          loginCookie, request.getContextPath());
+                }
+            }
+
+        }
+        catch (RollerException e)
+        {
+            // An error initializing the request is considered to be a 404
+            response.sendError(HttpServletResponse.SC_NOT_FOUND);
+            return;
+        }
+
+        if (session != null)
+        {
+            // look for messages and errors in the request, and if they
+            // exist, stuff them in the request - in Struts 1.2, you don't
+            // need to do this
+            if (session.getAttribute(Globals.MESSAGE_KEY) != null)
+            {
+                request.setAttribute(Globals.MESSAGE_KEY,
+                        session.getAttribute(Globals.MESSAGE_KEY));
+                session.removeAttribute(Globals.MESSAGE_KEY);
+            }
+            if (session.getAttribute(Globals.ERROR_KEY) != null)
+            {
+                request.setAttribute(Globals.ERROR_KEY,
+                        session.getAttribute(Globals.ERROR_KEY));
+                session.removeAttribute(Globals.ERROR_KEY);
+            }
+        }
+
+        Date updateTime = null;
+        try
+        {
+            updateTime = IfModifiedFilter.getLastPublishedDate(request);
+        }
+        catch (RollerException e1)
+        {
+            mLogger.debug("Getting lastUpdateTime", e1);
+        }
+        if (updateTime != null)
+        {
+            request.setAttribute("updateTime", updateTime);
+        }
+
+        chain.doFilter(req, res);
+    }
+
+    /**
+     * init
+     */
+    public void init(FilterConfig filterConfig) throws ServletException
+    {
+        mFilterConfig = filterConfig;
+    }
+}
+

Added: incubator/roller/trunk/src/org/roller/presentation/filters/package.html
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/filters/package.html?rev=189602&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/filters/package.html (added)
+++ incubator/roller/trunk/src/org/roller/presentation/filters/package.html Wed Jun  8 09:06:16 2005
@@ -0,0 +1,9 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+  <title></title>
+</head>
+<body>
+Servlet filters<br>
+</body>
+</html>

Added: incubator/roller/trunk/src/org/roller/presentation/forms/package.html
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/forms/package.html?rev=189602&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/forms/package.html (added)
+++ incubator/roller/trunk/src/org/roller/presentation/forms/package.html Wed Jun  8 09:06:16 2005
@@ -0,0 +1,9 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+  <title></title>
+</head>
+<body>
+XDoclet generated form classes, one for each Roller bean.<br>
+</body>
+</html>

Added: incubator/roller/trunk/src/org/roller/presentation/newsfeeds/NewsfeedCache.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/newsfeeds/NewsfeedCache.java?rev=189602&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/newsfeeds/NewsfeedCache.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/newsfeeds/NewsfeedCache.java Wed Jun  8 09:06:16 2005
@@ -0,0 +1,153 @@
+package org.roller.presentation.newsfeeds;
+
+import java.io.IOException;
+import java.net.URL;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.roller.pojos.RollerConfig;
+import org.roller.util.LRUCache2;
+
+import de.nava.informa.core.ChannelIF;
+import de.nava.informa.core.ParseException;
+import de.nava.informa.impl.basic.ChannelBuilder;
+import de.nava.informa.parsers.RSSParser;
+
+/**
+ * Returns parsed RSS feed by pulling one from a cache or by retrieving and
+ * parging the specified feed using the Flock RSS parser.
+ * 
+ * @author Lance Lavandowska
+ * @author Dave Johnson
+ */
+public class NewsfeedCache
+{
+    private static Log mLogger = LogFactory.getFactory().getInstance(
+            NewsfeedCache.class);
+
+    /** Static singleton * */
+    public static NewsfeedCache mInstance = null;
+
+    /** Instance vars * */
+    private RollerConfig mConfig = null;
+
+    /** LRU cache */
+    LRUCache2 mCache = null;
+
+    /** Constructor * */
+    public NewsfeedCache(RollerConfig config)
+    {
+        mConfig = config;
+        // TODO: make cache size configurable
+        mCache = new LRUCache2(100, mConfig.getRssCacheTime().intValue());
+    }
+
+    /** static singleton retriever * */
+    public static NewsfeedCache getInstance(RollerConfig config)
+    {
+        if (mInstance == null)
+        {
+            if (mLogger.isDebugEnabled())
+            {
+                mLogger.debug("Instantiating new NewsfeedCache");
+            }
+
+            synchronized (NewsfeedCache.class)
+            {
+                mInstance = new NewsfeedCache(config);
+            }
+        }
+        return mInstance;
+    }
+
+    /**
+     * Returns a Channel object for the supplied RSS newsfeed URL.
+     * 
+     * @param feedUrl
+     *            RSS newsfeed URL.
+     * @return FlockFeedI for specified RSS newsfeed URL.
+     */
+    public ChannelIF getChannel(String feedUrl)
+    {
+        ChannelIF channel = null;
+        //FlockFeed feed = null;
+        try
+        {
+            //FlockFeedFactory factory = new FlockFeedFactory();
+
+            // If aggregator has been disable return null
+            boolean enabled = mConfig.getEnableAggregator().booleanValue();
+            if (!enabled)
+            {
+                return null;
+            }
+
+            if (mConfig.getRssUseCache().booleanValue())
+            {
+                if (mLogger.isDebugEnabled())
+                {
+                    mLogger.debug("Newsfeed: use Cache for " + feedUrl);
+                }
+
+                // Get pre-parsed feed from the cache
+                channel = (ChannelIF) mCache.get(feedUrl);
+                if (mLogger.isDebugEnabled())
+                {
+                    mLogger.debug("Newsfeed: got from Cache");
+                }
+
+                if (channel == null)
+                {
+                    try
+                    {
+                        // Parse the feed
+                        channel = RSSParser.parse(
+                                new ChannelBuilder(), new URL(feedUrl));
+                    }
+                    catch (ParseException e1)
+                    {
+                        mLogger.info("Error parsing RSS: " + feedUrl);
+                    }
+                }
+
+                //channel = factory.createFeed(new URL(feedUrl));
+
+                // Store parsed feed in the cache
+                mCache.put(feedUrl, channel);
+                mLogger.debug("Newsfeed: not in Cache");
+            }
+            else
+            {
+                if (mLogger.isDebugEnabled())
+                {
+                    mLogger.debug("Newsfeed: not using Cache for " + feedUrl);
+                }
+                try
+                {
+                    // Parse the feed
+                    channel = RSSParser.parse(new ChannelBuilder(), new URL(
+                            feedUrl));
+                }
+                catch (ParseException e1)
+                {
+                    mLogger.info("Error parsing RSS: " + feedUrl);
+                }
+            }
+        }
+        catch (IOException ioe)
+        {
+            if (mLogger.isDebugEnabled())
+            {
+                mLogger.debug("Newsfeed: Unexpected exception", ioe);
+            }
+        }
+        //        catch (FlockResourceException e)
+        //        {
+        //            if (mLogger.isDebugEnabled())
+        //            {
+        //                mLogger.debug("Newsfeed: Flock parsing exception",e);
+        //            }
+        //        }
+        return channel;
+    }
+}
\ No newline at end of file

Added: incubator/roller/trunk/src/org/roller/presentation/newsfeeds/package.html
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/newsfeeds/package.html?rev=189602&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/newsfeeds/package.html (added)
+++ incubator/roller/trunk/src/org/roller/presentation/newsfeeds/package.html Wed Jun  8 09:06:16 2005
@@ -0,0 +1,9 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+  <title></title>
+</head>
+<body>
+Newsfeed parser and cache.<br>
+</body>
+</html>

Added: incubator/roller/trunk/src/org/roller/presentation/package.html
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/package.html?rev=189602&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/package.html (added)
+++ incubator/roller/trunk/src/org/roller/presentation/package.html Wed Jun  8 09:06:16 2005
@@ -0,0 +1,29 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+  <title></title>
+</head>
+<body>
+
+<p>
+The org.roller.presentation package and the packages under it comprise the 
+<b>Presentation Layer</b> - the web-based Roller user interface.
+ 
+The code in the packages depend on Web APIs including the Servlet API, JSP API, 
+Struts, Velocity and the like. 
+</p>
+
+<p>
+These packages are also the home of Roller's Web Services interfaces -
+both the XML-RPC based Blogger/MetaWeblog APIs and the REST based Atom API
+Web Service interfaces are suppported.
+
+Generally speaking, the code in these packages implements presentation logic
+and presentation logic only.
+
+Wherever possible, application logic that can be made independent of Web APIs
+is implemented inside the Business Layer
+</p>
+
+</body>
+</html>

Added: incubator/roller/trunk/src/org/roller/presentation/pagecache/FilterHandler.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/pagecache/FilterHandler.java?rev=189602&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/pagecache/FilterHandler.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/pagecache/FilterHandler.java Wed Jun  8 09:06:16 2005
@@ -0,0 +1,37 @@
+package org.roller.presentation.pagecache;
+
+import org.roller.pojos.UserData;
+
+import java.io.IOException;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+
+public interface FilterHandler
+{
+    /**
+     * Clean up anything necessary before destruction.
+     */
+    public void destroy();
+
+    /**
+     * Exactly as Filter.doFilter().
+     */
+    public void doFilter(ServletRequest request,
+        ServletResponse response, FilterChain chain)
+        throws ServletException, IOException;
+
+    /**
+     * Clear the entire cache.
+     */
+    public void flushCache(HttpServletRequest req);
+
+    /**
+     * Remove the entries for this User
+     * from the cache.
+     */
+    public void removeFromCache(HttpServletRequest req,UserData user);
+}

Added: incubator/roller/trunk/src/org/roller/presentation/pagecache/PageCache.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/pagecache/PageCache.java?rev=189602&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/pagecache/PageCache.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/pagecache/PageCache.java Wed Jun  8 09:06:16 2005
@@ -0,0 +1,117 @@
+
+package org.roller.presentation.pagecache;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.roller.pojos.UserData;
+import org.roller.presentation.filters.IfModifiedFilter;
+import org.roller.presentation.pagecache.rollercache.LRUCacheHandler2;
+
+
+/////////////////////////////////////////////////////////////////////////////
+/**
+ * Roller's in memory page cache. See Javadoc for LRUCacheHandler for more 
+ * information on configuring this cache. 
+ * 
+ * @web.filter name="PageCacheFilter"
+ * 
+ * @web.filter-init-param name="size" value="100"
+ *     description="Number of pages to keep in cache"
+ * 
+ * @web.filter-init-param name="timeoutInterval" value="1800"
+ *     description="Page Cache timeout interval in seconds (-1 to disable timeout, default is 30 minutes)"
+ * 
+ * @web.filter-init-param name="timeoutRatio" value="1.0"
+ *     description="Ratio of pages to be timed out on interval (1.0 means 100%, default is 1.0)"
+ * 
+ * @author Lance Lavandowska
+ * @author David M Johnson
+ */
+public class PageCache implements Filter
+{
+    private static Log mLogger = 
+        LogFactory.getFactory().getInstance(PageCache.class);
+        
+    private FilterHandler mHandler = null;
+    
+    private static ArrayList mHandlers = new ArrayList();
+
+    //-----------------------------------------------------------------------
+    /**
+     * Initialize the filter.
+     * @param filerConfig The filter configuration
+     */
+    public void init(FilterConfig filterConfig)
+    {
+        String handlerClass = filterConfig.getInitParameter("handler");
+        if (mLogger.isDebugEnabled())
+        {
+            mLogger.debug(
+                "Initializing as filterName: "+filterConfig.getFilterName());
+        }        
+        mHandler = new LRUCacheHandler2(filterConfig);
+        mHandlers.add(mHandler);
+    }
+
+    //-----------------------------------------------------------------------
+    /**
+     * Filter clean-up
+     */
+    public void destroy()    
+    {
+        mHandler.destroy();
+    }
+
+    //-----------------------------------------------------------------------
+    /**
+     * Process the doFilter
+     * @param request The servlet request
+     * @param response The servlet response
+     * @param chain The filet chain
+     * @throws ServletException IOException
+     */
+    public void doFilter(ServletRequest request,
+        ServletResponse response, FilterChain chain)
+        throws ServletException, IOException
+    {
+        mHandler.doFilter(request, response, chain);
+    }
+
+    //-----------------------------------------------------------------------
+    /** Flush cache for all handlers of this class */
+    public static void flushCache(HttpServletRequest req)
+    {
+        Iterator iter = mHandlers.iterator();
+        while (iter.hasNext())
+        {
+            FilterHandler handler = (FilterHandler)iter.next();
+            handler.flushCache(req);            
+        }
+        IfModifiedFilter.purgeDateCache(null);
+    }
+
+    //-----------------------------------------------------------------------
+    /** Remove from cache for all handlers of this class */
+    public static void removeFromCache(HttpServletRequest req, UserData user)
+    {
+        Iterator iter = mHandlers.iterator();
+        while (iter.hasNext())
+        {
+            FilterHandler handler = (FilterHandler)iter.next();
+            handler.removeFromCache(req, user);
+        }       
+        IfModifiedFilter.purgeDateCache(user);
+    }
+}

Added: incubator/roller/trunk/src/org/roller/presentation/pagecache/package.html
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/pagecache/package.html?rev=189602&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/pagecache/package.html (added)
+++ incubator/roller/trunk/src/org/roller/presentation/pagecache/package.html Wed Jun  8 09:06:16 2005
@@ -0,0 +1,9 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+  <title></title>
+</head>
+<body>
+Roller's custom OSCache Servlet filter.<br>
+</body>
+</html>

Added: incubator/roller/trunk/src/org/roller/presentation/pagecache/rollercache/CacheHttpServletResponseWrapper.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/pagecache/rollercache/CacheHttpServletResponseWrapper.java?rev=189602&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/pagecache/rollercache/CacheHttpServletResponseWrapper.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/pagecache/rollercache/CacheHttpServletResponseWrapper.java Wed Jun  8 09:06:16 2005
@@ -0,0 +1,212 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package org.roller.presentation.pagecache.rollercache;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import java.util.Locale;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+/**
+ * CacheServletResponse is a serialized representation of a response
+ *
+ * @author  <a href="mailto:sergek@lokitech.com">Serge Knystautas</a>
+ * @version $Revision: 1.1 $
+ */
+public class CacheHttpServletResponseWrapper extends HttpServletResponseWrapper {
+    private final Log log = LogFactory.getLog(this.getClass());
+
+    /**
+     * We cache the printWriter so we can maintain a single instance
+     * of it no matter how many times it is requested.
+     */
+    private PrintWriter cachedWriter;
+    private ResponseContent result = null;
+    private SplitServletOutputStream cacheOut = null;
+    private int status = SC_OK;
+
+    /**
+     * Constructor
+     *
+     * @param response The servlet response
+     */
+    public CacheHttpServletResponseWrapper(HttpServletResponse response) {
+        super(response);
+        result = new ResponseContent();
+    }
+
+    /**
+     * Get a response content
+     *
+     * @return The content
+     */
+    public ResponseContent getContent() {
+        //Create the byte array
+        result.commit();
+
+        //Return the result from this response
+        return result;
+    }
+
+    /**
+     * Set the content type
+     *
+     * @param value The content type
+     */
+    public void setContentType(String value) {
+        super.setContentType(value);
+        result.setContentType(value);
+    }
+
+    /**
+     * Set the date of a header
+     *
+     * @param name The header name
+     * @param value The date
+     */
+    public void setDateHeader(String name, long value) {
+        if (log.isDebugEnabled()) {
+            log.debug("dateheader: " + name + ": " + value);
+        }
+
+        super.setDateHeader(name, value);
+    }
+
+    /**
+     * Set a header field
+     *
+     * @param name The header name
+     * @param value The header value
+     */
+    public void setHeader(String name, String value) {
+        if (log.isDebugEnabled()) {
+            log.debug("header: " + name + ": " + value);
+        }
+
+        super.setHeader(name, value);
+    }
+
+    /**
+     * Set the int value of the header
+     *
+     * @param name The header name
+     * @param value The int value
+     */
+    public void setIntHeader(String name, int value) {
+        if (log.isDebugEnabled()) {
+            log.debug("intheader: " + name + ": " + value);
+        }
+
+        super.setIntHeader(name, value);
+    }
+
+    /**
+     * We override this so we can catch the response status. Only
+     * responses with a status of 200 (<code>SC_OK</code>) will
+     * be cached.
+     */
+    public void setStatus(int status) {
+        super.setStatus(status);
+        this.status = status;
+    }
+
+    /**
+     * We override this so we can catch the response status. Only
+     * responses with a status of 200 (<code>SC_OK</code>) will
+     * be cached.
+     */
+    public void sendError(int status, String string) throws IOException {
+        super.sendError(status, string);
+        this.status = status;
+    }
+
+    /**
+     * We override this so we can catch the response status. Only
+     * responses with a status of 200 (<code>SC_OK</code>) will
+     * be cached.
+     */
+    public void sendError(int status) throws IOException {
+        super.sendError(status);
+        this.status = status;
+    }
+
+    /**
+     * We override this so we can catch the response status. Only
+     * responses with a status of 200 (<code>SC_OK</code>) will
+     * be cached.
+     */
+    public void setStatus(int status, String string) {
+        super.setStatus(status, string);
+        this.status = status;
+    }
+
+    public void sendRedirect(String location) throws IOException {
+        this.status = SC_MOVED_TEMPORARILY;
+        super.sendRedirect(location);
+    }
+
+    /**
+     * Retrieves the captured HttpResponse status.
+     */
+    public int getStatus() {
+        return status;
+    }
+
+    /**
+     * Set the locale
+     *
+     * @param value The locale
+     */
+    public void setLocale(Locale value) {
+        super.setLocale(value);
+        result.setLocale(value);
+    }
+
+    /**
+     * Get an output stream
+     *
+     * @throws IOException
+     */
+    public ServletOutputStream getOutputStream() throws IOException {
+        // Pass this faked servlet output stream that captures what is sent
+        if (cacheOut == null) {
+            cacheOut = new SplitServletOutputStream(result.getOutputStream(), super.getOutputStream());
+        }
+
+        return cacheOut;
+    }
+
+    /**
+     * Get a print writer
+     *
+     * @throws IOException
+     */
+    public PrintWriter getWriter() throws IOException {
+        if (cachedWriter == null) {
+            cachedWriter = new PrintWriter(getOutputStream());
+        }
+
+        return cachedWriter;
+    }
+
+    public void flushBuffer() throws IOException {
+        super.flushBuffer();
+
+        if (cacheOut != null) {
+            cacheOut.flush();
+        }
+
+        if (cachedWriter != null) {
+            cachedWriter.flush();
+        }
+    }
+}

Added: incubator/roller/trunk/src/org/roller/presentation/pagecache/rollercache/LRUCacheHandler.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/pagecache/rollercache/LRUCacheHandler.java?rev=189602&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/pagecache/rollercache/LRUCacheHandler.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/pagecache/rollercache/LRUCacheHandler.java Wed Jun  8 09:06:16 2005
@@ -0,0 +1,466 @@
+/*
+ * Created on Jun 15, 2004
+ */
+package org.roller.presentation.pagecache.rollercache;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.roller.pojos.UserData;
+import org.roller.presentation.LanguageUtil;
+import org.roller.presentation.pagecache.FilterHandler;
+import org.roller.util.LRUCache;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.TreeMap;
+
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Page cache implementation that uses a simple LRUCache. Can be configured 
+ * using filter configuration parameters:
+ * <ul>
+ * <li><b>size</b>: number of pages to keep in cache. Once cache reaches
+ * this size, each new cache entry will push out the LRU cache entry.</li>
+ * 
+ * <li><b>timeoutInterval</b>: interval to timeout pages in seconds
+ * (default is 1800 seconds). Sites with a large number of users, and thus a 
+ * lot of cache churn which makes this check unnecessary, may wish to  set this
+ *  to -1 to disable timeout checking.</li>
+ * 
+ * <li><b>timeoutRatio</b>: portion of old pages to expire on timeout 
+ * interval where 1.0 is 100% (default is 1.0). This only applies if the 
+ * timeoutInterval is set to something other than -1.</li>
+ * </ul> 
+ * @author David M Johnson
+ */
+public class LRUCacheHandler implements FilterHandler
+{
+    private static Log mLogger = 
+        LogFactory.getFactory().getInstance(LRUCacheHandler.class);
+
+    private Map mPageCache = null;
+    
+    private String mName = null;
+    
+    /** Timeout interval: how often to run timeout task (default 30 mintes) */
+    private long mTimeoutInterval = 30 * 60 * 1000; // milliseconds
+    
+    /** Timeout ratio: % of cache to expire on each timeout (default 1.0) */
+    private float mTimeoutRatio = 1.0F;
+    
+    // Statistics
+    private int misses = 0;
+    private int hits = 0;
+    
+    private final static String FILE_SEPARATOR = "/";
+    private final static char FILE_SEPARATOR_CHAR = FILE_SEPARATOR.charAt(0);
+    private final static short AVERAGE_KEY_LENGTH = 30;
+    private static final String m_strBase64Chars = 
+        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+    
+
+    public LRUCacheHandler(FilterConfig config)
+    {      
+        mName = config.getFilterName();
+        mLogger.info("Initializing for: " + mName);
+        int size = 200;
+        try
+        {
+            size = Integer.parseInt(config.getInitParameter("size"));
+        }
+        catch (Exception e)
+        {
+            mLogger.warn(config.getFilterName() 
+                + "Can't read cache size parameter, using default...");
+        }
+        mLogger.info(mName + " size=" + size);
+        mPageCache = Collections.synchronizedMap(new LRUCache(size));
+
+        long intervalSeconds = mTimeoutInterval / 1000L;
+        try
+        {
+            mTimeoutInterval = 1000L * Long.parseLong(
+                config.getInitParameter("timeoutInterval"));
+            
+            if (mTimeoutInterval == -1)
+            {
+                mLogger.info(config.getFilterName() 
+                   + "timeoutInterval of -1: timeouts are disabled");
+            }
+            else if (mTimeoutInterval < (30 * 1000))
+            {
+                mTimeoutInterval = 30 * 1000;
+                mLogger.warn(config.getFilterName() 
+                   + "timeoutInterval cannot be less than 30 seconds");
+            }
+        }
+        catch (Exception e)
+        {
+            mLogger.warn(config.getFilterName() 
+                + "Can't read timeoutInterval parameter, disabling timeout.");
+            mTimeoutInterval = -1;
+        }
+        mLogger.info(mName + " timeoutInterval=" + intervalSeconds);
+        
+        try
+        {
+            mTimeoutRatio = Float.parseFloat(
+                config.getInitParameter("timeoutRatio"));
+        }
+        catch (Exception e)
+        {
+            mLogger.warn(config.getFilterName() 
+                + "Can't read timeoutRatio parameter, using default...");
+        }
+        mLogger.info(mName + " timeoutRatio=" + mTimeoutRatio);
+        
+        if (mTimeoutInterval != -1 && mTimeoutRatio != 0.0)
+        {
+            Timer timer = new Timer();            
+            timer.scheduleAtFixedRate(new TimerTask() {
+                public void run() {
+                    timeoutCache();
+                }
+            }, mTimeoutInterval, mTimeoutInterval);        
+        }
+    }
+    
+    /**
+     * @see org.roller.presentation.pagecache.FilterHandler#destroy()
+     */
+    public void destroy()
+    {
+    }
+
+    /**
+     * @see org.roller.presentation.pagecache.FilterHandler#doFilter(
+     *      javax.servlet.ServletRequest, javax.servlet.ServletResponse,
+     *      javax.servlet.FilterChain)
+     */
+    public void doFilter(ServletRequest req, ServletResponse res,
+                    FilterChain chain) throws ServletException, IOException
+    {
+        HttpServletRequest request = (HttpServletRequest) req;
+        HttpServletResponse response = (HttpServletResponse) res;
+
+        // get locale
+        Locale locale = LanguageUtil.getViewLocale(request);
+        
+        // generate language-sensitive cache-key
+        String generatedKey = null;
+        if (locale != null)
+        {
+            generatedKey = generateEntryKey(null, 
+                      request, 1, locale.getLanguage());
+        }
+        else
+        {
+            generatedKey = generateEntryKey(null, 
+                      request, 1, null);
+        }
+        
+        // Add authenticated user name, if there is one, to cache key
+        java.security.Principal prince = request.getUserPrincipal();        
+        StringBuffer keyb = new StringBuffer();
+        keyb.append(generatedKey);
+        if (prince != null)
+        {
+            keyb.append("_");
+            keyb.append(prince);
+        }
+        String key = keyb.toString();
+                
+        ResponseContent respContent = (ResponseContent)getFromCache(key);
+        if (respContent == null) 
+        {
+            try
+            {
+                CacheHttpServletResponseWrapper cacheResponse = 
+                    new CacheHttpServletResponseWrapper(response);
+                chain.doFilter(request, cacheResponse);
+                
+                // Store as the cache content the result of the response
+                // if no exception was noted by content generator.
+                if (request.getAttribute("DisplayException") == null)
+                {
+                    ResponseContent rc = cacheResponse.getContent();
+                    putToCache(key, rc);
+                }
+                else
+                {
+                    StringBuffer sb = new StringBuffer();
+                    sb.append("Display exception, cache, key=");
+                    sb.append(key);
+                    mLogger.error(sb.toString());
+                }
+            }
+            catch (java.net.SocketException se)
+            {
+                // ignore socket exceptions
+            }
+            catch (Exception e)
+            {
+                // something unexpected and bad happened
+                StringBuffer sb = new StringBuffer();
+                sb.append("Error rendering page, key=");
+                sb.append(key);
+                mLogger.error(sb.toString());
+            }           
+        }
+        else
+        {
+            try
+            {
+                respContent.writeTo(response);
+            }
+            catch (java.net.SocketException se)
+            {
+                // ignore socket exceptions
+            }
+            catch (Exception e)
+            {
+                if (mLogger.isDebugEnabled())
+                {
+                    StringBuffer sb = new StringBuffer();
+                    sb.append("Probably a client abort exception, key=");
+                    sb.append(key);
+                    mLogger.error(sb.toString());
+                }
+            }
+            
+        }
+    }
+
+    /**
+     * Purge entire cache.
+     */
+    public synchronized void flushCache(HttpServletRequest req)
+    {
+        mPageCache.clear();
+    }
+
+    /**
+     * Purge user's entries from cache.
+     */
+    public synchronized void removeFromCache(HttpServletRequest req, UserData user)
+    {
+        // TODO: can we make this a little more precise, perhaps via regex?
+        String rssString = "/rss/" + user.getUserName(); // user's pages
+        String pageString = "/page/" + user.getUserName(); // user's RSS feeds
+        String mainRssString = "/rss_"; // main RSS feed
+        List purgeList = new ArrayList();
+        
+        Iterator keys = mPageCache.keySet().iterator();
+        while (keys.hasNext())
+        {
+            String key = (String) keys.next();
+            
+            if (key.indexOf(rssString)!=-1 || key.indexOf(pageString)!=-1 || key.indexOf(mainRssString)!=-1) 
+            {
+                purgeList.add(key);
+            }
+        }
+        
+        Iterator purgeIter = purgeList.iterator();
+        while (purgeIter.hasNext())
+        {
+            String key = (String) purgeIter.next();
+            mPageCache.remove(key);
+        }
+        
+        if (mLogger.isDebugEnabled())
+        {
+            StringBuffer sb = new StringBuffer();
+            sb.append("Purged, count=");
+            sb.append(purgeList.size());
+            sb.append(", user=");
+            sb.append(user.getUserName());
+            mLogger.debug(sb.toString());
+        }        
+    }
+    
+    public synchronized void timeoutCache() 
+    {
+        if (mTimeoutRatio == 1.0)
+        {
+            mLogger.debug("Timing out whole cache: " + mName);
+            mPageCache.clear();   
+        }
+        else 
+        {
+            int numToTimeout = (int)(mTimeoutRatio * mPageCache.size());
+            mLogger.debug(
+                "Timing out " + numToTimeout + " of " + mPageCache.size()
+                + " entries from cache: " + mName);
+            ArrayList allKeys = new ArrayList(mPageCache.keySet());
+            for (int i=numToTimeout; i>0; i--) 
+            {
+                mPageCache.remove(allKeys.get(i));
+            }
+        }
+    }
+    
+    /** 
+     * Get from cache. Synchronized because "In access-ordered linked hash 
+     * maps, merely querying the map with get is a structural modification" 
+     */
+    public synchronized Object getFromCache(String key) 
+    {
+        Object entry = mPageCache.get(key);
+        
+        if (entry != null && mLogger.isDebugEnabled())
+        {
+            hits++;
+        }
+        return entry;
+    }
+
+    public synchronized void putToCache(String key, Object entry) 
+    {
+        mPageCache.put(key, entry);
+        if (mLogger.isDebugEnabled())
+        {
+            misses++;
+            
+            StringBuffer sb = new StringBuffer();
+            sb.append("Missed, cache size=");
+            sb.append(mPageCache.size());
+            sb.append(", hits=");
+            sb.append(hits);
+            sb.append(", misses=");
+            sb.append(misses);
+            sb.append(", key=");
+            sb.append(key);
+            mLogger.debug(sb.toString());
+        }
+    }
+
+    public String generateEntryKey(String key, 
+                       HttpServletRequest request, int scope, String language)
+    {
+        StringBuffer cBuffer = new StringBuffer(AVERAGE_KEY_LENGTH);
+        // Append the language if available
+        if (language != null)
+        {
+            cBuffer.append(FILE_SEPARATOR).append(language);
+        }
+        
+        //cBuffer.append(FILE_SEPARATOR).append(request.getServerName());
+        
+        if (key != null)
+        {
+            cBuffer.append(FILE_SEPARATOR).append(key);
+        }
+        else
+        {
+            String generatedKey = request.getRequestURI();
+            if (generatedKey.charAt(0) != FILE_SEPARATOR_CHAR)
+            {
+                cBuffer.append(FILE_SEPARATOR_CHAR);
+            }
+            cBuffer.append(generatedKey);
+            cBuffer.append("_").append(request.getMethod()).append("_");
+            generatedKey = getSortedQueryString(request);
+            if (generatedKey != null)
+            {
+                try
+                {
+                    java.security.MessageDigest digest = 
+                        java.security.MessageDigest.getInstance("MD5");
+                    byte[] b = digest.digest(generatedKey.getBytes());
+                    cBuffer.append("_");
+                    // Base64 encoding allows for unwanted slash characters.
+                    cBuffer.append(toBase64(b).replace('/', '_'));
+                }
+                catch (Exception e)
+                {
+                    // Ignore query string
+                }
+            }
+        }
+        return cBuffer.toString();
+    }
+
+    protected String getSortedQueryString(HttpServletRequest request)
+    {
+        Map paramMap = request.getParameterMap();
+        if (paramMap.isEmpty())
+        {
+            return null;
+        }
+        Set paramSet = new TreeMap(paramMap).entrySet();
+        StringBuffer buf = new StringBuffer();
+        boolean first = true;
+        for (Iterator it = paramSet.iterator(); it.hasNext();)
+        {
+            Map.Entry entry = (Map.Entry) it.next();
+            String[] values = (String[]) entry.getValue();
+            for (int i = 0; i < values.length; i++)
+            {
+                String key = (String) entry.getKey();
+                if ((key.length() != 10) || !"jsessionid".equals(key))
+                {
+                    if (first)
+                    {
+                        first = false;
+                    }
+                    else
+                    {
+                        buf.append('&');
+                    }
+                    buf.append(key).append('=').append(values[i]);
+                }
+            }
+        }
+        // We get a 0 length buffer if the only parameter was a jsessionid
+        if (buf.length() == 0)
+        {
+            return null;
+        }
+        else
+        {
+            return buf.toString();
+        }
+    }
+
+    /**
+     * Convert a byte array into a Base64 string (as used in mime formats)
+     */
+    private static String toBase64(byte[] aValue)
+    {
+        int byte1;
+        int byte2;
+        int byte3;
+        int iByteLen = aValue.length;
+        StringBuffer tt = new StringBuffer();
+        for (int i = 0; i < iByteLen; i += 3)
+        {
+            boolean bByte2 = (i + 1) < iByteLen;
+            boolean bByte3 = (i + 2) < iByteLen;
+            byte1 = aValue[i] & 0xFF;
+            byte2 = (bByte2) ? (aValue[i + 1] & 0xFF) : 0;
+            byte3 = (bByte3) ? (aValue[i + 2] & 0xFF) : 0;
+            tt.append(m_strBase64Chars.charAt(byte1 / 4));
+            tt.append(m_strBase64Chars.charAt((byte2 / 16)
+                            + ((byte1 & 0x3) * 16)));
+            tt.append(((bByte2) ? m_strBase64Chars.charAt((byte3 / 64)
+                            + ((byte2 & 0xF) * 4)) : '='));
+            tt.append(((bByte3) ? m_strBase64Chars.charAt(byte3 & 0x3F) : '='));
+        }
+        return tt.toString();
+    }
+}

Added: incubator/roller/trunk/src/org/roller/presentation/pagecache/rollercache/LRUCacheHandler2.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/pagecache/rollercache/LRUCacheHandler2.java?rev=189602&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/pagecache/rollercache/LRUCacheHandler2.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/pagecache/rollercache/LRUCacheHandler2.java Wed Jun  8 09:06:16 2005
@@ -0,0 +1,385 @@
+/*
+ * Created on Jun 15, 2004
+ */
+package org.roller.presentation.pagecache.rollercache;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.roller.pojos.UserData;
+import org.roller.presentation.LanguageUtil;
+import org.roller.presentation.pagecache.FilterHandler;
+import org.roller.util.LRUCache2;
+
+/**
+ * Page cache implementation that uses a simple LRUCache. Can be configured 
+ * using filter configuration parameters:
+ * <ul>
+ * <li><b>size</b>: number of pages to keep in cache. Once cache reaches
+ * this size, each new cache entry will push out the LRU cache entry.</li>
+ * <li><b>timeout</b>: interval to timeout pages in milliseconds.</li>
+ * </ul> 
+ * @author David M Johnson
+ */
+public class LRUCacheHandler2 implements FilterHandler
+{
+    private static Log mLogger = 
+        LogFactory.getFactory().getInstance(LRUCacheHandler2.class);
+
+    private LRUCache2 mPageCache = null;   
+    private String mName = null;
+    
+    // Statistics
+    private int misses = 0;
+    private int hits = 0;
+    
+    private final static String FILE_SEPARATOR = "/";
+    private final static char FILE_SEPARATOR_CHAR = FILE_SEPARATOR.charAt(0);
+    private final static short AVERAGE_KEY_LENGTH = 30;
+    private static final String m_strBase64Chars = 
+        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";    
+
+    public LRUCacheHandler2(FilterConfig config)
+    {      
+        mName = config.getFilterName();
+        mLogger.info("Initializing for: " + mName);
+        int size = 200;
+        try
+        {
+            size = Integer.parseInt(config.getInitParameter("size"));
+        }
+        catch (Exception e)
+        {
+            mLogger.warn(config.getFilterName() 
+                + "Can't read cache size parameter, using default...");
+        }
+        mLogger.info(mName + " size=" + size);
+
+        long timeout = 30000;
+        try
+        {
+            timeout = Long.parseLong(
+                config.getInitParameter("timeout"));
+            
+            if (timeout < (30 * 1000))
+            {
+                timeout = 30 * 1000;
+                mLogger.warn(config.getFilterName() 
+                   + "timeout cannot be less than 30 seconds");
+            }
+        }
+        catch (Exception e)
+        {
+            mLogger.warn(config.getFilterName() 
+                + "Can't read timeout parameter, using default.");
+        }
+        mLogger.info(mName + " timeout=" + timeout);
+
+        mPageCache = new LRUCache2(size, timeout);
+    }
+    
+    /**
+     * @see org.roller.presentation.pagecache.FilterHandler#destroy()
+     */
+    public void destroy()
+    {
+    }
+
+    /**
+     * @see org.roller.presentation.pagecache.FilterHandler#doFilter(
+     *      javax.servlet.ServletRequest, javax.servlet.ServletResponse,
+     *      javax.servlet.FilterChain)
+     */
+    public void doFilter(ServletRequest req, ServletResponse res,
+                    FilterChain chain) throws ServletException, IOException
+    {
+        HttpServletRequest request = (HttpServletRequest) req;
+        HttpServletResponse response = (HttpServletResponse) res;
+
+        // get locale
+        Locale locale = LanguageUtil.getViewLocale(request);
+        
+        // generate language-sensitive cache-key
+        String generatedKey = null;
+        if (locale != null)
+        {
+            generatedKey = generateEntryKey(null, 
+                      request, 1, locale.getLanguage());
+        }
+        else
+        {
+            generatedKey = generateEntryKey(null, 
+                      request, 1, null);
+        }
+        
+        // Add authenticated user name, if there is one, to cache key
+        java.security.Principal prince = request.getUserPrincipal();        
+        StringBuffer keyb = new StringBuffer();
+        keyb.append(generatedKey);
+        if (prince != null)
+        {
+            keyb.append("_");
+            keyb.append(prince);
+        }
+        String key = keyb.toString();
+                
+        ResponseContent respContent = (ResponseContent)getFromCache(key);
+        if (respContent == null) 
+        {
+            try
+            {
+                CacheHttpServletResponseWrapper cacheResponse = 
+                    new CacheHttpServletResponseWrapper(response);
+                chain.doFilter(request, cacheResponse);
+                
+                // Store as the cache content the result of the response
+                // if no exception was noted by content generator.
+                if (request.getAttribute("DisplayException") == null)
+                {
+                    ResponseContent rc = cacheResponse.getContent();
+                    putToCache(key, rc);
+                }
+                else
+                {
+                    StringBuffer sb = new StringBuffer();
+                    sb.append("Display exception, cache, key=");
+                    sb.append(key);
+                    mLogger.error(sb.toString());
+                }
+            }
+            catch (java.net.SocketException se)
+            {
+                // ignore socket exceptions
+            }
+            catch (Exception e)
+            {
+                // something unexpected and bad happened
+                StringBuffer sb = new StringBuffer();
+                sb.append("Error rendering page, key=");
+                sb.append(key);
+                mLogger.error(sb.toString());
+            }           
+        }
+        else
+        {
+            try
+            {
+                respContent.writeTo(response);
+            }
+            catch (java.net.SocketException se)
+            {
+                // ignore socket exceptions
+            }
+            catch (Exception e)
+            {
+                if (mLogger.isDebugEnabled())
+                {
+                    StringBuffer sb = new StringBuffer();
+                    sb.append("Probably a client abort exception, key=");
+                    sb.append(key);
+                    mLogger.error(sb.toString());
+                }
+            }
+            
+        }
+    }
+
+    /**
+     * Purge entire cache.
+     */
+    public synchronized void flushCache(HttpServletRequest req)
+    {
+        mPageCache.purge();
+    }
+
+    /**
+     * Purge user's entries from cache.
+     */
+    public synchronized void removeFromCache(HttpServletRequest req, UserData user)
+    {
+        // TODO: can we make this a little more precise, perhaps via regex?
+        String rssString = "/rss/" + user.getUserName(); // user's pages
+        String pageString = "/page/" + user.getUserName(); // user's RSS feeds
+        String mainRssString = "/rss_"; // main RSS feed
+        
+        int beforeSize = mPageCache.size();
+        mPageCache.purge(new String[] {rssString, pageString, mainRssString});
+        int afterSize = mPageCache.size();
+        
+        if (mLogger.isDebugEnabled())
+        {
+            StringBuffer sb = new StringBuffer();
+            sb.append("Purged, count=");
+            sb.append(beforeSize - afterSize);
+            sb.append(", user=");
+            sb.append(user.getUserName());
+            mLogger.debug(sb.toString());
+        }        
+    }
+    
+    /** 
+     * Get from cache. Synchronized because "In access-ordered linked hash 
+     * maps, merely querying the map with get is a structural modification" 
+     */
+    public synchronized Object getFromCache(String key) 
+    {
+        Object entry = mPageCache.get(key);
+        
+        if (entry != null && mLogger.isDebugEnabled())
+        {
+            hits++;
+        }
+        return entry;
+    }
+
+    public synchronized void putToCache(String key, Object entry) 
+    {
+        mPageCache.put(key, entry);
+        if (mLogger.isDebugEnabled())
+        {
+            misses++;
+            
+            StringBuffer sb = new StringBuffer();
+            sb.append("Missed, cache size=");
+            sb.append(mPageCache.size());
+            sb.append(", hits=");
+            sb.append(hits);
+            sb.append(", misses=");
+            sb.append(misses);
+            sb.append(", key=");
+            sb.append(key);
+            mLogger.debug(sb.toString());
+        }
+    }
+
+    public String generateEntryKey(String key, 
+                       HttpServletRequest request, int scope, String language)
+    {
+        StringBuffer cBuffer = new StringBuffer(AVERAGE_KEY_LENGTH);
+        // Append the language if available
+        if (language != null)
+        {
+            cBuffer.append(FILE_SEPARATOR).append(language);
+        }
+        
+        //cBuffer.append(FILE_SEPARATOR).append(request.getServerName());
+        
+        if (key != null)
+        {
+            cBuffer.append(FILE_SEPARATOR).append(key);
+        }
+        else
+        {
+            String generatedKey = request.getRequestURI();
+            if (generatedKey.charAt(0) != FILE_SEPARATOR_CHAR)
+            {
+                cBuffer.append(FILE_SEPARATOR_CHAR);
+            }
+            cBuffer.append(generatedKey);
+            cBuffer.append("_").append(request.getMethod()).append("_");
+            generatedKey = getSortedQueryString(request);
+            if (generatedKey != null)
+            {
+                try
+                {
+                    java.security.MessageDigest digest = 
+                        java.security.MessageDigest.getInstance("MD5");
+                    byte[] b = digest.digest(generatedKey.getBytes());
+                    cBuffer.append("_");
+                    // Base64 encoding allows for unwanted slash characters.
+                    cBuffer.append(toBase64(b).replace('/', '_'));
+                }
+                catch (Exception e)
+                {
+                    // Ignore query string
+                }
+            }
+        }
+        return cBuffer.toString();
+    }
+
+    protected String getSortedQueryString(HttpServletRequest request)
+    {
+        Map paramMap = request.getParameterMap();
+        if (paramMap.isEmpty())
+        {
+            return null;
+        }
+        Set paramSet = new TreeMap(paramMap).entrySet();
+        StringBuffer buf = new StringBuffer();
+        boolean first = true;
+        for (Iterator it = paramSet.iterator(); it.hasNext();)
+        {
+            Map.Entry entry = (Map.Entry) it.next();
+            String[] values = (String[]) entry.getValue();
+            for (int i = 0; i < values.length; i++)
+            {
+                String key = (String) entry.getKey();
+                if ((key.length() != 10) || !"jsessionid".equals(key))
+                {
+                    if (first)
+                    {
+                        first = false;
+                    }
+                    else
+                    {
+                        buf.append('&');
+                    }
+                    buf.append(key).append('=').append(values[i]);
+                }
+            }
+        }
+        // We get a 0 length buffer if the only parameter was a jsessionid
+        if (buf.length() == 0)
+        {
+            return null;
+        }
+        else
+        {
+            return buf.toString();
+        }
+    }
+
+    /**
+     * Convert a byte array into a Base64 string (as used in mime formats)
+     */
+    private static String toBase64(byte[] aValue)
+    {
+        int byte1;
+        int byte2;
+        int byte3;
+        int iByteLen = aValue.length;
+        StringBuffer tt = new StringBuffer();
+        for (int i = 0; i < iByteLen; i += 3)
+        {
+            boolean bByte2 = (i + 1) < iByteLen;
+            boolean bByte3 = (i + 2) < iByteLen;
+            byte1 = aValue[i] & 0xFF;
+            byte2 = (bByte2) ? (aValue[i + 1] & 0xFF) : 0;
+            byte3 = (bByte3) ? (aValue[i + 2] & 0xFF) : 0;
+            tt.append(m_strBase64Chars.charAt(byte1 / 4));
+            tt.append(m_strBase64Chars.charAt((byte2 / 16)
+                            + ((byte1 & 0x3) * 16)));
+            tt.append(((bByte2) ? m_strBase64Chars.charAt((byte3 / 64)
+                            + ((byte2 & 0xF) * 4)) : '='));
+            tt.append(((bByte3) ? m_strBase64Chars.charAt(byte3 & 0x3F) : '='));
+        }
+        return tt.toString();
+    }
+}

Added: incubator/roller/trunk/src/org/roller/presentation/pagecache/rollercache/ResponseContent.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/pagecache/rollercache/ResponseContent.java?rev=189602&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/pagecache/rollercache/ResponseContent.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/pagecache/rollercache/ResponseContent.java Wed Jun  8 09:06:16 2005
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package org.roller.presentation.pagecache.rollercache;
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.util.Locale;
+
+import javax.servlet.ServletResponse;
+
+/**
+ * Holds the servlet response in a byte array so that it can be held
+ * in the cache (and, since this class is serializable, optionally
+ * persisted to disk).
+ *
+ * @version $Revision: 1.1 $
+ * @author  <a href="mailto:sergek@lokitech.com">Serge Knystautas</a>
+ */
+public class ResponseContent implements Serializable {
+    private transient ByteArrayOutputStream bout = new ByteArrayOutputStream(1000);
+    private Locale locale = null;
+    private String contentType = null;
+    private byte[] content = null;
+
+    /**
+     * Set the content type. We capture this so that when we serve this
+     * data from cache, we can set the correct content type on the response.
+     */
+    public void setContentType(String value) {
+        contentType = value;
+    }
+
+    /**
+     * Set the Locale. We capture this so that when we serve this data from
+     * cache, we can set the correct locale on the response.
+     */
+    public void setLocale(Locale value) {
+        locale = value;
+    }
+
+    /**
+     * Get an output stream. This is used by the {@link SplitServletOutputStream}
+     * to capture the original (uncached) response into a byte array.
+     */
+    public OutputStream getOutputStream() {
+        return bout;
+    }
+
+    /**
+     * Gets the size of this cached content.
+     *
+     * @return The size of the content, in bytes. If no content
+     * exists, this method returns <code>-1</code>.
+     */
+    public int getSize() {
+        return (content != null) ? content.length : (-1);
+    }
+
+    /**
+     * Called once the response has been written in its entirety. This
+     * method commits the response output stream by converting the output
+     * stream into a byte array.
+     */
+    public void commit() {
+        content = bout.toByteArray();
+    }
+
+    /**
+     * Writes this cached data out to the supplied <code>ServletResponse</code>.
+     *
+     * @param response The servlet response to output the cached content to.
+     * @throws IOException
+     */
+    public void writeTo(ServletResponse response) throws IOException {
+        //Send the content type and data to this response
+        if (contentType != null) {
+            response.setContentType(contentType);
+        }
+
+        response.setContentLength(content.length);
+
+        if (locale != null) {
+            response.setLocale(locale);
+        }
+
+        OutputStream out = new BufferedOutputStream(response.getOutputStream());
+        out.write(content);
+        out.flush();
+    }
+}

Added: incubator/roller/trunk/src/org/roller/presentation/pagecache/rollercache/SplitServletOutputStream.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/pagecache/rollercache/SplitServletOutputStream.java?rev=189602&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/pagecache/rollercache/SplitServletOutputStream.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/pagecache/rollercache/SplitServletOutputStream.java Wed Jun  8 09:06:16 2005
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package org.roller.presentation.pagecache.rollercache;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javax.servlet.ServletOutputStream;
+
+/**
+ * Extends the base <code>ServletOutputStream</code> class so that
+ * the stream can be captured as it gets written. This is achieved
+ * by overriding the <code>write()</code> methods and outputting
+ * the data to two streams - the original stream and a secondary stream
+ * that is designed to capture the written data.
+ *
+ * @version $Revision: 1.1 $
+ * @author  <a href="mailto:sergek@lokitech.com">Serge Knystautas</a>
+ */
+public class SplitServletOutputStream extends ServletOutputStream {
+    OutputStream captureStream = null;
+    OutputStream passThroughStream = null;
+
+    /**
+     * Constructs a split output stream that both captures and passes through
+     * the servlet response.
+     *
+     * @param captureStream The stream that will be used to capture the data.
+     * @param passThroughStream The pass-through <code>ServletOutputStream</code>
+     * that will write the response to the client as originally intended.
+     */
+    public SplitServletOutputStream(OutputStream captureStream, OutputStream passThroughStream) {
+        this.captureStream = captureStream;
+        this.passThroughStream = passThroughStream;
+    }
+
+    /**
+     * Writes the incoming data to both the output streams.
+     *
+     * @param value The int data to write.
+     * @throws IOException
+     */
+    public void write(int value) throws IOException {
+        captureStream.write(value);
+        passThroughStream.write(value);
+    }
+
+    /**
+     * Writes the incoming data to both the output streams.
+     *
+     * @param value The bytes to write to the streams.
+     * @throws IOException
+     */
+    public void write(byte[] value) throws IOException {
+        captureStream.write(value);
+        passThroughStream.write(value);
+    }
+
+    /**
+     * Writes the incoming data to both the output streams.
+     *
+     * @param b The bytes to write out to the streams.
+     * @param off The offset into the byte data where writing should begin.
+     * @param len The number of bytes to write.
+     * @throws IOException
+     */
+    public void write(byte[] b, int off, int len) throws IOException {
+        captureStream.write(b, off, len);
+        passThroughStream.write(b, off, len);
+    }
+}

Added: incubator/roller/trunk/src/org/roller/presentation/tags/DateTag.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/tags/DateTag.java?rev=189602&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/tags/DateTag.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/tags/DateTag.java Wed Jun  8 09:06:16 2005
@@ -0,0 +1,120 @@
+package org.roller.presentation.tags;
+
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.tagext.TagSupport;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.struts.Globals;
+import org.apache.struts.action.ActionMapping;
+import org.apache.struts.util.RequestUtils;
+
+/**
+ * Struts-based date field tag that wraps Matt Kruze's JavaScript data chooser.
+ * @jsp.tag name="Date"
+ */
+public class DateTag extends TagSupport
+{
+
+    // Unique key prefix keeps us from colliding with other tags and user data
+    public static final String KEY_PREFIX = "ZZZ_DATETAG_ZZZ";
+
+    private String property = null;
+    private String dateFormat = null;
+    private Boolean readOnly = Boolean.FALSE;
+
+    private static Log mLogger =
+        LogFactory.getFactory().getInstance(DateTag.class);
+
+    /**
+     * Renders date field by calling a JSP page.
+     */
+    public int doStartTag() throws JspException
+    {
+
+        // Get form name
+        ActionMapping mapping =
+            (ActionMapping) pageContext.getRequest().getAttribute(
+                Globals.MAPPING_KEY);
+        String formName = mapping.getName();
+
+        // Get value of form field
+        Object value =
+            RequestUtils.lookup(pageContext, formName, property, null);
+        if (value == null)
+            value = "";
+
+        // put variables into request scope for view page
+        pageContext.getRequest().setAttribute(
+            KEY_PREFIX + "_formName",
+            formName);
+        pageContext.getRequest().setAttribute(
+            KEY_PREFIX + "_property",
+            property);
+        pageContext.getRequest().setAttribute(
+            KEY_PREFIX + "_dateFormat",
+            dateFormat);
+        pageContext.getRequest().setAttribute(
+            KEY_PREFIX + "_readOnly",
+            readOnly);
+        pageContext.getRequest().setAttribute(KEY_PREFIX + "_value", value);
+
+        // dispatch to view page
+        try
+        {
+            pageContext.include("/tags/date.jsp");
+        }
+        catch (Exception e)
+        {
+            // can't handle this here
+            throw new JspException("ERROR including date.jsp");
+        }
+
+        // Don't evaluate content of tag, just continue processing this page
+        return (SKIP_BODY);
+    }
+
+    /**
+     * Date format string to be used.
+     * 
+     * @jsp.attribute required="true" rtexprvalue="true" type="java.lang.String"
+     */
+    public String getDateFormat()
+    {
+        return dateFormat;
+    }
+
+    /**
+     * Name of form property represented. 
+     * @jsp.attribute required="true" rtexprvalue="true" type="java.lang.String"
+     */
+    public String getProperty()
+    {
+        return property;
+    }
+
+    /**
+     * True if field should be readOnly. 
+     * @jsp.attribute required="false" rtexprvalue="true" type="java.lang.Boolean"
+     */
+    public Boolean getReadOnly()
+    {
+        return readOnly;
+    }
+
+    public void setDateFormat(String dateFormat)
+    {
+        this.dateFormat = dateFormat;
+    }
+
+    public void setProperty(String property)
+    {
+        this.property = property;
+    }
+
+    public void setReadOnly(Boolean readOnly)
+    {
+        this.readOnly = readOnly;
+    }
+
+}

Added: incubator/roller/trunk/src/org/roller/presentation/tags/HybridTag.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/tags/HybridTag.java?rev=189602&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/tags/HybridTag.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/tags/HybridTag.java Wed Jun  8 09:06:16 2005
@@ -0,0 +1,79 @@
+/*
+ * HybridTag.java
+ *
+ * Created on February 10, 2002, 11:12 PM
+ */
+
+package org.roller.presentation.tags;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.tagext.TagSupport;
+
+/**
+ * JSP tag designed to be used from JSP page or from Velocity page.
+ * Tag must be a standalone tag, design precludes contents.
+ * @author David M Johnson
+ */
+public abstract class HybridTag extends TagSupport 
+{
+    private static Log mLogger = 
+        LogFactory.getFactory().getInstance(HybridTag.class);
+
+    public HybridTag() 
+    {
+    }
+
+	public String toString()
+	{
+        String ret = null;
+        try 
+        {
+            StringWriter sw = new StringWriter();
+            doStartTag( new PrintWriter( sw, true ));
+			// See, design precludes contents 
+            doEndTag( new PrintWriter( sw, true ));
+            ret = sw.toString();
+        }
+        catch (Exception e)
+        {
+            ret = "Exception in tag";
+            mLogger.error(ret,e);
+        }
+        return ret;
+	}
+    
+	public String emit()
+	{
+		return toString();
+	}
+
+	public int doStartTag() throws JspException 
+	{
+		return doStartTag( new PrintWriter( pageContext.getOut(), true) );
+	}
+
+
+	public int doEndTag() throws JspException 
+	{
+		return doEndTag( new PrintWriter( pageContext.getOut(), true) );
+	}
+
+	/** Default processing of the end tag returning SKIP_BODY. */
+	public int doStartTag( PrintWriter pw ) throws JspException
+	{
+		return SKIP_BODY;
+	}
+
+	/** Default processing of the end tag returning EVAL_PAGE. */
+	public int doEndTag( PrintWriter pw ) throws JspException
+	{
+		return EVAL_PAGE;
+	}
+
+}