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 [16/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/filters/MainPageCacheFilter.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/presentation/filters/MainPageCacheFilter.java?rev=398712&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/presentation/filters/MainPageCacheFilter.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/presentation/filters/MainPageCacheFilter.java Mon May  1 15:23:02 2006
@@ -0,0 +1,332 @@
+/*
+* 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.
+*/
+/*
+ * MainPageCacheFilter.java
+ *
+ * Created on November 7, 2005, 2:32 PM
+ */
+package org.apache.roller.presentation.filters;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Locale;
+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.LanguageUtil;
+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.util.CacheHttpServletResponseWrapper;
+import org.apache.roller.presentation.util.ResponseContent;
+
+
+/**
+ * A filter used for caching the fully rendered pages ... /main.do, /planet.do
+ *
+ * @web.filter name="MainPageCacheFilter"
+ *
+ * @author  Allen Gilliland
+ */
+public class MainPageCacheFilter implements Filter, CacheHandler {
+    
+    private static Log mLogger = LogFactory.getLog(MainPageCacheFilter.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.mainpage";
+    
+    private boolean excludeOwnerPages = false;
+    private Cache mCache = null;
+    
+    // for metrics
+    private double hits = 0;
+    private double misses = 0;
+    private double skips = 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;
+        
+        String key = null;
+        Principal prince = null;
+        
+        String servlet = request.getServletPath();
+        if(servlet.equals("/main.do")) {
+            key = "main/page";
+        } else {
+            // not a main page request
+            mLogger.warn("not a main page "+servlet);
+            response.sendError(HttpServletResponse.SC_NOT_FOUND);
+            return;
+        }
+        
+        try {
+            // determine language
+            Locale locale = LanguageUtil.getViewLocale(request);
+            key += "/" + locale.getLanguage();
+            
+            // login status
+            prince = request.getUserPrincipal();
+            if(prince != null) {
+                key += "/user=" + prince.getName();
+            }
+            
+        } catch(Exception e) {
+            // problem getting language?
+            mLogger.error("problem parsing request", e);
+            response.sendError(HttpServletResponse.SC_NOT_FOUND);
+            return;
+        }
+        
+        
+        try {
+            ResponseContent respContent = null;
+            if(!this.excludeOwnerPages || prince == null) {
+                respContent = (ResponseContent) this.mCache.get(key);
+            }
+            
+            if(respContent == null) {
+
+                mLogger.debug("MISS "+key);
+                this.misses++;
+                
+                CacheHttpServletResponseWrapper cacheResponse =
+                        new CacheHttpServletResponseWrapper(response);
+                
+                chain.doFilter(request, cacheResponse);
+                
+                cacheResponse.flushBuffer();
+                
+                // only cache if we didn't get an exception
+                if (request.getAttribute("DisplayException") == null) {
+                    ResponseContent rc = cacheResponse.getContent();
+                    
+                    // only cache if this is not a logged in user?
+                    if(!this.excludeOwnerPages || prince == null) {
+                        this.mCache.put(key, rc);
+                    } else {
+                        mLogger.debug("SKIPPED "+key);
+                        this.skips++;
+                    }
+                } 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 page "+key, ex);
+            }
+            
+            // gotta send something to the client
+            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+        }
+        
+        mLogger.debug("exiting");
+    }
+    
+    
+    /**
+     * A weblog entry has changed.
+     */
+    public void invalidate(WeblogEntryData entry) {
+        this.mCache.clear();
+    }
+    
+    
+    /**
+     * A weblog has changed.
+     */
+    public void invalidate(WebsiteData website) {
+        this.mCache.clear();
+    }
+    
+    
+    /**
+     * 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) {
+        // ignored
+    }
+    
+    
+    /**
+     * 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();
+        this.startTime = new Date();
+        this.hits = 0;
+        this.misses = 0;
+        this.skips = 0;
+    }
+    
+    
+    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("skips", new Double(this.skips));
+        
+        // calculate efficiency
+        if(misses > 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 main page cache");
+
+        this.excludeOwnerPages = 
+                RollerConfig.getBooleanProperty("cache.mainpage.excludeOwnerEditPages");
+        
+        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/PersistenceSessionFilter.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/presentation/filters/PersistenceSessionFilter.java?rev=398712&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/presentation/filters/PersistenceSessionFilter.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/presentation/filters/PersistenceSessionFilter.java Mon May  1 15:23:02 2006
@@ -0,0 +1,75 @@
+/*
+* 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.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.model.Roller;
+import org.apache.roller.model.RollerFactory;
+
+
+/**
+ * Sole responsibility is to ensure that each request's Roller
+ * persistence session is released at end of the request.
+ *
+ * @web.filter name="PersistenceSessionFilter"
+ * @author David M. Johnson
+ */
+public class PersistenceSessionFilter implements Filter {
+    
+    private static Log mLogger = LogFactory.getLog(PersistenceSessionFilter.class);
+    
+    
+    /**
+     * Release Roller persistence session at end of request processing.
+     */
+    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
+            throws IOException, ServletException {
+        
+        HttpServletRequest request = (HttpServletRequest) req;
+        HttpServletResponse response = (HttpServletResponse) res;
+        
+        mLogger.debug("Entered PersistenceSessionFilter");
+        
+        Roller roller = RollerFactory.getRoller();
+        try {
+            chain.doFilter(request, response);
+        } finally {
+            mLogger.debug("Releasing Roller Session");
+            roller.release();
+        }
+        
+        mLogger.debug("Exiting PersistenceSessionFilter");
+    }
+    
+
+    public void init(FilterConfig filterConfig) throws ServletException {}
+    
+    public void destroy() {}
+    
+}
+

Added: incubator/roller/trunk/src/org/apache/roller/presentation/filters/PlanetCacheFilter.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/presentation/filters/PlanetCacheFilter.java?rev=398712&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/presentation/filters/PlanetCacheFilter.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/presentation/filters/PlanetCacheFilter.java Mon May  1 15:23:02 2006
@@ -0,0 +1,355 @@
+/*
+* 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.
+*/
+/*
+ * PlanetCacheFilter.java
+ *
+ * Created on December 12, 2005, 10:03 AM
+ */
+
+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.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.PlanetRequest;
+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.util.CacheHttpServletResponseWrapper;
+import org.apache.roller.presentation.util.ResponseContent;
+
+/**
+ * A cache filter for Planet Roller items ... /planet.do, /planetrss
+ *
+ * @web.filter name="PlanetCacheFilter"
+ *
+ * @author Allen Gilliland
+ */
+public class PlanetCacheFilter implements Filter, CacheHandler {
+    
+    private static Log mLogger = LogFactory.getLog(PlanetCacheFilter.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.planet";
+    
+    private boolean excludeOwnerPages = false;
+    private Cache mCache = null;
+    
+    // for metrics
+    private double hits = 0;
+    private double misses = 0;
+    private double skips = 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;
+        
+        PlanetRequest planetRequest = null;
+        try {
+            planetRequest = new PlanetRequest(request);
+        } catch(Exception e) {
+            // some kind of error parsing the request
+            mLogger.error("error creating planet request", e);
+            response.sendError(HttpServletResponse.SC_NOT_FOUND);
+            return;
+        }
+        
+        String key = "planetCache:"+this.generateKey(planetRequest);
+        
+        try {
+            ResponseContent respContent = null;
+            if(!this.excludeOwnerPages || !planetRequest.isLoggedIn()) {
+                respContent = (ResponseContent) this.mCache.get(key);
+            }
+            
+            if(respContent == null) {
+
+                mLogger.debug("MISS "+key);
+                this.misses++;
+                
+                CacheHttpServletResponseWrapper cacheResponse =
+                        new CacheHttpServletResponseWrapper(response);
+                
+                chain.doFilter(request, cacheResponse);
+                
+                cacheResponse.flushBuffer();
+                
+                // only cache if we didn't get an exception
+                if (request.getAttribute("DisplayException") == null) {
+                    ResponseContent rc = cacheResponse.getContent();
+                    
+                    // only cache if this is not a logged in user?
+                    if(!this.excludeOwnerPages || !planetRequest.isLoggedIn()) {
+                        this.mCache.put(key, rc);
+                    } else {
+                        mLogger.debug("SKIPPED "+key);
+                        this.skips++;
+                    }
+                } 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 page "+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 planet request.
+     * This generates a key of the form ...
+     *
+     * <context>/<type>/<language>[/user]
+     *   or
+     * <context>/<type>[/flavor]/<language>[/excerpts]
+     *
+     *
+     * examples ...
+     *
+     * planet/page/en
+     * planet/feed/rss/en/excerpts
+     *
+     */
+    private String generateKey(PlanetRequest planetRequest) {
+        
+        StringBuffer key = new StringBuffer();
+        key.append(planetRequest.getContext());
+        key.append("/");
+        key.append(planetRequest.getType());
+        
+        if(planetRequest.getFlavor() != null) {
+            key.append("/").append(planetRequest.getFlavor());
+        }
+        
+        // add language
+        key.append("/").append(planetRequest.getLanguage());
+        
+        if(planetRequest.getFlavor() != null) {
+            // add excerpts
+            if(planetRequest.isExcerpts()) {
+                key.append("/excerpts");
+            }
+        } else {
+            // add login state
+            if(planetRequest.getAuthenticUser() != null) {
+                key.append("/user=").append(planetRequest.getAuthenticUser());
+            }
+        }
+        
+        return key.toString();
+    }
+    
+    
+    /**
+     * A weblog entry has changed.
+     */
+    public void invalidate(WeblogEntryData entry) {
+        // ignored
+    }
+    
+    
+    /**
+     * A weblog has changed.
+     */
+    public void invalidate(WebsiteData website) {
+        // ignored
+    }
+    
+    
+    /**
+     * 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) {
+        // ignored
+    }
+    
+    
+    /**
+     * 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();
+        this.startTime = new Date();
+        this.hits = 0;
+        this.misses = 0;
+        this.skips = 0;
+    }
+    
+    
+    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("skips", new Double(this.skips));
+        
+        // calculate efficiency
+        if(misses > 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 planet cache");
+        
+        this.excludeOwnerPages = 
+                RollerConfig.getBooleanProperty("cache.planet.excludeOwnerEditPages");
+        
+        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/RefererFilter.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/presentation/filters/RefererFilter.java?rev=398712&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/presentation/filters/RefererFilter.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/presentation/filters/RefererFilter.java Mon May  1 15:23:02 2006
@@ -0,0 +1,223 @@
+/*
+* 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.regex.Pattern;
+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.business.referrers.IncomingReferrer;
+import org.apache.roller.business.referrers.ReferrerQueueManager;
+import org.apache.roller.model.RollerFactory;
+import org.apache.roller.presentation.RollerContext;
+import org.apache.roller.config.RollerConfig;
+import org.apache.roller.model.UserManager;
+import org.apache.roller.pojos.WebsiteData;
+import org.apache.roller.presentation.InvalidRequestException;
+import org.apache.roller.presentation.WeblogPageRequest;
+import org.apache.roller.util.SpamChecker;
+
+
+/**
+ * Keep track of referers.
+ *
+ * @author David M. Johnson
+ * @web.filter name="RefererFilter"
+ */
+public class RefererFilter implements Filter {
+    
+    private static Log mLogger = LogFactory.getLog(RefererFilter.class);
+    private static final String ROBOT_PATTERN_PROP_NAME = "referrer.robotCheck.userAgentPattern";
+    
+    private static Pattern robotPattern = null;
+    
+    private FilterConfig mFilterConfig = null;
+    private boolean processingEnabled = true;
+    
+    
+    /**
+     * doFilter
+     */
+    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) 
+            throws IOException, ServletException {
+        
+        // if referrer processing is disabled then we are done
+        if(!this.processingEnabled) {
+            chain.doFilter(req, res);
+            return;
+        }
+        
+        HttpServletRequest request = (HttpServletRequest) req;
+        HttpServletResponse response = (HttpServletResponse) res;
+        boolean ignoreReferrer = false;
+        boolean isRefSpammer = false;
+        boolean isRobot = false;
+        String referrerUrl = request.getHeader("Referer");
+        StringBuffer reqsb = request.getRequestURL();
+        if (request.getQueryString() != null) {
+            reqsb.append("?");
+            reqsb.append(request.getQueryString());
+        }
+        String requestUrl = reqsb.toString();
+        
+        // parse the incoming request and make sure it's a valid page request
+        WebsiteData weblog = null;
+        WeblogPageRequest pageRequest = null;
+        try {
+            pageRequest = new WeblogPageRequest(request);
+            UserManager userMgr = RollerFactory.getRoller().getUserManager();
+            weblog = userMgr.getWebsiteByHandle(pageRequest.getWeblogHandle());
+            
+            if(weblog == null) {
+                throw new Exception("no weblog named "+pageRequest.getWeblogHandle());
+            }
+            
+        } catch(Exception ex) {
+            // bad url or couldn't obtain weblog
+            response.sendError(HttpServletResponse.SC_NOT_FOUND);
+            return;
+        }
+        
+        // determine if this request came from a robot
+        if (robotPattern != null) {
+            // If the pattern is present, we check for whether the User-Agent matches,
+            // and set isRobot if so.  Currently, all referral processing, including
+            // spam check, is skipped for robots identified in this way.
+            String userAgent = request.getHeader("User-Agent");
+            isRobot = (userAgent != null && userAgent.length() > 0 && robotPattern.matcher(userAgent).matches());
+        }
+        
+        // validate the referrer
+        if (pageRequest != null && pageRequest.getWeblogHandle() != null && !isRobot) {
+            
+            RollerContext rctx = RollerContext.getRollerContext();
+            
+            // Base page URLs, with and without www.
+            String basePageUrlWWW =
+                    rctx.getAbsoluteContextUrl(request)+"/page/"+weblog.getHandle();
+            String basePageUrl = basePageUrlWWW;
+            if ( basePageUrlWWW.startsWith("http://www.") ) {
+                // chop off the http://www.
+                basePageUrl = "http://"+basePageUrlWWW.substring(11);
+            }
+            
+            // ignore referrers coming from users own blog
+            if (referrerUrl == null ||
+                    (!referrerUrl.startsWith(basePageUrl) &&
+                    !referrerUrl.startsWith(basePageUrlWWW))) {
+                
+                String selfSiteFragment = "/page/"+weblog.getHandle();
+
+                // validate the referrer
+                if ( referrerUrl != null ) {
+                    // ignore a Referrer from the persons own blog
+                    if (referrerUrl.indexOf(selfSiteFragment) != -1) {
+                        referrerUrl = null;
+                        ignoreReferrer = true;
+                    } else {
+                        // treat editor referral as direct
+                        int lastSlash = requestUrl.indexOf("/", 8);
+                        if (lastSlash == -1) lastSlash = requestUrl.length();
+                        String requestSite = requestUrl.substring(0, lastSlash);
+                        
+                        if (referrerUrl.matches(requestSite + ".*\\.do.*")) {
+                            referrerUrl = null;
+                        } else {
+                            // If referer URL is blacklisted, throw it out
+                            isRefSpammer = SpamChecker.checkReferrer(weblog, referrerUrl);
+                        }
+                    }
+                }
+                
+            } else {
+                mLogger.debug("Ignoring referer = "+referrerUrl);
+                ignoreReferrer = true;
+            }
+        }
+        
+        // pre-processing complete, let's finish the job
+        if (isRefSpammer) {
+            // spammers get a 403 Access Denied
+            response.sendError(HttpServletResponse.SC_FORBIDDEN);
+            return;
+            
+        } else if(!isRobot && !ignoreReferrer) {
+            // referrer is valid, lets record it
+            try {
+                IncomingReferrer referrer = new IncomingReferrer();
+                referrer.setReferrerUrl(referrerUrl);
+                referrer.setRequestUrl(requestUrl);
+                referrer.setWeblogHandle(pageRequest.getWeblogHandle());
+                referrer.setWeblogAnchor(pageRequest.getWeblogAnchor());
+                referrer.setWeblogDateString(pageRequest.getWeblogDate());
+                
+                ReferrerQueueManager refQueue =
+                    RollerFactory.getRoller().getReferrerQueueManager();
+                refQueue.processReferrer(referrer);
+            } catch(Exception e) {
+                mLogger.error("Error processing referrer", e);
+            }
+        }
+        
+        // referrer processed, continue with request
+        chain.doFilter(req, res);
+    }
+    
+    
+    /**
+     * init
+     */
+    public void init(FilterConfig filterConfig) throws ServletException {
+        
+        this.mFilterConfig = filterConfig;
+        
+        // see if built-in referrer processing is enabled
+        this.processingEnabled = 
+                RollerConfig.getBooleanProperty("referrers.processing.enabled");
+        
+        mLogger.info("Referrer processing enabled = "+this.processingEnabled);
+        
+        // check for possible robot pattern
+        String robotPatternStr = RollerConfig.getProperty(ROBOT_PATTERN_PROP_NAME);
+        if (robotPatternStr != null && robotPatternStr.length() >0) {
+            // Parse the pattern, and store the compiled form.
+            try {
+                robotPattern = Pattern.compile(robotPatternStr);
+            } catch (Exception e) {
+                // Most likely a PatternSyntaxException; log and continue as if it is not set.
+                mLogger.error("Error parsing "+ ROBOT_PATTERN_PROP_NAME + " value '" +
+                        robotPatternStr + "'.  Robots will not be filtered. ", e);
+            }
+        }
+    }
+    
+    
+    /**
+     * destroy
+     */
+    public void destroy() {}
+    
+}

Added: incubator/roller/trunk/src/org/apache/roller/presentation/filters/RequestFilter.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/presentation/filters/RequestFilter.java?rev=398712&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/presentation/filters/RequestFilter.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/presentation/filters/RequestFilter.java Mon May  1 15:23:02 2006
@@ -0,0 +1,93 @@
+/*
+* 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.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;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.struts.Globals;
+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.presentation.RollerContext;
+import org.apache.roller.presentation.RollerRequest;
+import org.apache.roller.presentation.util.RequestUtil;
+
+
+/**
+ * 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);
+    
+    public void doFilter(
+            ServletRequest req, ServletResponse res, FilterChain chain)
+            throws IOException, ServletException {
+        
+        // NOTE: Setting character encoding and JSTL/Struts locale sync has been
+        // moved to CharEncodingFilter, which is mapped for all URIs in the context.
+        
+        HttpSession session = ((HttpServletRequest)req).getSession();
+        HttpServletRequest request = (HttpServletRequest)req;
+        HttpServletResponse response = (HttpServletResponse)res;
+        Roller roller = RollerFactory.getRoller();
+        RollerRequest rreq = null;
+        try {
+            rreq = RollerRequest.getRollerRequest(
+                       request, mFilterConfig.getServletContext());
+        } catch (Throwable e) {            
+            // NOTE: this is not a page-not-found problem
+            request.setAttribute("DisplayException", e);            
+            mLogger.error(e);
+            return;
+        }
+        chain.doFilter(req, res);
+    }
+    
+    public void init(FilterConfig filterConfig) throws ServletException {
+        mFilterConfig = filterConfig;
+    }
+    
+    public void destroy() {
+    }
+}
+

Added: incubator/roller/trunk/src/org/apache/roller/presentation/filters/SchemeEnforcementFilter.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/presentation/filters/SchemeEnforcementFilter.java?rev=398712&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/presentation/filters/SchemeEnforcementFilter.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/presentation/filters/SchemeEnforcementFilter.java Mon May  1 15:23:02 2006
@@ -0,0 +1,174 @@
+/*
+* 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.
+*/
+/*
+ * SchemeEnforcementFilter.java
+ *
+ * Created on September 16, 2005, 3:17 PM
+ */
+
+package org.apache.roller.presentation.filters;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Iterator;
+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;
+
+
+/**
+ * The SchemeEnforcementFilter is provided for Roller sites that enable secure
+ * logins and want to ensure that only login urls are used under https.
+ *
+ * @author  Allen Gilliland
+ *
+ * @web.filter name="SchemeEnforcementFilter"
+ */
+public class SchemeEnforcementFilter implements Filter {
+    
+    private static Log mLogger = 
+            LogFactory.getLog(SchemeEnforcementFilter.class);
+    
+    private FilterConfig filterConfig = null;
+    
+    private boolean schemeEnforcementEnabled = false;
+    private boolean secureLoginEnabled = false;
+    private int httpPort = 80;
+    private int httpsPort = 443;
+    private String httpsHeaderName = null;
+    private String httpsHeaderValue = null;
+    
+    private Set allowedUrls = new HashSet();
+    
+    
+    /**
+     * Process filter.
+     *
+     * We'll take the incoming request and first determine if this is a
+     * secure request.  If the request is secure then we'll see if it matches
+     * one of the allowed secure urls, if not then we will redirect back out
+     * of https.
+     */
+    public void doFilter(ServletRequest request, ServletResponse response,
+                        FilterChain chain)
+            throws IOException, ServletException {
+        
+        if(this.schemeEnforcementEnabled && this.secureLoginEnabled) {
+            
+            HttpServletRequest req = (HttpServletRequest) request;
+            HttpServletResponse res = (HttpServletResponse) response;
+            
+            mLogger.debug("checking path = "+req.getServletPath());
+            
+            if(!request.isSecure() && allowedUrls.contains(req.getServletPath())) {
+                // http insecure request that should be over https
+                String redirect = "https://"+req.getServerName();
+                
+                if(this.httpsPort != 443)
+                    redirect += ":"+this.httpsPort;
+                
+                redirect += req.getRequestURI();
+                
+                if(req.getQueryString() != null)
+                    redirect += "?"+req.getQueryString();
+                
+                mLogger.debug("Redirecting to "+redirect);
+                res.sendRedirect(redirect);
+                return;
+                
+            } else if(request.isSecure() && !allowedUrls.contains(req.getServletPath())) {
+                // https secure request that should be over http
+                String redirect = "http://"+req.getServerName();
+                
+                if(this.httpPort != 80)
+                    redirect += ":"+this.httpPort;
+                
+                redirect += req.getRequestURI();
+                
+                if(req.getQueryString() != null)
+                    redirect += "?"+req.getQueryString();
+                
+                mLogger.debug("Redirecting to "+redirect);
+                res.sendRedirect(redirect);
+                return;
+            }
+        }
+        
+        chain.doFilter(request, response);
+    }
+    
+    
+    public void destroy() {}
+    
+    
+    /**
+     * Filter init.
+     *
+     * We are just collecting init properties which we'll use for each request.
+     */
+    public void init(FilterConfig filterConfig) {
+        this.filterConfig = filterConfig;
+        
+        // determine if we are doing scheme enforcement
+        this.schemeEnforcementEnabled = 
+                RollerConfig.getBooleanProperty("schemeenforcement.enabled");
+        this.secureLoginEnabled = 
+                RollerConfig.getBooleanProperty("securelogin.enabled");
+        
+        if(this.schemeEnforcementEnabled && this.secureLoginEnabled) {
+            // gather some more properties
+            String http_port = 
+                    RollerConfig.getProperty("securelogin.http.port");
+            String https_port = 
+                    RollerConfig.getProperty("securelogin.https.port");
+            
+            try {
+                this.httpPort = Integer.parseInt(http_port);
+                this.httpsPort = Integer.parseInt(https_port);
+            } catch(NumberFormatException nfe) {
+                // ignored ... guess we'll have to use the defaults
+                mLogger.warn("error with secure login ports", nfe);
+            }
+            
+            // finally, construct our list of allowable https urls
+            String urls = 
+                    RollerConfig.getProperty("schemeenforcement.https.urls");
+            String[] urlsArray = urls.split(",");
+            for(int i=0; i < urlsArray.length; i++)
+                this.allowedUrls.add(urlsArray[i]);
+            
+            // some logging for the curious
+            mLogger.info("Scheme enforcement = enabled");
+            if(mLogger.isDebugEnabled()) {
+                mLogger.debug("allowed urls are:");
+                for(Iterator it = this.allowedUrls.iterator(); it.hasNext();)
+                    mLogger.debug(it.next());
+            }
+        }
+    }
+    
+}

Added: incubator/roller/trunk/src/org/apache/roller/presentation/filters/WeblogPageCacheFilter.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/presentation/filters/WeblogPageCacheFilter.java?rev=398712&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/presentation/filters/WeblogPageCacheFilter.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/presentation/filters/WeblogPageCacheFilter.java Mon May  1 15:23:02 2006
@@ -0,0 +1,460 @@
+/*
+* 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.
+*/
+/*
+ * WeblogPageCacheFilter.java
+ *
+ * Created on October 27, 2005, 12:19 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.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.presentation.util.CacheHttpServletResponseWrapper;
+import org.apache.roller.presentation.util.ResponseContent;
+import org.apache.roller.util.Utilities;
+
+
+/**
+ * A filter used for caching fully rendered weblog pages ... /page/*
+ *
+ * @web.filter name="WeblogPageCacheFilter"
+ *
+ * @author  Allen Gilliland
+ */
+public class WeblogPageCacheFilter implements Filter, CacheHandler {
+    
+    private static Log mLogger = LogFactory.getLog(WeblogPageCacheFilter.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.weblogpage";
+    
+    private boolean excludeOwnerPages = false;
+    private Cache mCache = null;
+    
+    // for metrics
+    private double hits = 0;
+    private double misses = 0;
+    private double purges = 0;
+    private double skips = 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;
+        
+        WeblogPageRequest pageRequest = null;
+        try {
+            pageRequest = new WeblogPageRequest(request);
+        } catch(Exception e) {
+            // some kind of error parsing the request
+            mLogger.error("error creating page request", e);
+            response.sendError(HttpServletResponse.SC_NOT_FOUND);
+            return;
+        }
+        
+        String key = this.CACHE_ID+":"+this.generateKey(pageRequest);
+        
+        try {
+            ResponseContent respContent = null;
+            if(!this.excludeOwnerPages || !pageRequest.isLoggedIn()) {
+                // 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) {
+                    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();
+                
+                // 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();
+                    
+                    // only cache if this is not a logged in user?
+                    if (!this.excludeOwnerPages || !pageRequest.isLoggedIn()) {
+                        if (rc != null && rc.getSize() > 0) {
+                            this.mCache.put(key, new LazyExpiringCacheEntry(rc));
+                        } else {
+                            mLogger.debug("Not caching zero length content for key: " + key);
+                        }
+                    } else {
+                        mLogger.debug("SKIPPED "+key);
+                        this.skips++;
+                    }
+                } 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 page "+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 page request.
+     * This generates a key of the form ...
+     *
+     * weblog/<handle>/page/<type>[/anchor]/<weblogPage>/<language>[/user]
+     *   or
+     * weblog/<handle>/page/<type>[/date][/category]/<weblogPage>/<language>[/user]
+     *
+     *
+     * examples ...
+     *
+     * weblog/foo/page/main/Weblog/en
+     * weblog/foo/page/permalink/entry_anchor/Weblog/en
+     * weblog/foo/page/archive/20051110/Weblog/en
+     * weblog/foo/page/archive/MyCategory/Weblog/en/user=myname
+     *
+     */
+    private String generateKey(WeblogPageRequest pageRequest) {
+        
+        StringBuffer key = new StringBuffer();
+        key.append("weblog/");
+        key.append(pageRequest.getWeblogHandle().toLowerCase());
+        key.append("/page/");
+        key.append(pageRequest.getPageType());
+        
+        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 page name
+        key.append("/").append(pageRequest.getWeblogPage());
+        
+        // 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 synchronized 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 archive pages
+        
+        /*
+        String handle = entry.getWebsite().getHandle();
+        String keyBase = "pageCache:weblog/"+handle+"/page";
+        
+        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;
+        
+        synchronized(mCache) {
+            Iterator allKeys = this.mCache.keySet().iterator();
+            while(allKeys.hasNext()) {
+                key = (String) allKeys.next();
+                if(key.startsWith(keyBase+"/main")) {
+                    removeSet.add(key);
+                } else if(key.startsWith(keyBase+"/archive")) {
+                    // at some point it would be cool to actually calculate what
+                    // archive pages to remove in specific, rather than all of 'em
+                    removeSet.add(key);
+                } else if(key.startsWith(keyBase+"/permalink/"+entry.getAnchor())) {
+                    removeSet.add(key);
+                }
+            }
+        }
+
+        this.mCache.remove(removeSet);
+        this.purges += removeSet.size();
+        */
+        
+        this.invalidate(entry.getWebsite());
+    }
+    
+    
+    /**
+     * A weblog has changed.
+     */
+    public synchronized 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;
+        
+        synchronized(mCache) {
+            Iterator allKeys = this.mCache.keySet().iterator();
+            while(allKeys.hasNext()) {
+                key = (String) allKeys.next();
+                
+                if(key.startsWith("pageCache:weblog/"+website.getHandle())) {
+                    removeSet.add(key);
+                }
+            }
+        }
+        
+        this.mCache.remove(removeSet);
+        this.purges += removeSet.size();
+        */
+    }
+    
+    
+    /**
+     * 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 reference user objects
+    }
+    
+    
+    /**
+     * 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());
+    }
+    
+    
+    /**
+     * 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;
+        this.skips = 0;
+    }
+    
+    
+    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));
+        stats.put("skips", new Double(this.skips));
+        
+        // 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 weblog page cache");
+
+        this.excludeOwnerPages = 
+                RollerConfig.getBooleanProperty("cache.weblogpage.excludeOwnerEditPages");
+        
+        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/package.html
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/presentation/filters/package.html?rev=398712&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/presentation/filters/package.html (added)
+++ incubator/roller/trunk/src/org/apache/roller/presentation/filters/package.html Mon May  1 15:23:02 2006
@@ -0,0 +1,27 @@
+<!--
+  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.
+-->
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+  <title></title>
+</head>
+<body>
+Servlet filters for persistence, conditional get, compression, referers.
+
+</body>
+</html>

Added: incubator/roller/trunk/src/org/apache/roller/presentation/newsfeeds/NewsfeedCache.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/presentation/newsfeeds/NewsfeedCache.java?rev=398712&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/presentation/newsfeeds/NewsfeedCache.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/presentation/newsfeeds/NewsfeedCache.java Mon May  1 15:23:02 2006
@@ -0,0 +1,18 @@
+/*
+* 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.newsfeeds;

import java.io.InputStreamReader;
import java.net.URL;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.roller.util.LRUCache2;

import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.io.SyndFeedInput;
import java.io.UnsupportedEncodingException;
import java.net.URLConnection;
import org.apache.roller.config.RollerConfig;

/**
 * Returns parsed RSS feed by pulling one from a cache or by retrieving and
 * parging the specified feed using the Flock RSS parser.
 * <br />
 * TODO: use PlanetRoller to implement NewsfeedCache instead.
 * <br />
 * @author Lance Lavandowska
 * @author Dave Johnson
 */
public class NewsfeedCache
{
    private static Log mLogger = LogFactory.getFactory().getInstance(
            NewsfeedCache.class);

    /** Static singleton * */
    private static NewsfeedCache mInstance = null;

    /** Instance vars * */
    private boolean 
 aggregator_enabled = true;
    private boolean aggregator_cache_enabled = true;
    private int aggregator_cache_timeout = 14400;

    /** LRU cache */
    LRUCache2 mCache = null;

    /** Constructor */
    private NewsfeedCache()
    {
        // lookup the props we need
        String enabled = RollerConfig.getProperty("aggregator.enabled");
        String usecache = RollerConfig.getProperty("aggregator.cache.enabled");
        String cachetime = RollerConfig.getProperty("aggregator.cache.timeout");
        
        if("true".equalsIgnoreCase(enabled))
            this.aggregator_enabled = true;
        
        if("true".equalsIgnoreCase(usecache))
            this.aggregator_cache_enabled = true;
        
        try {
            this.aggregator_cache_timeout = Integer.parseInt(cachetime);
        } catch(Exception e) { mLogger.warn(e); }
        
        // finally ... create the cache
        this.mCache = new LRUCache2(100, 1000 * this.aggregator_cache_timeout);
  
   }

    /** static singleton retriever */
    public static NewsfeedCache getInstance()
    {
        synchronized (NewsfeedCache.class)
        {
            if (mInstance == null)
            {
                if (mLogger.isDebugEnabled())
                {
                    mLogger.debug("Instantiating new NewsfeedCache");
                }
                mInstance = new NewsfeedCache();
            }
        }
        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 SyndFeed getChannel(String feedUrl)
    {
        SyndFeed feed = null;
        try
        {
            // If aggregator has been disable return null
            if (!aggregator_enabled)
            {
                return null;
            }

            if (aggregator_cache_enabled)
            {
             
    if (mLogger.isDebugEnabled())
                {
                    mLogger.debug("Newsfeed: use Cache for " + feedUrl);
                }

                // Get pre-parsed feed from the cache
                feed = (SyndFeed) mCache.get(feedUrl);
                if (mLogger.isDebugEnabled())
                {
                    mLogger.debug("Newsfeed: got from Cache");
                }

                if (feed == null)
                {
                    try
                    {
                        // Parse the feed
                        SyndFeedInput feedInput = new SyndFeedInput();
                        feed = feedInput.build(new InputStreamReader(
                                new URL(feedUrl).openStream()));
                    }
                    catch (Exception e1)
                    {
                        mLogger.info("Error parsing RSS: " + feedUrl);
                    }
                }
                // Store parsed feed in the cache
 
                mCache.put(feedUrl, feed);
                mLogger.debug("Newsfeed: not in Cache");
            }
            else
            {
                if (mLogger.isDebugEnabled())
                {
                    mLogger.debug("Newsfeed: not using Cache for " + feedUrl);
                }
                try
                {
                        // charset fix from Jason Rumney (see ROL-766)
                        URLConnection connection = new URL(feedUrl).openConnection();
                        connection.connect();
                        String contentType = connection.getContentType();
                        // Default charset to UTF-8, since we are expecting XML
                        String charset = "UTF8";
                        if (contentType != null) {
                            int charsetStart = contentType.indexOf("charset=");
                            if (charsetStart >= 0) {
                                int charsetEnd = conte
 ntType.indexOf(";", charsetStart);
                                if (charsetEnd == -1) charsetEnd = contentType.length();
                                charsetStart += "charset=".length();
                                charset = contentType.substring(charsetStart, charsetEnd);
                                // Check that charset is recognized by Java
                                try {
                                    byte[] test = "test".getBytes(charset);
                                }
                                catch (UnsupportedEncodingException codingEx) {
                                    // default to UTF-8
                                    charset = "UTF8";
                                }
                            }
                        } 
                        // Parse the feed
                        SyndFeedInput feedInput = new SyndFeedInput();
                        feed = feedInput.build(new InputStreamReader(
                 
        connection.getInputStream(), charset)); 
                }
                catch (Exception e1)
                {
                    mLogger.info("Error parsing RSS: " + feedUrl);
                }
            }
        }
        catch (Exception ioe)
        {
            if (mLogger.isDebugEnabled())
            {
                mLogger.debug("Newsfeed: Unexpected exception", ioe);
            }
        }
        return feed;
    }
}
\ No newline at end of file

Added: incubator/roller/trunk/src/org/apache/roller/presentation/newsfeeds/package.html
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/presentation/newsfeeds/package.html?rev=398712&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/presentation/newsfeeds/package.html (added)
+++ incubator/roller/trunk/src/org/apache/roller/presentation/newsfeeds/package.html Mon May  1 15:23:02 2006
@@ -0,0 +1,27 @@
+<!--
+  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.
+-->
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+  <title></title>
+</head>
+<body>
+Cache used by deprecated #showNewsfeed() macros.
+
+</body>
+</html>

Added: incubator/roller/trunk/src/org/apache/roller/presentation/package.html
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/presentation/package.html?rev=398712&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/presentation/package.html (added)
+++ incubator/roller/trunk/src/org/apache/roller/presentation/package.html Mon May  1 15:23:02 2006
@@ -0,0 +1,29 @@
+<!--
+  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.
+-->
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+  <title></title>
+</head>
+<body>
+Top-level presentation layer classes: context, session, request, etc.
+<p />
+<img src="roller-ui.png" alt="diagram of Roller UI packages" />
+
+</body>
+</html>

Added: incubator/roller/trunk/src/org/apache/roller/presentation/pings/PingQueueProcessor.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/presentation/pings/PingQueueProcessor.java?rev=398712&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/presentation/pings/PingQueueProcessor.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/presentation/pings/PingQueueProcessor.java Mon May  1 15:23:02 2006
@@ -0,0 +1,202 @@
+/*
+ * 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.pings;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.roller.RollerException;
+import org.apache.roller.config.PingConfig;
+import org.apache.roller.model.PingQueueManager;
+import org.apache.roller.model.RollerFactory;
+import org.apache.roller.pojos.PingQueueEntryData;
+import org.apache.roller.pojos.PingTargetData;
+import org.apache.roller.pojos.WebsiteData;
+import org.apache.roller.presentation.RollerContext;
+
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Ping Queue Processor.  Singleton encapsulating logic for processing the weblog update ping queue.
+ *
+ * @author <a href="mailto:anil@busybuddha.org">Anil Gangolli</a>
+ */
+public class PingQueueProcessor
+{
+    private static final Log logger = LogFactory.getLog(PingQueueProcessor.class);
+
+    private static PingQueueProcessor theInstance;
+
+
+    private RollerContext rollerContext;
+    private PingQueueManager pingQueueMgr;
+
+    public static PingQueueProcessor getInstance()
+    {
+        return theInstance;
+    }
+
+    private PingQueueProcessor(RollerContext rc) throws RollerException
+    {
+        rollerContext = rc;
+        pingQueueMgr = RollerFactory.getRoller().getPingQueueManager();
+    }
+
+    /**
+     * Initialize the singleton.  This is called during <code>RollerContext</code> initialization.
+     *
+     * @param rc the Roller context
+     * @throws RollerException
+     */
+    public static synchronized void init(RollerContext rc) throws RollerException
+    {
+        if (theInstance != null)
+        {
+            logger.warn("Ignoring duplicate initialization of PingQueueProcessor!");
+            return;
+        }
+        theInstance = new PingQueueProcessor(rc);
+        if (logger.isDebugEnabled()) logger.debug("Ping queue processor initialized.");
+    }
+
+    /**
+     * Process the ping queue.  Performs one pass through the ping queue, processing every entry once.  On ping failure
+     * an entry is requeued for processing on subsequent passes until the configured maximum number of attempts is
+     * reached.
+     */
+    public synchronized void processQueue()
+    {
+        if (PingConfig.getSuspendPingProcessing()) {
+            logger.info("Ping processing has been suspended.  Skipping current round of ping queue processing.");
+            return;
+        }
+
+        String absoluteContextUrl = rollerContext.getAbsoluteContextUrl();
+        if (absoluteContextUrl == null)
+        {
+            logger.warn("WARNING: Skipping current ping queue processing round because we cannot yet determine the site's absolute context url.");
+            return;
+        }
+
+        // TODO: Group by ping target and ping all sites for that target?
+        // We're currently not taking advantage of grouping by ping target site and then sending
+        // all of the pings for that target at once.  If it becomes an efficiency issue, we should do
+        // that.
+
+        try
+        {
+            if (logger.isDebugEnabled()) logger.debug("Started processing ping queue.");
+            // Get all of the entries
+            List entries = pingQueueMgr.getAllQueueEntries();
+
+            // Process each entry
+            for (Iterator i = entries.iterator(); i.hasNext();)
+            {
+                PingQueueEntryData pingQueueEntry = (PingQueueEntryData) i.next();
+                processQueueEntry(absoluteContextUrl, pingQueueEntry);
+            }
+            if (logger.isDebugEnabled()) logger.debug("Finished processing ping queue.");
+        }
+        catch (Exception ex)
+        {
+            logger.error("Unexpected exception processing ping queue!  Aborting this pass of ping queue processing.", ex);
+        }
+    }
+
+    /**
+     * Process an individual ping queue entry.
+     *
+     * @param absoluteContextUrl absolute context URL of the Roller site
+     * @param pingQueueEntry     the ping queue entry
+     * @throws RollerException only if there are problems processing the queue.  Exceptions from sending pings are
+     *                         handled, not thrown.
+     */
+    private void processQueueEntry(String absoluteContextUrl, PingQueueEntryData pingQueueEntry)
+        throws RollerException
+    {
+        if (logger.isDebugEnabled()) logger.debug("Processing ping queue entry: " + pingQueueEntry);
+
+        PingTargetData pingTarget = pingQueueEntry.getPingTarget();
+        WebsiteData website = pingQueueEntry.getWebsite();
+        boolean pingSucceeded = false;
+        if (PingConfig.getLogPingsOnly())
+        {
+            // Just log the ping and pretend it succeeded.
+            logger.info("Logging simulated ping for ping queue entry " + pingQueueEntry);
+            pingSucceeded = true;
+        }
+        else
+        {
+            // Actually process the ping
+            try
+            {
+                // Send the ping
+                WeblogUpdatePinger.sendPing(absoluteContextUrl, pingTarget, website);
+                // Consider successful ping transmission if we didn't get an exception.  We don't care here
+                // about the result of the ping if it was transmitted.
+                pingSucceeded = true;
+            }
+            catch (Exception ex)
+            {
+                // Handle the ping error, either removing or requeuing the ping queue entry.
+                handlePingError(pingQueueEntry, ex);
+            }
+        }
+        // We do this outside of the previous try-catch because we don't want an exception here to be considered a ping error.
+        if (pingSucceeded)
+        {
+            if (logger.isDebugEnabled()) logger.debug("Processed ping: " + pingQueueEntry);
+            pingQueueMgr.removeQueueEntry(pingQueueEntry);
+        }
+    }
+
+    /**
+     * Handle any ping error.
+     *
+     * @param pingQueueEntry the ping queue entry
+     * @param ex             the exception that occurred on the ping attempt
+     * @throws RollerException
+     */
+    private void handlePingError(PingQueueEntryData pingQueueEntry, Exception ex)
+        throws RollerException
+    {
+        if ((pingQueueEntry.incrementAttempts() < PingConfig.getMaxPingAttempts()) &&
+            WeblogUpdatePinger.shouldRetry(ex))
+        {
+            // We have attempts remaining, and it looks like we should retry,
+            // so requeue the entry for processing on subsequent rounds
+            logger.warn("Error on ping attempt (" + pingQueueEntry.getAttempts() + ") for " + pingQueueEntry +
+                ": [" + ex.getMessage() + "]. Will re-queue for later attempts.");
+            if (logger.isDebugEnabled()) logger.debug("Error on last ping attempt was: ", ex);
+            pingQueueMgr.saveQueueEntry(pingQueueEntry);
+        }
+        else
+        {
+            // Remove the entry
+            logger.warn("Error on ping attempt (" + pingQueueEntry.getAttempts() + ") for " + pingQueueEntry +
+                ": [" + ex.getMessage() + "].  Entry will be REMOVED from ping queue.");
+            if (logger.isDebugEnabled()) logger.debug("Error on last ping attempt was: ", ex);
+            pingQueueMgr.removeQueueEntry(pingQueueEntry);
+            // TODO: mark ping target invalid?
+        }
+    }
+
+
+}

Added: incubator/roller/trunk/src/org/apache/roller/presentation/pings/PingQueueTask.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/presentation/pings/PingQueueTask.java?rev=398712&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/presentation/pings/PingQueueTask.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/presentation/pings/PingQueueTask.java Mon May  1 15:23:02 2006
@@ -0,0 +1,89 @@
+/*
+ * 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.pings;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.roller.RollerException;
+import org.apache.roller.model.Roller;
+import org.apache.roller.model.RollerFactory;
+import org.apache.roller.presentation.RollerContext;
+
+import java.util.TimerTask;
+
+/**
+ * Task for processing the ping queue at fixed intervals.   This is set up during context initialization by {@link
+ * RollerContext}.  The queue processing interval is currently set from the configuration {@link
+ * org.apache.roller.config.PingConfig} at startup time only.
+ *
+ * @author <a href="mailto:anil@busybuddha.org">Anil Gangolli</a>
+ */
+public class PingQueueTask extends TimerTask
+{
+    private static final Log logger = LogFactory.getLog(PingQueueTask.class);
+
+    //  The periodic interval (in minutes) at which we are configured to run
+    long intervalMins;
+
+    /**
+     * Initialize the task.
+     *
+     * @param rc the Roller context.
+     * @throws RollerException
+     */
+    public void init(RollerContext rc, long intervalMins) throws RollerException
+    {
+        PingQueueProcessor.init(rc);
+        this.intervalMins = intervalMins;
+    }
+
+    /**
+     * Get the task's configured interval (in minutes).
+     *
+     * @return the tasks configured interval (in minutes).
+     */
+    public long getIntervalMins()
+    {
+        return intervalMins;
+    }
+
+    /**
+     * Run the task once.
+     */
+    public void run()
+    {
+        // Call the ping queue processor to process the queue
+        Roller roller = null;
+        try
+        {
+            roller = RollerFactory.getRoller();
+            PingQueueProcessor.getInstance().processQueue();
+            roller.flush();
+        }
+        catch (RollerException e)
+        {
+            // This is probably duplicate logging. May want to eliminate it, but should be rare.
+            logger.error("Error while processing ping queuer", e);
+        }
+        finally
+        {
+            if (roller != null) roller.release();
+        }
+    }
+}