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 2006/05/02 00:23:34 UTC

svn commit: r398712 [15/32] - in /incubator/roller/trunk/src/org/apache: ./ roller/ roller/business/ roller/business/hibernate/ roller/business/referrers/ roller/business/runnable/ roller/business/search/ roller/business/search/operations/ roller/busin...

Added: incubator/roller/trunk/src/org/apache/roller/presentation/cache/LRUCacheImpl.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/presentation/cache/LRUCacheImpl.java?rev=398712&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/presentation/cache/LRUCacheImpl.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/presentation/cache/LRUCacheImpl.java Mon May  1 15:23:02 2006
@@ -0,0 +1,122 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+*  contributor license agreements.  The ASF licenses this file to You
+* under the Apache License, Version 2.0 (the "License"); you may not
+* use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+/*
+ * LRUCacheImpl.java
+ *
+ * Created on November 6, 2005, 10:33 AM
+ */
+package org.apache.roller.presentation.cache;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+
+/**
+ * A simple LRU Cache.
+ *
+ * @author Allen Gilliland
+ */
+public class LRUCacheImpl implements Cache {
+    
+    private static Log mLogger = LogFactory.getLog(LRUCacheImpl.class);
+    
+    private Map cache = null;
+    
+    
+    protected LRUCacheImpl() {
+        
+        this.cache = Collections.synchronizedMap(new LRULinkedHashMap(100));
+    }
+    
+    
+    protected LRUCacheImpl(int maxsize) {
+        
+        this.cache = Collections.synchronizedMap(new LRULinkedHashMap(maxsize));
+    }
+    
+    
+    /**
+     * Store an entry in the cache.
+     */
+    public synchronized void put(String key, Object value) {
+        
+        this.cache.put(key, value);
+    }
+    
+    
+    /**
+     * Retrieve an entry from the cache.
+     */
+    public synchronized Object get(String key) {
+        
+        return this.cache.get(key);
+    }
+    
+    
+    public synchronized void remove(String key) {
+        
+        this.cache.remove(key);
+    }
+    
+    
+    public synchronized void remove(Set keys) {
+        
+        Iterator it = keys.iterator();
+        while(it.hasNext())
+            this.cache.remove((String) it.next());
+    }
+    
+    
+    public synchronized void clear() {
+        
+        this.cache.clear();
+    }
+    
+    
+    public synchronized Set keySet() {
+        return this.cache.keySet();
+    }
+    
+    
+    public Map stats() {
+        
+        return new HashMap();
+    }
+    
+    
+    // David Flanaghan: http://www.davidflanagan.com/blog/000014.html
+    private static class LRULinkedHashMap extends LinkedHashMap {
+        protected int maxsize;
+        
+        public LRULinkedHashMap(int maxsize) {
+            super(maxsize * 4 / 3 + 1, 0.75f, true);
+            this.maxsize = maxsize;
+        }
+        
+        protected boolean removeEldestEntry(Map.Entry eldest) {
+            return this.size() > this.maxsize;
+        }
+    }
+    
+}

Added: incubator/roller/trunk/src/org/apache/roller/presentation/cache/LazyExpiringCacheEntry.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/presentation/cache/LazyExpiringCacheEntry.java?rev=398712&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/presentation/cache/LazyExpiringCacheEntry.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/presentation/cache/LazyExpiringCacheEntry.java Mon May  1 15:23:02 2006
@@ -0,0 +1,91 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+*  contributor license agreements.  The ASF licenses this file to You
+* under the Apache License, Version 2.0 (the "License"); you may not
+* use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+/*
+ * LazyExpiringCacheEntry.java
+ *
+ * Created on January 17, 2006, 10:14 AM
+ */
+
+package org.apache.roller.presentation.cache;
+
+import java.io.Serializable;
+
+
+/**
+ * A cache entry that is meant to expire in a lazy fashion.
+ *
+ * The way to use this class is to wrap the object you want to cache in an
+ * instance of this class and store that in your cache.  Then when you want
+ * to retrieve this entry you must input a last-expired time which can be
+ * compared against the time this entry was cached to determine if the cached
+ * entry is "fresh".  If the object is not fresh then we don't return it.
+ *
+ * This essentially allows us to track when an object is cached and then before
+ * we can retrieve that cached object we must compare it with it's last known
+ * invalidation time to make sure it hasn't expired.  This is useful because
+ * instead of actively purging lots of cached objects from the cache at 
+ * invalidation time, we can now be lazy and just invalidate them when we
+ * actually try to retrieve the cached object.
+ *
+ * This is useful for Roller because we will no longer have to iterate through
+ * the list of cached objects and inspect the keys to figure out what items to
+ * invalidate.  Instead we can just sit back and let the items be invalidated as
+ * we try to use them.
+ *
+ * @author Allen Gilliland
+ */
+public class LazyExpiringCacheEntry implements Serializable {
+    
+    private Object value = null;
+    private long timeCached = -1;
+    
+    
+    public LazyExpiringCacheEntry(Object item) {
+        this.value = item;
+        this.timeCached = System.currentTimeMillis();
+    }
+    
+    
+    /**
+     * Retrieve the value of this cache entry if it is still "fresh".
+     *
+     * If the value has expired then we return null.
+     */
+    public Object getValue(long lastInvalidated) {
+        if(this.isInvalid(lastInvalidated)) {
+            return null;
+        } else {
+            return this.value;
+        }
+    }
+    
+    
+    /**
+     * Determine if this cache entry has expired.
+     */
+    public boolean isInvalid(long lastInvalidated) {
+        
+        return (this.timeCached < lastInvalidated);
+    }
+
+    
+    public long getTimeCached() {
+        return timeCached;
+    }
+    
+}

Added: incubator/roller/trunk/src/org/apache/roller/presentation/filters/BreadCrumbFilter.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/presentation/filters/BreadCrumbFilter.java?rev=398712&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/presentation/filters/BreadCrumbFilter.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/presentation/filters/BreadCrumbFilter.java Mon May  1 15:23:02 2006
@@ -0,0 +1,144 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+*  contributor license agreements.  The ASF licenses this file to You
+* under the Apache License, Version 2.0 (the "License"); you may not
+* use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+package org.apache.roller.presentation.filters;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.apache.commons.collections.ArrayStack;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.roller.config.RollerConfig;
+import org.apache.roller.presentation.RollerSession;
+import org.apache.roller.util.StringUtils;
+import org.apache.roller.util.Utilities;
+
+/**
+ * Intercepts requests and places URL
+ * into breadcrumb stack.
+ * 
+ * @web.filter name="BreadCrumbFilter"
+ * *web.filter-mapping url-pattern="/*.do"
+ * @web.filter-init-param name="maxStackSize" value="3"
+ *
+ * @author <a href="mailto:lance@brainopolis.com">Lance Lavandowska</a>
+ *
+**/
+public final class BreadCrumbFilter implements Filter
+{
+    private static Log mLogger = 
+        LogFactory.getFactory().getInstance(BreadCrumbFilter.class);
+    
+    private int mMaxStackSize = 10;
+
+    
+    public void doFilter(
+        ServletRequest req, ServletResponse resp, FilterChain chain)
+        throws IOException, ServletException
+    {
+        HttpServletRequest request = (HttpServletRequest) req;
+        HttpServletResponse response = (HttpServletResponse) resp;
+
+        HttpSession ses = request.getSession(false);
+        ArrayStack stack = null;
+        if (ses != null)
+        {
+            stack = (ArrayStack)ses.getAttribute(RollerSession.BREADCRUMB);
+        }
+        if (stack == null)
+        {
+            stack = new ArrayStack();
+        }
+
+        // This gives you a chance to look at your breadcrumb trail
+        if (request.getQueryString() != null 
+            && request.getQueryString().equals("BreadCrumb"))
+        {
+            response.setContentType("text/html; charset=UTF-8");
+            ServletOutputStream out = response.getOutputStream();
+
+            for (int i=0; i<stack.size(); i++)
+            {
+                out.println(stack.peek(i).toString() +"<br>");
+            }
+            out.flush();
+            out.close();
+        }
+        else
+        {
+            chain.doFilter(req, resp);
+        }
+
+        StringBuffer url = request.getRequestURL();
+        // now that we've successfully returned,
+        // add url to stack if it isn't a Velocity page
+        String servletPath = request.getServletPath();
+        if (servletPath.indexOf("page") == -1 &&
+            servletPath.indexOf("comments") == -1)
+        {    
+            if (request.getQueryString() != null)
+            {
+                url.append("?").append( request.getQueryString() );
+            }
+            if (stack.size() == mMaxStackSize)
+            {
+                stack.remove(mMaxStackSize-1);
+            }
+            stack.push(url.toString());
+        }
+        if (ses != null)
+        {    
+        	ses.setAttribute(RollerSession.BREADCRUMB, stack);
+        }
+    }
+
+    /**
+     * Initialize controller values of filter.
+    **/
+    public void init(FilterConfig filterConfig)
+    {
+        mLogger.debug("Initializing Breadcrumb Filter");
+
+        String stackSize = RollerConfig.getProperty("breadcrumbs.stacksize");
+        if (!StringUtils.isEmpty(stackSize))
+        {
+            int mSS = Utilities.stringToInt(stackSize);
+            if (mSS != 0)
+            {
+                mMaxStackSize = mSS;
+                mLogger.info("set breadcrumb stack size to "+mSS);
+            }
+        }
+    }
+
+    /** destroy any instance values other than filterConfig **/
+    public void destroy()
+    {
+        mMaxStackSize = 10;
+    }
+}

Added: incubator/roller/trunk/src/org/apache/roller/presentation/filters/ByteArrayOutputStreamWrapper.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/presentation/filters/ByteArrayOutputStreamWrapper.java?rev=398712&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/presentation/filters/ByteArrayOutputStreamWrapper.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/presentation/filters/ByteArrayOutputStreamWrapper.java Mon May  1 15:23:02 2006
@@ -0,0 +1,99 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+*  contributor license agreements.  The ASF licenses this file to You
+* under the Apache License, Version 2.0 (the "License"); you may not
+* use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+package org.apache.roller.presentation.filters;
+
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+
+import javax.servlet.ServletOutputStream;
+
+/*
+ * @author llavandowska
+ *
+ * Implementation of ServletOutputStream that allows the filter to hold the
+ * Response content for insertion into the cache.
+ */
+public class ByteArrayOutputStreamWrapper extends ServletOutputStream
+{
+    protected OutputStream intStream;
+    protected ByteArrayOutputStream baStream;
+    protected boolean finallized = false;
+    protected boolean flushOnFinalizeOnly = true;
+
+    public ByteArrayOutputStreamWrapper(OutputStream outStream)
+    {
+        intStream = outStream;
+        baStream = new ByteArrayOutputStream();
+    }
+
+    public ByteArrayOutputStreamWrapper()
+    {
+        intStream = System.out;
+        baStream = new ByteArrayOutputStream();
+    }
+
+    public ByteArrayOutputStream getByteArrayStream()
+    {
+        return baStream;
+    }
+
+    public void setFinallized()
+    {
+        finallized = true;
+    }
+
+    public boolean isFinallized()
+    {
+        return finallized;
+    }
+
+
+    public void write(int i) throws java.io.IOException
+    {
+        baStream.write(i);
+    }
+
+    public void close() throws java.io.IOException
+    {
+        if (finallized) {
+            processStream();
+            intStream.close();
+        }
+    }
+
+    public void flush() throws java.io.IOException
+    {
+        if (baStream.size() != 0) {
+            if (!flushOnFinalizeOnly || finallized) {
+                processStream();
+                baStream = new ByteArrayOutputStream();
+            }
+        }
+    }
+
+    protected void processStream() throws java.io.IOException
+    {
+        intStream.write(baStream.toByteArray());
+        intStream.flush();
+    }
+    
+    public void clear()
+    {
+        baStream = new ByteArrayOutputStream();
+    }
+}

Added: incubator/roller/trunk/src/org/apache/roller/presentation/filters/ByteArrayResponseWrapper.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/presentation/filters/ByteArrayResponseWrapper.java?rev=398712&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/presentation/filters/ByteArrayResponseWrapper.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/presentation/filters/ByteArrayResponseWrapper.java Mon May  1 15:23:02 2006
@@ -0,0 +1,70 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+*  contributor license agreements.  The ASF licenses this file to You
+* under the Apache License, Version 2.0 (the "License"); you may not
+* use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+package org.apache.roller.presentation.filters;
+
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+/*
+ * @author llavandowska
+ *
+ * Implementation of HttpServletResponseWrapper.
+ */
+public class ByteArrayResponseWrapper extends HttpServletResponseWrapper
+{
+    private PrintWriter tpWriter;
+    private ByteArrayOutputStreamWrapper tpStream;
+
+    public ByteArrayResponseWrapper(ServletResponse inResp)
+    throws java.io.IOException
+    {
+        super((HttpServletResponse) inResp);
+        tpStream = new ByteArrayOutputStreamWrapper(inResp.getOutputStream());
+        tpWriter = new PrintWriter(new OutputStreamWriter(tpStream,"UTF-8"));
+    }
+
+    public ServletOutputStream getOutputStream()
+    throws java.io.IOException
+    {
+        return tpStream;
+    }
+
+    public PrintWriter getWriter() throws java.io.IOException
+    {
+        return tpWriter;
+    }
+     
+    /** Get a String representation of the entire buffer.
+     */    
+    public String toString()
+    {
+        return tpStream.getByteArrayStream().toString();
+    }
+    
+    public ByteArrayOutputStream getByteArrayOutputStream()
+    throws java.io.IOException
+    {
+        return tpStream.getByteArrayStream();
+    }
+}

Added: incubator/roller/trunk/src/org/apache/roller/presentation/filters/CharEncodingFilter.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/presentation/filters/CharEncodingFilter.java?rev=398712&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/presentation/filters/CharEncodingFilter.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/presentation/filters/CharEncodingFilter.java Mon May  1 15:23:02 2006
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  The ASF licenses this file to You
+ * under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.  For additional information regarding
+ * copyright in this work, please see the NOTICE file in the top level
+ * directory of this distribution.
+ */
+
+package org.apache.roller.presentation.filters;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+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.HttpSession;
+import javax.servlet.jsp.jstl.core.Config;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.struts.Globals;
+
+/**
+ * Entry point filter for all requests.  This filter ensures that the request encoding is set to UTF-8 before any other
+ * processing forces request parsing using a default encoding.  It also syncs up the Struts and JSTL locales.  This
+ * filter should normally be first and last in the chain.
+ *
+ * @author <a href="mailto:anil@busybuddha.org">Anil Gangolli</a>
+ * @web.filter name="CharEncodingFilter"
+ */
+
+public class CharEncodingFilter implements Filter
+{
+    private FilterConfig mFilterConfig = null;
+    private static Log mLogger =
+        LogFactory.getFactory().getInstance(CharEncodingFilter.class);
+
+    /**
+     * init
+     */
+    public void init(FilterConfig filterConfig) throws ServletException
+    {
+        mFilterConfig = filterConfig;
+    }
+
+    /**
+     * destroy
+     */
+    public void destroy()
+    {
+    }
+
+    /**
+     * Set the character encoding and sync up Struts and JSTL locales.  This filter should normally be first (and last)
+     * in the chain.
+     */
+    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
+        throws IOException, ServletException
+    {
+        if (mLogger.isDebugEnabled()) mLogger.debug("Processing CharEncodingFilter");
+        try
+        {
+            req.setCharacterEncoding("UTF-8");
+            if (mLogger.isDebugEnabled()) mLogger.debug("Set request character encoding to UTF-8");
+        }
+        catch (UnsupportedEncodingException e)
+        {
+            // This should never happen since UTF-8 is a Java-specified required encoding.
+            throw new ServletException("Can't set incoming encoding to UTF-8");
+        }
+
+        // Keep JSTL and Struts Locale's in sync
+        // NOTE: The session here will get created if it is not present.  This code was taken from its
+        // earlier incarnation in RequestFilter, which also caused the session to be created.
+        HttpSession session = ((HttpServletRequest) req).getSession();
+        if (mLogger.isDebugEnabled()) mLogger.debug("Synchronizing JSTL and Struts locales");
+        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);
+
+        chain.doFilter(req, res);
+    }
+
+}

Added: incubator/roller/trunk/src/org/apache/roller/presentation/filters/CompressionFilter.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/presentation/filters/CompressionFilter.java?rev=398712&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/presentation/filters/CompressionFilter.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/presentation/filters/CompressionFilter.java Mon May  1 15:23:02 2006
@@ -0,0 +1,122 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+*  contributor license agreements.  The ASF licenses this file to You
+* under the Apache License, Version 2.0 (the "License"); you may not
+* use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+package org.apache.roller.presentation.filters;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.zip.GZIPOutputStream;
+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 org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+
+/** 
+ * Filter that compresses output with gzip (assuming that browser supports gzip).
+ * <P>
+ * Taken from More Servlets and JavaServer Pages from Prentice Hall and 
+ * Sun Microsystems Press, http://www.moreservlets.com/.
+ * &copy; 2002 Marty Hall; may be freely used or adapted.
+ *
+ * @web.filter name="CompressionFilter"
+ */
+
+public class CompressionFilter implements Filter {
+    
+    private static Log mLogger = LogFactory.getLog(CompressionFilter.class);
+    
+    
+    /** 
+     * If browser does not support gzip, invoke resource normally. If browser 
+     * does support gzip, set the Content-Encoding response header and invoke 
+     * resource with a wrapped response that collects all the output. Extract 
+     * the output and write it into a gzipped byte array. Finally, write that 
+     * array to the client's output stream.
+     */
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+            throws ServletException, IOException {
+        
+        HttpServletRequest req = (HttpServletRequest) request;
+        HttpServletResponse res = (HttpServletResponse) response;
+        
+        if (!isGzipSupported(req)) {
+            // Invoke resource normally.
+            chain.doFilter(req, res);
+        } else {
+            // Tell browser we are sending it gzipped data.
+            res.setHeader("Content-Encoding", "gzip");
+            
+            // Invoke resource, accumulating output in the wrapper.
+            ByteArrayResponseWrapper responseWrapper =
+                    new ByteArrayResponseWrapper(response);
+            
+            chain.doFilter(req, responseWrapper);
+            
+            ByteArrayOutputStream outputStream = responseWrapper.getByteArrayOutputStream();
+            
+            // Get character array representing output.
+            mLogger.debug("Pre-zip size:" + outputStream.size());
+            
+            // Make a writer that compresses data and puts
+            // it into a byte array.
+            ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+            GZIPOutputStream zipOut = new GZIPOutputStream(byteStream);
+            
+            // Compress original output and put it into byte array.
+            zipOut.write(responseWrapper.getByteArrayOutputStream().toByteArray());
+            
+            // Gzip streams must be explicitly closed.
+            zipOut.close();
+            
+            mLogger.debug("Gzip size:" + byteStream.size());
+            
+            // Update the Content-Length header.
+            res.setContentLength(byteStream.size());
+            
+            ByteArrayOutputStreamWrapper newOut =
+                    (ByteArrayOutputStreamWrapper) responseWrapper.getOutputStream();
+            newOut.clear();
+            newOut.setFinallized();
+            
+            /* now force close of OutputStream */
+            newOut.write(byteStream.toByteArray());
+            newOut.close();
+        }
+        
+    }
+    
+
+    public void init(FilterConfig config) throws ServletException {}
+    
+    
+    public void destroy() {}
+    
+    
+    private boolean isGzipSupported(HttpServletRequest req) {
+        String browserEncodings = req.getHeader("Accept-Encoding");
+        return ((browserEncodings != null)
+                    && (browserEncodings.indexOf("gzip") != -1));
+    }
+    
+}

Added: incubator/roller/trunk/src/org/apache/roller/presentation/filters/DebugFilter.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/presentation/filters/DebugFilter.java?rev=398712&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/presentation/filters/DebugFilter.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/presentation/filters/DebugFilter.java Mon May  1 15:23:02 2006
@@ -0,0 +1,71 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+*  contributor license agreements.  The ASF licenses this file to You
+* under the Apache License, Version 2.0 (the "License"); you may not
+* use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+/*
+ * DebugFilter.java
+ *
+ * Created on April 17, 2006, 10:30 AM
+ */
+
+package org.apache.roller.presentation.filters;
+
+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;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+
+/**
+ * A simple debugging filter.
+ *
+ * This filter is NOT mapped by default and is here only for Roller developers
+ * to use while they are working on the code and debugging things.
+ *
+ * @web.filter name="DebugFilter"
+ */
+public class DebugFilter implements Filter {
+    
+    private static Log log = LogFactory.getLog(DebugFilter.class);
+    
+    
+    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
+            throws IOException, ServletException {
+        
+        HttpServletRequest request = (HttpServletRequest) req;
+        HttpServletResponse response = (HttpServletResponse) res;
+        
+        log.info("ENTERING "+request.getRequestURL());
+        
+        chain.doFilter(request, response);
+        
+        log.info("EXITING "+request.getRequestURL());
+    }
+    
+    
+    public void destroy() {}
+    
+    
+    public void init(FilterConfig filterConfig) {}
+    
+}

Added: incubator/roller/trunk/src/org/apache/roller/presentation/filters/FeedCacheFilter.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/presentation/filters/FeedCacheFilter.java?rev=398712&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/presentation/filters/FeedCacheFilter.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/presentation/filters/FeedCacheFilter.java Mon May  1 15:23:02 2006
@@ -0,0 +1,384 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+*  contributor license agreements.  The ASF licenses this file to You
+* under the Apache License, Version 2.0 (the "License"); you may not
+* use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+/*
+ * RssCacheFilter.java
+ *
+ * Created on November 5, 2005, 6:32 PM
+ */
+
+package org.apache.roller.presentation.filters;
+
+import java.io.IOException;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+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 org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.roller.config.RollerConfig;
+import org.apache.roller.pojos.BookmarkData;
+import org.apache.roller.pojos.CommentData;
+import org.apache.roller.pojos.FolderData;
+import org.apache.roller.pojos.RefererData;
+import org.apache.roller.pojos.UserData;
+import org.apache.roller.pojos.WeblogCategoryData;
+import org.apache.roller.pojos.WeblogEntryData;
+import org.apache.roller.pojos.WeblogTemplate;
+import org.apache.roller.pojos.WebsiteData;
+import org.apache.roller.presentation.WeblogFeedRequest;
+import org.apache.roller.presentation.cache.Cache;
+import org.apache.roller.presentation.cache.CacheHandler;
+import org.apache.roller.presentation.cache.CacheManager;
+import org.apache.roller.presentation.cache.LazyExpiringCacheEntry;
+import org.apache.roller.presentation.util.CacheHttpServletResponseWrapper;
+import org.apache.roller.presentation.util.ResponseContent;
+
+
+/**
+ * A filter used for caching fully rendered xml feeds.
+ *
+ * This filter should only be applied to /rss/*, /atom/*, /flavor/*
+ *
+ * @web.filter name="FeedCacheFilter"
+ *
+ * @author  Allen Gilliland
+ */
+public class FeedCacheFilter implements Filter, CacheHandler {
+    
+    private static Log mLogger = LogFactory.getLog(FeedCacheFilter.class);
+    
+    // a unique identifier for this cache, this is used as the prefix for
+    // roller config properties that apply to this cache
+    private static final String CACHE_ID = "cache.feed";
+    
+    // our cache of rendered feeds
+    private Cache mCache = null;
+    
+    // the last time the main feeds were expired
+    private Date mainLastExpiredDate = new Date();
+    
+    // for metrics
+    private double hits = 0;
+    private double misses = 0;
+    private double purges = 0;
+    private Date startTime = new Date();
+    
+    
+    /**
+     * Process filter.
+     */
+    public void doFilter(ServletRequest req,
+                        ServletResponse res,
+                        FilterChain chain)
+            throws IOException, ServletException {
+        
+        mLogger.debug("entering");
+        
+        HttpServletRequest request = (HttpServletRequest) req;
+        HttpServletResponse response = (HttpServletResponse) res;
+        
+        WeblogFeedRequest feedRequest = null;
+        try {
+            feedRequest = new WeblogFeedRequest(request);
+        } catch(Exception e) {
+            // some kind of error parsing the request
+            mLogger.error("error creating weblog feed request", e);
+            response.sendError(HttpServletResponse.SC_NOT_FOUND);
+            return;
+        }
+        
+        String key = this.CACHE_ID+":"+this.generateKey(feedRequest);
+        
+        try {
+            ResponseContent respContent = null;
+            long lastExpiration = 0;
+            
+            // first, we need to determine the last time the specified feed was expired.
+            // if this is a weblog specific feed then ask the CacheManager for
+            // the last expired time of the weblog.  otherwise this is a main feed and we
+            // keep that last expired time ourselves
+            if(feedRequest.getWeblogHandle() != null) {
+                Date lastExpirationDate =
+                        (Date) CacheManager.getLastExpiredDate(feedRequest.getWeblogHandle());
+                if(lastExpirationDate != null)
+                    lastExpiration = lastExpirationDate.getTime();
+            } else {
+                lastExpiration = this.mainLastExpiredDate.getTime();
+            }
+            
+            LazyExpiringCacheEntry entry =
+                    (LazyExpiringCacheEntry) this.mCache.get(key);
+            if(entry != null) {
+                respContent = (ResponseContent) entry.getValue(lastExpiration);
+                
+                if(respContent == null)
+                    mLogger.debug("HIT-INVALID "+key);
+            }
+                
+            if (respContent == null) {
+                
+                mLogger.debug("MISS "+key);
+                this.misses++;
+                
+                CacheHttpServletResponseWrapper cacheResponse =
+                        new CacheHttpServletResponseWrapper(response);
+                
+                chain.doFilter(request, cacheResponse);
+                
+                cacheResponse.flushBuffer();
+                
+                // only cache if there wasn't an exception
+                if (request.getAttribute("DisplayException") == null) {
+                    ResponseContent rc = cacheResponse.getContent();
+                    
+                    this.mCache.put(key, new LazyExpiringCacheEntry(rc));
+                } else {
+                    // it is expected that whoever caught this display exception
+                    // is the one who reported it to the logs
+                    mLogger.debug("Display exception "+key);
+                }
+                
+            } else {
+                
+                mLogger.debug("HIT "+key);
+                this.hits++;
+                
+                respContent.writeTo(response);
+            }
+            
+        } catch(Exception ex) {
+            
+            if(ex.getMessage().indexOf("ClientAbort") != -1) {
+                // ClientAbortException ... ignored
+                mLogger.debug(ex.getMessage());
+                
+            } else if(ex.getMessage().indexOf("SocketException") != -1) {
+                // SocketException ... ignored
+                mLogger.debug(ex.getMessage());
+                
+            } else {
+                mLogger.error("Unexpected exception rendering feed "+key, ex);
+            }
+            
+            // gotta send something to the client
+            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+        }
+        
+        mLogger.debug("exiting");
+    }
+    
+    
+    /**
+     * Generate a cache key from a parsed weblog feed request.
+     * This generates a key of the form ...
+     *
+     * <context>[/handle]/<flavor>[/category]/<language>[/excerpts]
+     *
+     * examples ...
+     *
+     * main/rss/en
+     * weblog/foo/rss/MyCategory/en
+     * weblog/foo/atom/en/excerpts
+     *
+     */
+    private String generateKey(WeblogFeedRequest feedRequest) {
+        
+        StringBuffer key = new StringBuffer();
+        key.append(feedRequest.getContext());
+        
+        if(feedRequest.getContext().equals("weblog")) {
+            key.append("/").append(feedRequest.getWeblogHandle().toLowerCase());
+            key.append("/").append(feedRequest.getFlavor());
+            
+            if(feedRequest.getWeblogCategory() != null) {
+                String cat = feedRequest.getWeblogCategory();
+                if(cat.startsWith("/"))
+                    cat = cat.substring(1).replaceAll("/","_");
+                
+                // categories may contain spaces, which is not desired
+                key.append("/").append(org.apache.commons.lang.StringUtils.deleteWhitespace(cat));
+            }
+        } else {
+            key.append("/").append(feedRequest.getFlavor());
+        }
+        
+        // add language
+        key.append("/").append(feedRequest.getLanguage());
+        
+        if(feedRequest.isExcerpts()) {
+            key.append("/excerpts");
+        }
+        
+        return key.toString();
+    }
+    
+    
+    /**
+     * A weblog entry has changed.
+     */
+    public void invalidate(WeblogEntryData entry) {
+        this.invalidate(entry.getWebsite());
+    }
+    
+    
+    /**
+     * A weblog has changed.
+     */
+    public synchronized void invalidate(WebsiteData website) {
+        
+        mLogger.debug("invalidating website = "+website.getHandle());
+        
+        // update our main feed last expiration date
+        synchronized(this) {
+            this.mainLastExpiredDate = new Date();
+        }
+    }
+    
+    
+    /**
+     * A bookmark has changed.
+     */
+    public void invalidate(BookmarkData bookmark) {
+        // ignored
+    }
+    
+    
+    /**
+     * A folder has changed.
+     */
+    public void invalidate(FolderData folder) {
+        // ignored
+    }
+    
+    
+    /**
+     * A comment has changed.
+     */
+    public void invalidate(CommentData comment) {
+        // ignored
+    }
+    
+    
+    /**
+     * A referer has changed.
+     */
+    public void invalidate(RefererData referer) {
+        // ignored
+    }
+    
+    
+    /**
+     * A user profile has changed.
+     */
+    public void invalidate(UserData user) {
+        // ignored
+    }
+    
+    
+    /**
+     * A category has changed.
+     */
+    public void invalidate(WeblogCategoryData category) {
+        this.invalidate(category.getWebsite());
+    }
+    
+    
+    /**
+     * Clear the entire cache.
+     */
+    public void clear() {
+        mLogger.info("Clearing cache");
+        this.mCache.clear();
+        this.startTime = new Date();
+        this.hits = 0;
+        this.misses = 0;
+        this.purges = 0;
+    }
+    
+    
+    /**
+     * A weblog template has changed.
+     */
+    public void invalidate(WeblogTemplate template) {
+        // ignored
+    }
+    
+    
+    public Map getStats() {
+        
+        Map stats = new HashMap();
+        stats.put("cacheType", this.mCache.getClass().getName());
+        stats.put("startTime", this.startTime);
+        stats.put("hits", new Double(this.hits));
+        stats.put("misses", new Double(this.misses));
+        stats.put("purges", new Double(this.purges));
+        
+        // calculate efficiency
+        if((misses - purges) > 0) {
+            double efficiency = hits / (misses + hits);
+            stats.put("efficiency", new Double(efficiency * 100));
+        }
+        
+        return stats;
+    }
+    
+    
+    /**
+     * Destroy method for this filter
+     */
+    public void destroy() {
+    }
+    
+    
+    /**
+     * Init method for this filter
+     */
+    public void init(FilterConfig filterConfig) {
+        
+        mLogger.info("Initializing feed cache");
+        
+        Map cacheProps = new HashMap();
+        Enumeration allProps = RollerConfig.keys();
+        String prop = null;
+        while(allProps.hasMoreElements()) {
+            prop = (String) allProps.nextElement();
+            
+            // we are only interested in props for this cache
+            if(prop.startsWith(CACHE_ID+".")) {
+                cacheProps.put(prop.substring(CACHE_ID.length()+1), 
+                        RollerConfig.getProperty(prop));
+            }
+        }
+        
+        mLogger.info(cacheProps);
+        
+        mCache = CacheManager.constructCache(this, cacheProps);
+    }
+    
+}

Added: incubator/roller/trunk/src/org/apache/roller/presentation/filters/IPBanFilter.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/presentation/filters/IPBanFilter.java?rev=398712&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/presentation/filters/IPBanFilter.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/presentation/filters/IPBanFilter.java Mon May  1 15:23:02 2006
@@ -0,0 +1,92 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+*  contributor license agreements.  The ASF licenses this file to You
+* under the Apache License, Version 2.0 (the "License"); you may not
+* use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+
+package org.apache.roller.presentation.filters;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+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 org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.roller.util.StringUtils;
+
+/**
+ * Braindead simple IPBanFilter. XDoclet tags disabled because I don't want this
+ * to be on by default. Users who want it can configure it themselves in web.xml.
+ * web.filter name="IPBanFilter"
+ * web.filter-init-param name="denyFrom" value="" description="Comma-separated list of banned IPs"
+ * @author David M Johnson
+ */
+public class IPBanFilter implements Filter
+{
+    private List denyFrom = null;
+    private static Log mLogger =
+        LogFactory.getFactory().getInstance(IPBanFilter.class);
+    
+    public IPBanFilter()
+    {
+        super();
+    }
+
+    public void init(FilterConfig filterConfig) throws ServletException
+    {
+        /*
+         * This should be updated to the new config, however I don't want
+         * to do it myself since I'm not sure if/how it is being used.
+         * 
+         * This looks like something that could be a long list, so maybe
+         * it should be in the DB or possibly its own file?
+         * -- Allen G
+         */
+        String denyFromParam = filterConfig.getInitParameter("denyFrom");
+        denyFrom = Arrays.asList(StringUtils.split(denyFromParam,","));
+    }
+
+    public void doFilter(
+        ServletRequest req,
+        ServletResponse res,
+        FilterChain chain)
+        throws IOException, ServletException
+    {
+        HttpServletRequest request = (HttpServletRequest) req;
+        HttpServletResponse response = (HttpServletResponse) res;
+        if (denyFrom.contains(request.getRemoteAddr()))
+        {
+            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+            return;
+        }
+        else 
+        {            
+            chain.doFilter(request, response);
+        }
+    }
+
+    public void destroy() 
+    {
+    }
+}

Added: incubator/roller/trunk/src/org/apache/roller/presentation/filters/IfModifiedFeedCacheFilter.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/presentation/filters/IfModifiedFeedCacheFilter.java?rev=398712&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/presentation/filters/IfModifiedFeedCacheFilter.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/presentation/filters/IfModifiedFeedCacheFilter.java Mon May  1 15:23:02 2006
@@ -0,0 +1,376 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+*  contributor license agreements.  The ASF licenses this file to You
+* under the Apache License, Version 2.0 (the "License"); you may not
+* use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+/*
+ * IfModifiedFeedCacheFilter.java
+ *
+ * Created on November 9, 2005, 2:47 PM
+ */
+
+package org.apache.roller.presentation.filters;
+
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+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 org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.roller.RollerException;
+import org.apache.roller.config.RollerConfig;
+import org.apache.roller.model.Roller;
+import org.apache.roller.model.RollerFactory;
+import org.apache.roller.model.UserManager;
+import org.apache.roller.model.WeblogManager;
+import org.apache.roller.pojos.BookmarkData;
+import org.apache.roller.pojos.CommentData;
+import org.apache.roller.pojos.FolderData;
+import org.apache.roller.pojos.RefererData;
+import org.apache.roller.pojos.UserData;
+import org.apache.roller.pojos.WeblogCategoryData;
+import org.apache.roller.pojos.WeblogEntryData;
+import org.apache.roller.pojos.WeblogTemplate;
+import org.apache.roller.pojos.WebsiteData;
+import org.apache.roller.presentation.WeblogFeedRequest;
+import org.apache.roller.presentation.cache.Cache;
+import org.apache.roller.presentation.cache.CacheHandler;
+import org.apache.roller.presentation.cache.CacheManager;
+import org.apache.roller.presentation.cache.LazyExpiringCacheEntry;
+
+
+/**
+ * A filter used for caching last modified dates.
+ *
+ * This may be applied to /rss/*, /atom/*, /flavor/*
+ * 
+ * @web.filter name="IfModifiedFeedCacheFilter"
+ *
+ * @author Allen Gilliland
+ */
+public class IfModifiedFeedCacheFilter implements Filter, CacheHandler {
+    
+    private static Log mLogger = 
+            LogFactory.getLog(IfModifiedFeedCacheFilter.class);
+    
+    // a unique identifier for this cache, this is used as the prefix for
+    // roller config properties that apply to this cache
+    private static final String CACHE_ID = "cache.ifmodified.feed";
+    
+    // the cache of last updated times
+    private Cache mCache = null;
+    
+    // the last time we expired our main feeds
+    private Date mainLastExpiredDate = new Date();
+    
+    SimpleDateFormat dateFormatter =
+            new SimpleDateFormat("EEE MMM d HH:mm:ss z yyyy");
+    
+    
+    /**
+     * Process filter.
+     */
+    public void doFilter(ServletRequest req, 
+                        ServletResponse res, 
+                        FilterChain chain)
+            throws IOException, ServletException {
+        
+        mLogger.debug("entering");
+        
+        HttpServletRequest request = (HttpServletRequest) req;
+        HttpServletResponse response = (HttpServletResponse) res;
+        
+        WeblogFeedRequest feedRequest = null;
+        try {
+            feedRequest = new WeblogFeedRequest(request);
+        } catch(Exception e) {
+            mLogger.error("error creating feed request", e);
+            response.sendError(HttpServletResponse.SC_NOT_FOUND);
+            return;
+        }
+        
+        String key = this.CACHE_ID+":"+this.generateKey(feedRequest);
+        
+        Date updateTime = null;
+        try {
+            long lastExpiration = 0;
+            
+            // first, we need to determine the last time the specified feed was expired.
+            // if this is a weblog specific feed then ask the CacheManager for
+            // the last expired time of the weblog.  otherwise this is a main feed and we
+            // keep that last expired time ourselves
+            if(feedRequest.getWeblogHandle() != null) {
+                Date lastExpirationDate =
+                        (Date) CacheManager.getLastExpiredDate(feedRequest.getWeblogHandle());
+                if(lastExpirationDate != null)
+                    lastExpiration = lastExpirationDate.getTime();
+            } else {
+                lastExpiration = this.mainLastExpiredDate.getTime();
+            }
+            
+            LazyExpiringCacheEntry entry =
+                    (LazyExpiringCacheEntry) this.mCache.get(key);
+            if(entry != null) {
+                updateTime = (Date) entry.getValue(lastExpiration);
+                
+                if(updateTime == null)
+                    mLogger.debug("HIT-INVALID "+key);
+            }
+            
+            if (updateTime == null) {
+                mLogger.debug("MISS "+key);
+                
+                if(feedRequest.getWeblogHandle() != null) {
+                    Roller roller = RollerFactory.getRoller();
+                    UserManager umgr = roller.getUserManager();
+                    WeblogManager wmgr = roller.getWeblogManager();
+                    
+                    updateTime = wmgr.getWeblogLastPublishTime(
+                            umgr.getWebsiteByHandle(feedRequest.getWeblogHandle()),
+                            feedRequest.getWeblogCategory());
+                    
+                    this.mCache.put(key, new LazyExpiringCacheEntry(updateTime));
+                    
+                } else {
+                    this.mCache.put(key, new LazyExpiringCacheEntry(new Date()));
+                }
+                
+            } else {
+                mLogger.debug("HIT "+key);
+            }
+            
+            // 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) {
+                    mLogger.debug("NOT_MODIFIED "+key);
+                    response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                    return;
+                }
+            }
+            
+        } 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);
+        
+        mLogger.debug("exiting");
+    }
+    
+    
+    /**
+     * Generate a cache key from a parsed weblog feed request.
+     * This generates a key of the form ...
+     *
+     * <context>/[handle]/<flavor>/[category]/<language>/[excerpts]
+     *
+     * examples ...
+     *
+     * main/rss/en
+     * planet/rss/en
+     * weblog/foo/rss/MyCategory/en
+     * weblog/foo/atom/en/excerpts
+     *
+     */
+    private String generateKey(WeblogFeedRequest feedRequest) {
+        
+        StringBuffer key = new StringBuffer();
+        key.append(feedRequest.getContext());
+        
+        if(feedRequest.getContext().equals("weblog")) {
+            key.append("/").append(feedRequest.getWeblogHandle().toLowerCase());
+            key.append("/").append(feedRequest.getFlavor());
+            
+            if(feedRequest.getWeblogCategory() != null) {
+                String cat = feedRequest.getWeblogCategory();
+                if(cat.startsWith("/"))
+                    cat = cat.substring(1).replaceAll("/","_");
+                
+                // categories may contain spaces, which is not desired
+                key.append("/").append(org.apache.commons.lang.StringUtils.deleteWhitespace(cat));
+            }
+        } else {
+            key.append("/").append(feedRequest.getFlavor());
+        }
+        
+        // add language
+        key.append("/").append(feedRequest.getLanguage());
+        
+        if(feedRequest.isExcerpts()) {
+            key.append("/excerpts");
+        }
+        
+        return key.toString();
+    }
+    
+    
+    /**
+     * A weblog entry has changed.
+     */
+    public void invalidate(WeblogEntryData entry) {
+        this.invalidate(entry.getWebsite());
+    }
+    
+    
+    /**
+     * A weblog has changed.
+     */
+    public synchronized void invalidate(WebsiteData website) {
+        
+        mLogger.debug("invalidating website = "+website.getHandle());
+        
+        // update our main feed last expiration date
+        synchronized(this) {
+            this.mainLastExpiredDate = new Date();
+        }
+    }
+    
+    
+    /**
+     * A bookmark has changed.
+     */
+    public void invalidate(BookmarkData bookmark) {
+        // ignored
+    }
+    
+    
+    /**
+     * A folder has changed.
+     */
+    public void invalidate(FolderData folder) {
+        // ignored
+    }
+    
+    
+    /**
+     * A comment has changed.
+     */
+    public void invalidate(CommentData comment) {
+        // ignored
+    }
+    
+    
+    /**
+     * A referer has changed.
+     */
+    public void invalidate(RefererData referer) {
+        // ignored
+    }
+    
+    
+    /**
+     * A user profile has changed.
+     */
+    public void invalidate(UserData user) {
+        // ignored
+    }
+    
+    
+    /**
+     * A category has changed.
+     */
+    public void invalidate(WeblogCategoryData category) {
+        this.invalidate(category.getWebsite());
+    }
+    
+    
+    /**
+     * A weblog template has changed.
+     */
+    public void invalidate(WeblogTemplate template) {
+        // ignored
+    }
+    
+    
+    /**
+     * Clear the entire cache.
+     */
+    public void clear() {
+        mLogger.info("Clearing cache");
+        this.mCache.clear();
+    }
+    
+    
+    public Map getStats() {
+        
+        Map stats = new HashMap();
+        
+        return stats;
+    }
+    
+    
+    /**
+     * Destroy method for this filter
+     */
+    public void destroy() {
+    }
+    
+    
+    /**
+     * Init method for this filter
+     */
+    public void init(FilterConfig filterConfig) {
+        
+        mLogger.info("Initializing if-modified feed cache");
+        
+        Map cacheProps = new HashMap();
+        Enumeration allProps = RollerConfig.keys();
+        String prop = null;
+        while(allProps.hasMoreElements()) {
+            prop = (String) allProps.nextElement();
+            
+            // we are only interested in props for this cache
+            if(prop.startsWith(CACHE_ID+".")) {
+                cacheProps.put(prop.substring(CACHE_ID.length()+1), 
+                        RollerConfig.getProperty(prop));
+            }
+        }
+        
+        mLogger.info(cacheProps);
+        
+        mCache = CacheManager.constructCache(this, cacheProps);
+    }
+    
+}

Added: incubator/roller/trunk/src/org/apache/roller/presentation/filters/IfModifiedWeblogPageCacheFilter.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/presentation/filters/IfModifiedWeblogPageCacheFilter.java?rev=398712&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/presentation/filters/IfModifiedWeblogPageCacheFilter.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/presentation/filters/IfModifiedWeblogPageCacheFilter.java Mon May  1 15:23:02 2006
@@ -0,0 +1,390 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+*  contributor license agreements.  The ASF licenses this file to You
+* under the Apache License, Version 2.0 (the "License"); you may not
+* use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+/*
+ * IfModifiedWeblogPageCacheFilter.java
+ *
+ * Created on November 9, 2005, 9:02 PM
+ */
+
+package org.apache.roller.presentation.filters;
+
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+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 org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.roller.config.RollerConfig;
+import org.apache.roller.pojos.BookmarkData;
+import org.apache.roller.pojos.CommentData;
+import org.apache.roller.pojos.FolderData;
+import org.apache.roller.pojos.RefererData;
+import org.apache.roller.pojos.UserData;
+import org.apache.roller.pojos.WeblogCategoryData;
+import org.apache.roller.pojos.WeblogEntryData;
+import org.apache.roller.pojos.WeblogTemplate;
+import org.apache.roller.pojos.WebsiteData;
+import org.apache.roller.presentation.WeblogPageRequest;
+import org.apache.roller.presentation.cache.Cache;
+import org.apache.roller.presentation.cache.CacheHandler;
+import org.apache.roller.presentation.cache.CacheManager;
+import org.apache.roller.presentation.cache.LazyExpiringCacheEntry;
+import org.apache.roller.util.Utilities;
+
+
+/**
+ * A filter used for caching last modified dates.
+ * 
+ * @web.filter name="IfModifiedWeblogPageCacheFilter"
+ *
+ * @author Allen Gilliland
+ */
+public class IfModifiedWeblogPageCacheFilter implements Filter, CacheHandler {
+    
+    private static Log mLogger = 
+            LogFactory.getLog(IfModifiedWeblogPageCacheFilter.class);
+    
+    // a unique identifier for this cache, this is used as the prefix for
+    // roller config properties that apply to this cache
+    private static final String CACHE_ID = "cache.ifmodified.weblogpage";
+    
+    private Cache mCache = null;
+    
+    SimpleDateFormat dateFormatter =
+            new SimpleDateFormat("EEE MMM d HH:mm:ss z yyyy");
+    
+    
+    /**
+     * Process filter.
+     */
+    public void doFilter(ServletRequest req, 
+                        ServletResponse res, 
+                        FilterChain chain)
+            throws IOException, ServletException {
+        
+        mLogger.debug("entering");
+        
+        HttpServletRequest request = (HttpServletRequest) req;
+        HttpServletResponse response = (HttpServletResponse) res;
+        
+        WeblogPageRequest pageRequest = null;
+        try {
+            pageRequest = new WeblogPageRequest(request);
+        } catch(Exception e) {
+            mLogger.error("error creating page request", e);
+            response.sendError(HttpServletResponse.SC_NOT_FOUND);
+            return;
+        }
+        
+        String key = this.CACHE_ID+":"+this.generateKey(pageRequest);
+        
+        Date updateTime = null;
+        try {
+            // we need the last expiration time for the given weblog
+            long lastExpiration = 0;
+            Date lastExpirationDate =
+                    (Date) CacheManager.getLastExpiredDate(pageRequest.getWeblogHandle());
+            if(lastExpirationDate != null)
+                lastExpiration = lastExpirationDate.getTime();
+            
+            LazyExpiringCacheEntry entry =
+                    (LazyExpiringCacheEntry) this.mCache.get(key);
+            if(entry != null) {
+                updateTime = (Date) entry.getValue(lastExpiration);
+                
+                if(updateTime == null)
+                    mLogger.debug("HIT-INVALID "+key);
+            }
+            
+            if (updateTime == null) {
+                mLogger.debug("MISS "+key);
+                
+                if(pageRequest.getWeblogHandle() != null) {
+                    // just set updateTime to now
+                    updateTime = new Date();
+                    this.mCache.put(key, new LazyExpiringCacheEntry(updateTime));
+                }
+                
+            } else {
+                mLogger.debug("HIT "+key);
+            }
+            
+            // 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) {
+                    mLogger.debug("NOT_MODIFIED "+key);
+                    response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                    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);
+        
+        mLogger.debug("exiting");
+    }
+    
+    
+    /**
+     * Generate a cache key from a parsed weblog page request.
+     * This generates a key of the form ...
+     *
+     * weblog/<handle>/page/<weblogPage>/[anchor]/<language>/[user]
+     *   or
+     * weblog/<handle>/page/<weblogPage>/[date]/[category]/<language>/[user]
+     *
+     *
+     * examples ...
+     *
+     * weblog/foo/page/Weblog/en
+     * weblog/foo/page/Weblog/entry_anchor/en
+     * weblog/foo/page/Weblog/20051110/en
+     * weblog/foo/page/Weblog/MyCategory/en
+     *
+     */
+    private String generateKey(WeblogPageRequest pageRequest) {
+        
+        StringBuffer key = new StringBuffer();
+        key.append("weblog/");
+        key.append(pageRequest.getWeblogHandle().toLowerCase());
+        key.append("/page/");
+        key.append(pageRequest.getWeblogPage());
+        
+        if(pageRequest.getWeblogAnchor() != null) {
+            // convert to base64 because there can be spaces in anchors :/
+            key.append("/").append(Utilities.toBase64(pageRequest.getWeblogAnchor().getBytes()));
+        } else {
+            if(pageRequest.getWeblogDate() != null) {
+                key.append("/").append(pageRequest.getWeblogDate());
+            }
+            
+            if(pageRequest.getWeblogCategory() != null) {
+                String cat = pageRequest.getWeblogCategory();
+                if(cat.startsWith("/")) {
+                    cat = cat.substring(1);
+                }
+
+                // categories may contain spaces, which is not desired
+                key.append("/").append(org.apache.commons.lang.StringUtils.deleteWhitespace(cat));
+            }
+        }
+        
+        // add language
+        key.append("/").append(pageRequest.getLanguage());
+        
+        // add login state
+        if(pageRequest.getAuthenticUser() != null) {
+            key.append("/user=").append(pageRequest.getAuthenticUser());
+        }
+        
+        return key.toString();
+    }
+    
+    
+    /**
+     * A weblog entry has changed.
+     */
+    public void invalidate(WeblogEntryData entry) {
+        
+        mLogger.debug("invalidating entry = "+entry.getAnchor());
+        
+        // we need to remove the following cached items if they exist
+        //   - the weblog main page
+        //   - the weblog entry permalink page
+        //   - the weblog entry category page
+        //   - the weblog entry date archive pages
+        
+        /*
+        Set removeSet = new HashSet();
+        
+        // TODO: it would be nice to be able to do this without iterating
+        //       over the entire cache key set
+        String key = null;
+        Iterator allKeys = this.mCache.keySet().iterator();
+        while(allKeys.hasNext()) {
+            key = (String) allKeys.next();
+            if(key.startsWith("ifmod:weblog/"+entry.getWebsite().getHandle())) {
+                removeSet.add(key);
+            }
+        }
+        
+        this.mCache.remove(removeSet);
+        */
+    }
+    
+    
+    /**
+     * A weblog has changed.
+     */
+    public void invalidate(WebsiteData website) {
+        
+        mLogger.debug("invalidating website = "+website.getHandle());
+        
+        // we need to remove the following cached items if they exist
+        //   - all pages for this weblog
+        
+        /*
+        Set removeSet = new HashSet();
+        
+        // TODO: it would be nice to be able to do this without iterating
+        //       over the entire cache key set
+        String key = null;
+        Iterator allKeys = this.mCache.keySet().iterator();
+        while(allKeys.hasNext()) {
+            key = (String) allKeys.next();
+            
+            if(key.startsWith("ifmod:weblog/"+website.getHandle())) {
+                removeSet.add(key);
+            }
+        }
+        
+        this.mCache.remove(removeSet);
+        */
+    }
+    
+    
+    /**
+     * A bookmark has changed.
+     */
+    public void invalidate(BookmarkData bookmark) {
+        this.invalidate(bookmark.getWebsite());
+    }
+    
+    
+    /**
+     * A folder has changed.
+     */
+    public void invalidate(FolderData folder) {
+        this.invalidate(folder.getWebsite());
+    }
+    
+    
+    /**
+     * A comment has changed.
+     */
+    public void invalidate(CommentData comment) {
+        this.invalidate(comment.getWeblogEntry());
+    }
+    
+    
+    /**
+     * A referer has changed.
+     */
+    public void invalidate(RefererData referer) {
+        // ignored
+        // TODO: we probably should invalidate the entire website?
+    }
+    
+    
+    /**
+     * A user profile has changed.
+     */
+    public void invalidate(UserData user) {
+        // ignored
+        // TODO: i don't think weblog pages currently have user info, but this may change
+    }
+    
+    
+    /**
+     * A category has changed.
+     */
+    public void invalidate(WeblogCategoryData category) {
+        this.invalidate(category.getWebsite());
+    }
+    
+    
+    /**
+     * A weblog template has changed.
+     */
+    public void invalidate(WeblogTemplate template) {
+        this.invalidate(template.getWebsite());
+    }
+    
+    
+    public void clear() {
+        this.mCache.clear();
+    }
+    
+    
+    public Map getStats() {
+        
+        Map stats = new HashMap();
+        
+        return stats;
+    }
+    
+    
+    /**
+     * Destroy method for this filter
+     */
+    public void destroy() {
+    }
+    
+    
+    /**
+     * Init method for this filter
+     */
+    public void init(FilterConfig filterConfig) {
+        
+        mLogger.info("Initializing if-modified cache");
+        
+        Map cacheProps = new HashMap();
+        Enumeration allProps = RollerConfig.keys();
+        String prop = null;
+        while(allProps.hasMoreElements()) {
+            prop = (String) allProps.nextElement();
+            
+            // we are only interested in props for this cache
+            if(prop.startsWith(CACHE_ID+".")) {
+                cacheProps.put(prop.substring(CACHE_ID.length()+1), 
+                        RollerConfig.getProperty(prop));
+            }
+        }
+        
+        mLogger.info(cacheProps);
+        
+        mCache = CacheManager.constructCache(this, cacheProps);
+    }
+    
+}

Added: incubator/roller/trunk/src/org/apache/roller/presentation/filters/IfPlanetModifiedFilter.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/presentation/filters/IfPlanetModifiedFilter.java?rev=398712&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/presentation/filters/IfPlanetModifiedFilter.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/presentation/filters/IfPlanetModifiedFilter.java Mon May  1 15:23:02 2006
@@ -0,0 +1,156 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+*  contributor license agreements.  The ASF licenses this file to You
+* under the Apache License, Version 2.0 (the "License"); you may not
+* use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+package org.apache.roller.presentation.filters;
+
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+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 org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.roller.RollerException;
+import org.apache.roller.config.RollerConfig;
+import org.apache.roller.model.RollerFactory;
+import org.apache.roller.presentation.PlanetRequest;
+import org.apache.roller.presentation.cache.ExpiringCacheEntry;
+
+
+/**
+ * Handles if-modified-since checking for planet resources.
+ *
+ * @web.filter name="IfPlanetModifiedFilter"
+ *
+ * @author David M Johnson
+ */
+public class IfPlanetModifiedFilter implements Filter {
+    
+    private static Log mLogger = LogFactory.getLog(IfPlanetModifiedFilter.class);
+    
+    private long timeout = 15 * 60 * 1000;
+    private ExpiringCacheEntry lastUpdateTime = null;
+    
+    SimpleDateFormat dateFormatter = new SimpleDateFormat("EEE MMM d HH:mm:ss z yyyy");
+    
+    
+    /**
+     * Filter processing.
+     *
+     * We check the incoming request for an "if-modified-since" header and
+     * repond with a 304 NOT MODIFIED when appropriate.
+     */
+    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
+            throws IOException, ServletException {
+        
+        HttpServletRequest request = (HttpServletRequest) req;
+        HttpServletResponse response = (HttpServletResponse) res;
+        
+        PlanetRequest planetRequest = null;
+        try {
+            planetRequest = new PlanetRequest(request);
+        } catch(Exception e) {
+            mLogger.error("error creating planet request", e);
+            response.sendError(HttpServletResponse.SC_NOT_FOUND);
+            return;
+        }
+        
+        Date updateTime = null;
+        try {
+            // first try our cached version
+            if(this.lastUpdateTime != null) {
+                updateTime = (Date) this.lastUpdateTime.getValue();
+            }
+            
+            // we need to get a fresh value
+            if(updateTime == null) {
+                
+                updateTime = RollerFactory.getRoller().getPlanetManager().getLastUpdated();
+                if (updateTime == null) {
+                    updateTime = new Date();
+                    mLogger.warn("Can't get lastUpdate time, using current time instead");
+                }
+                
+                this.lastUpdateTime = new ExpiringCacheEntry(updateTime, this.timeout);
+            }
+            
+            // 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)
+                synchronized (dateFormatter) {
+                    String date = dateFormatter.format(updateTime);
+                    updateTime = new Date(date);
+                }
+                if (updateTime.compareTo(sinceDate) <= 0) {
+                    response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                    return;
+                }
+            }
+            
+        } catch(RollerException re) {
+            // problem talking to db?
+            response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+            request.setAttribute("DisplayException", re);
+            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);
+    }
+    
+    
+    /**
+     * Init method for this filter
+     */
+    public void init(FilterConfig filterConfig) {
+        
+        mLogger.info("Initializing if-modified planet filter");
+        
+        // lookup our timeout value
+        String timeoutString = RollerConfig.getProperty("cache.planet.timeout");
+        try {
+            long timeoutSecs = Long.parseLong(timeoutString);
+            this.timeout = timeoutSecs * 1000;
+        } catch(Exception e) {
+            // ignored ... illegal value
+        }
+    }
+    
+    
+    public void destroy() {}
+    
+}