You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@roller.apache.org by ag...@apache.org on 2005/11/28 20:44:39 UTC
svn commit: r349478 [1/2] - in /incubator/roller/trunk:
src/org/roller/presentation/ src/org/roller/presentation/cache/
src/org/roller/presentation/filters/ src/org/roller/presentation/pagecache/
src/org/roller/presentation/pagecache/rollercache/ src/o...
Author: agilliland
Date: Mon Nov 28 11:44:28 2005
New Revision: 349478
URL: http://svn.apache.org/viewcvs?rev=349478&view=rev
Log:
new caching code.
- new org.roller.presentation.cache package with new caching implementations
- 4 new caching filters in org.roller.presentation.filters
- a new lightweight ParsedRequest object with 2 subclasses
- deleted old caching code except for a few classes which moved to org.roller.presentation.util
- updated roller.properties with new cache controlling properties
Added:
incubator/roller/trunk/src/org/roller/presentation/ParsedRequest.java
incubator/roller/trunk/src/org/roller/presentation/WeblogFeedRequest.java
incubator/roller/trunk/src/org/roller/presentation/WeblogPageRequest.java
incubator/roller/trunk/src/org/roller/presentation/cache/
incubator/roller/trunk/src/org/roller/presentation/cache/Cache.java
incubator/roller/trunk/src/org/roller/presentation/cache/CacheFactory.java
incubator/roller/trunk/src/org/roller/presentation/cache/CacheHandler.java
incubator/roller/trunk/src/org/roller/presentation/cache/CacheManager.java
incubator/roller/trunk/src/org/roller/presentation/cache/ExpiringCacheEntry.java
incubator/roller/trunk/src/org/roller/presentation/cache/ExpiringLRUCacheFactoryImpl.java
incubator/roller/trunk/src/org/roller/presentation/cache/ExpiringLRUCacheImpl.java
incubator/roller/trunk/src/org/roller/presentation/cache/LRUCacheFactoryImpl.java
incubator/roller/trunk/src/org/roller/presentation/cache/LRUCacheImpl.java
incubator/roller/trunk/src/org/roller/presentation/filters/FeedCacheFilter.java
incubator/roller/trunk/src/org/roller/presentation/filters/IfModifiedFeedCacheFilter.java
incubator/roller/trunk/src/org/roller/presentation/filters/MainPageCacheFilter.java
incubator/roller/trunk/src/org/roller/presentation/filters/WeblogPageCacheFilter.java
incubator/roller/trunk/src/org/roller/presentation/util/CacheHttpServletResponseWrapper.java
- copied, changed from r349452, incubator/roller/trunk/src/org/roller/presentation/pagecache/rollercache/CacheHttpServletResponseWrapper.java
incubator/roller/trunk/src/org/roller/presentation/util/ResponseContent.java
- copied, changed from r349452, incubator/roller/trunk/src/org/roller/presentation/pagecache/rollercache/ResponseContent.java
incubator/roller/trunk/src/org/roller/presentation/util/SplitPrintWriter.java
- copied, changed from r349452, incubator/roller/trunk/src/org/roller/presentation/pagecache/rollercache/SplitPrintWriter.java
incubator/roller/trunk/src/org/roller/presentation/util/SplitServletOutputStream.java
- copied, changed from r349452, incubator/roller/trunk/src/org/roller/presentation/pagecache/rollercache/SplitServletOutputStream.java
Removed:
incubator/roller/trunk/src/org/roller/presentation/pagecache/FilterHandler.java
incubator/roller/trunk/src/org/roller/presentation/pagecache/PageCacheFilter.java
incubator/roller/trunk/src/org/roller/presentation/pagecache/package.html
incubator/roller/trunk/src/org/roller/presentation/pagecache/rollercache/CacheHttpServletResponseWrapper.java
incubator/roller/trunk/src/org/roller/presentation/pagecache/rollercache/LRUCacheHandler2.java
incubator/roller/trunk/src/org/roller/presentation/pagecache/rollercache/ResponseContent.java
incubator/roller/trunk/src/org/roller/presentation/pagecache/rollercache/SplitPrintWriter.java
incubator/roller/trunk/src/org/roller/presentation/pagecache/rollercache/SplitServletOutputStream.java
incubator/roller/trunk/src/org/roller/presentation/pagecache/rollercache/package.html
Modified:
incubator/roller/trunk/web/WEB-INF/classes/roller.properties
Added: incubator/roller/trunk/src/org/roller/presentation/ParsedRequest.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/ParsedRequest.java?rev=349478&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/ParsedRequest.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/ParsedRequest.java Mon Nov 28 11:44:28 2005
@@ -0,0 +1,84 @@
+/*
+ * WeblogRequest.java
+ *
+ * Created on November 7, 2005, 12:29 PM
+ */
+
+package org.roller.presentation;
+
+import java.util.Locale;
+import javax.servlet.http.HttpServletRequest;
+
+
+/**
+ * An abstract class representing any request made to Roller that has been
+ * parsed in order to extract relevant pieces of information from the url.
+ *
+ * NOTE: It is extremely important to mention that this class and all of its
+ * subclasses are meant to be extremely light weight. Meaning they should
+ * avoid any time consuming operations at all costs, especially operations
+ * which require a trip to the db. Those operations should be used very, very
+ * sparingly and should only be triggered when it's guaranteed that they are
+ * needed.
+ *
+ * @author Allen Gilliland
+ */
+public abstract class ParsedRequest {
+
+ HttpServletRequest request = null;
+
+ private String language = null;
+ private String authenticUser = null;
+
+
+ ParsedRequest() {}
+
+
+ /**
+ * Parse the given http request and extract any information we can.
+ *
+ * This abstract version of the constructor gathers info likely to be
+ * relevant to all requests to Roller.
+ */
+ public ParsedRequest(HttpServletRequest request) throws Exception {
+
+ // keep a reference to the original request
+ this.request = request;
+
+ // login status
+ java.security.Principal prince = request.getUserPrincipal();
+ if(prince != null) {
+ this.authenticUser = prince.getName();
+ }
+
+ }
+
+
+ public String getLanguage() {
+ if(this.language == null) {
+ // determine language
+ // this operation can possibly require a trip to the db, so we only
+ // do it when we know our user needs to know the language
+ Locale locale = LanguageUtil.getViewLocale(request);
+ this.language = locale.getLanguage();
+ }
+
+ return this.language;
+ }
+
+
+ public String getAuthenticUser() {
+ return this.authenticUser;
+ }
+
+
+ public boolean isLoggedIn() {
+ return (this.authenticUser != null);
+ }
+
+
+ public HttpServletRequest getRequest() {
+ return this.request;
+ }
+
+}
Added: incubator/roller/trunk/src/org/roller/presentation/WeblogFeedRequest.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/WeblogFeedRequest.java?rev=349478&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/WeblogFeedRequest.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/WeblogFeedRequest.java Mon Nov 28 11:44:28 2005
@@ -0,0 +1,162 @@
+/*
+ * WeblogFeedRequest.java
+ *
+ * Created on November 7, 2005, 1:59 PM
+ */
+
+package org.roller.presentation;
+
+import java.util.HashSet;
+import java.util.Set;
+import javax.servlet.http.HttpServletRequest;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.roller.RollerException;
+import org.roller.pojos.WeblogTemplate;
+
+
+/**
+ * Represents a request for a Roller weblog feed.
+ *
+ * any of /rss/*, /atom/*, /flavor/*, or /planetrss
+ *
+ * We use this class as a helper to parse an incoming url and sort out the
+ * information embedded in the url for later use.
+ *
+ * @author Allen Gilliland
+ */
+public class WeblogFeedRequest extends ParsedRequest {
+
+ private static Log mLogger = LogFactory.getLog(WeblogFeedRequest.class);
+
+ private static Set feedServlets = new HashSet();
+
+ private String context = null;
+ private String flavor = null;
+ private String weblogHandle = null;
+ private String weblogCategory = null;
+ private boolean excerpts = false;
+
+
+ static {
+ // initialize our servlet list
+ feedServlets.add("rss");
+ feedServlets.add("flavor");
+ feedServlets.add("atom");
+ feedServlets.add("planetrss");
+ }
+
+
+ /**
+ * Construct the WeblogFeedRequest by parsing the incoming url
+ */
+ public WeblogFeedRequest(HttpServletRequest request) throws Exception {
+
+ super(request);
+
+ // parse the request object and figure out what we've got
+ mLogger.debug("parsing url "+request.getRequestURL());
+
+ String servlet = request.getServletPath();
+ String pathInfo = request.getPathInfo();
+
+ // what servlet is our destination?
+ if(servlet != null) {
+ // strip off the leading slash
+ servlet = servlet.substring(1);
+
+ if(feedServlets.contains(servlet)) {
+ this.context = "weblog";
+ this.flavor = servlet;
+ } else {
+ // not a request to a feed servlet
+ throw new RollerException("not a weblog feed request, "+request.getRequestURL());
+ }
+ }
+
+ // parse the path info
+ if(pathInfo != null && pathInfo.trim().length() > 1) {
+ // strip off the leading slash
+ pathInfo = pathInfo.substring(1);
+ String[] pathElements = pathInfo.split("/");
+
+ if(pathElements[0].length() > 0) {
+ this.weblogHandle = pathElements[0];
+ }
+
+ } else {
+
+ // no path info means this was a non-weblog request
+ // we handle a few exceptions for this which include
+ // /rss - main rss feed
+ // /planetrss - main planet rss feed
+ // /atom - main atom feed
+ // /flavor - main flavor feed
+ if(servlet.equals("rss") || servlet.equals("atom") ||
+ servlet.equals("flavor")) {
+
+ this.context = "main";
+ } else if(servlet.equals("planetrss")) {
+
+ this.context = "planet";
+ }
+ }
+
+ /*
+ * parse request parameters
+ *
+ * the only params we currently care about are:
+ * flavor - defines the feed type
+ * catname - specifies a weblog category
+ * path - specifies a weblog category
+ * excerpts - specifies the feed should only include excerpts
+ *
+ */
+ if(request.getParameter("flavor") != null) {
+ this.flavor = request.getParameter("flavor");
+ }
+
+ if(request.getParameter("path") != null) {
+ this.weblogCategory = request.getParameter("path");
+ }
+
+ if(request.getParameter("catname") != null) {
+ this.weblogCategory = request.getParameter("catname");
+ }
+
+ if(request.getParameter("excerpts") != null) {
+ this.excerpts = Boolean.valueOf(request.getParameter("excerpts")).booleanValue();
+ }
+
+ // one small final adjustment.
+ // if our flavor is "flavor" then that means someone is just getting
+ // the default flavor, which is rss, so let's set that
+ if(this.flavor.equals("flavor")) {
+ this.flavor = "rss";
+ }
+
+ }
+
+
+ public String getContext() {
+ return context;
+ }
+
+ public String getFlavor() {
+ return flavor;
+ }
+
+ public String getWeblogHandle() {
+ return weblogHandle;
+ }
+
+ public String getWeblogCategory() {
+ return weblogCategory;
+ }
+
+ public boolean isExcerpts() {
+ return excerpts;
+ }
+
+}
Added: incubator/roller/trunk/src/org/roller/presentation/WeblogPageRequest.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/WeblogPageRequest.java?rev=349478&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/WeblogPageRequest.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/WeblogPageRequest.java Mon Nov 28 11:44:28 2005
@@ -0,0 +1,205 @@
+/*
+ * WeblogRequest.java
+ *
+ * Created on November 5, 2005, 12:05 PM
+ */
+
+package org.roller.presentation;
+
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+import javax.servlet.http.HttpServletRequest;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.roller.RollerException;
+import org.roller.pojos.WeblogTemplate;
+import org.roller.util.Utilities;
+
+
+/**
+ * Represents a request for a Roller weblog page.
+ *
+ * any url from ... /page/*
+ *
+ * We use this class as a helper to parse an incoming url and sort out the
+ * information embedded in the url for later use.
+ *
+ * @author Allen Gilliland
+ */
+public class WeblogPageRequest extends ParsedRequest {
+
+ private static Log mLogger = LogFactory.getLog(WeblogPageRequest.class);
+
+ // various page types
+ public static final String MAIN = "main";
+ public static final String PERMALINK = "permalink";
+ public static final String ARCHIVE = "archive";
+
+ private String context = null;
+ private String pageType = null;
+ private String weblogHandle = null;
+ private String weblogAnchor = null;
+ private String weblogPage = null;
+ private String weblogCategory = null;
+ private String weblogDate = null;
+
+
+ /**
+ * Construct the WeblogPageRequest by parsing the incoming url
+ */
+ public WeblogPageRequest(HttpServletRequest request) throws Exception {
+
+ // let our parent take care of their business first
+ super(request);
+
+ // parse the request object and figure out what we've got
+ mLogger.debug("parsing url "+request.getRequestURL());
+
+ String servlet = request.getServletPath();
+ String pathInfo = request.getPathInfo();
+
+ // make sure this request was destined for the page servlet
+ if(servlet != null) {
+ // strip off the leading slash
+ servlet = servlet.substring(1);
+
+ if("page".equals(servlet)) {
+ this.context = "weblog";
+ } else {
+ // not a request to the page servlet
+ throw new RollerException("not a weblog page request, "+request.getRequestURL());
+ }
+ }
+
+ /*
+ * parse path info
+ *
+ * we expect one of the following forms of urls ...
+ *
+ * [handle] - get default page for user for today's date
+ * [handle]/[date] - get default page for user for specified date
+ * [handle]/[pagelink] - get specified page for today's date
+ * [handle]/[pagelink]/[date] - get specified page for specified date
+ * [handle]/[pagelink]/[anchor] - get specified page & entry (by anchor)
+ * [handle]/[pagelink]/[date]/[anchor] - get specified page & entry (by anchor)
+ */
+ if(pathInfo != null && pathInfo.trim().length() > 1) {
+ // strip off the leading slash
+ pathInfo = pathInfo.substring(1);
+ String[] pathElements = pathInfo.split("/");
+
+ if ( pathElements.length == 1 ) {
+
+ // /handle
+ this.weblogHandle = pathElements[0];
+ this.weblogPage = WeblogTemplate.DEFAULT_PAGE;
+ this.pageType = MAIN;
+
+ } else if ( pathElements.length == 2 ) {
+
+ // /handle/date or /handle/page
+ this.weblogHandle = pathElements[0];
+ this.weblogPage = WeblogTemplate.DEFAULT_PAGE;
+
+ if(this.isValidDateString(pathElements[1])) {
+ this.weblogDate = pathElements[1];
+ this.pageType = ARCHIVE;
+ } else {
+ this.weblogPage = pathElements[1];
+ this.pageType = MAIN;
+ }
+
+ } else if ( pathElements.length == 3 ) {
+
+ // /handle/page/date or /handle/page/anchor
+ this.weblogHandle = pathElements[0];
+ this.weblogPage = pathElements[1];
+
+ if(this.isValidDateString(pathElements[2])) {
+ this.weblogDate = pathElements[2];
+ this.pageType = ARCHIVE;
+ } else {
+ this.weblogAnchor = pathElements[2];
+ this.pageType = PERMALINK;
+ }
+
+ } else if ( pathElements.length == 4 ) {
+
+ // /handle/page/date/anchor
+ this.weblogHandle = pathElements[0];
+ this.weblogPage = pathElements[1];
+ this.weblogDate = pathElements[2];
+ this.weblogAnchor = pathElements[3];
+ this.pageType = PERMALINK;
+ }
+
+ } else {
+ // invalid request ... path info is empty
+ throw new RollerException("not a weblog page request, "+request.getRequestURL());
+ }
+
+
+ /*
+ * parse request parameters
+ *
+ * the only params we currently care about are:
+ * anchor - specifies a weblog entry
+ * entry - specifies a weblog entry
+ * catname - specifies a weblog category
+ */
+ if(request.getParameter("anchor") != null) {
+ this.weblogAnchor = request.getParameter("anchor");
+ this.pageType = PERMALINK;
+ }
+
+ if(request.getParameter("entry") != null) {
+ this.weblogAnchor = request.getParameter("entry");
+ this.pageType = PERMALINK;
+ }
+
+ if(request.getParameter("catname") != null) {
+ String cat = request.getParameter("catname");
+
+ this.weblogCategory = cat;
+ this.pageType = ARCHIVE;
+ }
+
+ }
+
+
+ private boolean isValidDateString(String dateString) {
+ return (dateString != null && dateString.length() > 3 && StringUtils.isNumeric(dateString));
+ }
+
+ public String getContext() {
+ return context;
+ }
+
+ public String getWeblogHandle() {
+ return weblogHandle;
+ }
+
+ public String getWeblogAnchor() {
+ return weblogAnchor;
+ }
+
+ public String getWeblogPage() {
+ return weblogPage;
+ }
+
+ public String getWeblogCategory() {
+ return weblogCategory;
+ }
+
+ public String getWeblogDate() {
+ return weblogDate;
+ }
+
+ public String getPageType() {
+ return pageType;
+ }
+
+}
Added: incubator/roller/trunk/src/org/roller/presentation/cache/Cache.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/cache/Cache.java?rev=349478&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/cache/Cache.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/cache/Cache.java Mon Nov 28 11:44:28 2005
@@ -0,0 +1,61 @@
+/*
+ * RollerCache.java
+ *
+ * Created on September 18, 2005, 10:59 AM
+ */
+
+package org.roller.presentation.cache;
+
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * Base interface representing a presentation cache in Roller.
+ *
+ * @author Allen Gilliland
+ */
+public interface Cache {
+
+ /**
+ * put an item in the cache.
+ */
+ public void put(String key, Object value);
+
+
+ /**
+ * get an item from the cache.
+ */
+ public Object get(String key);
+
+
+ /**
+ * remove an item from the cache.
+ */
+ public void remove(String key);
+
+
+ /**
+ * remove a set of items from the cache.
+ */
+ public void remove(Set keys);
+
+
+ /**
+ * clear the entire cache.
+ */
+ public void clear();
+
+
+ /**
+ * get a list of keys used in the cache.
+ */
+ public Set keySet();
+
+
+ /**
+ * get cache stats.
+ */
+ public Map stats();
+
+}
Added: incubator/roller/trunk/src/org/roller/presentation/cache/CacheFactory.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/cache/CacheFactory.java?rev=349478&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/cache/CacheFactory.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/cache/CacheFactory.java Mon Nov 28 11:44:28 2005
@@ -0,0 +1,25 @@
+/*
+ * CacheFactory.java
+ *
+ * Created on October 26, 2005, 3:25 PM
+ */
+
+package org.roller.presentation.cache;
+
+import java.util.Map;
+
+
+/**
+ * An interface representing a cache factory. Implementors of this interface
+ * are responsible for providing a method to construct cache implementations.
+ *
+ * In Roller you switch between various caching options by choosing a different
+ * cache factory before starting up the application.
+ *
+ * @author Allen Gilliland
+ */
+public interface CacheFactory {
+
+ public Cache constructCache(Map properties);
+
+}
Added: incubator/roller/trunk/src/org/roller/presentation/cache/CacheHandler.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/cache/CacheHandler.java?rev=349478&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/cache/CacheHandler.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/cache/CacheHandler.java Mon Nov 28 11:44:28 2005
@@ -0,0 +1,53 @@
+/*
+ * CacheHandler.java
+ *
+ * Created on November 5, 2005, 7:33 PM
+ */
+
+package org.roller.presentation.cache;
+
+import java.util.Map;
+import org.roller.pojos.BookmarkData;
+import org.roller.pojos.CommentData;
+import org.roller.pojos.FolderData;
+import org.roller.pojos.RefererData;
+import org.roller.pojos.UserData;
+import org.roller.pojos.WeblogCategoryData;
+import org.roller.pojos.WeblogEntryData;
+import org.roller.pojos.WeblogTemplate;
+import org.roller.pojos.WebsiteData;
+
+
+/**
+ * A class which utilizes a cache.
+ *
+ * The primary purpose of this interface is to force cache handlers to implement
+ * the set of invalidate() methods which server as notifications of changed
+ * objects. Various caches can determine for themselves how to deal with changes
+ * to each type of object.
+ *
+ * @author Allen Gilliland
+ */
+public interface CacheHandler {
+
+ public void invalidate(WeblogEntryData entry);
+
+ public void invalidate(WebsiteData website);
+
+ public void invalidate(BookmarkData bookmark);
+
+ public void invalidate(FolderData folder);
+
+ public void invalidate(CommentData comment);
+
+ public void invalidate(RefererData referer);
+
+ public void invalidate(UserData user);
+
+ public void invalidate(WeblogCategoryData category);
+
+ public void invalidate(WeblogTemplate template);
+
+ public Map getStats();
+
+}
Added: incubator/roller/trunk/src/org/roller/presentation/cache/CacheManager.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/cache/CacheManager.java?rev=349478&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/cache/CacheManager.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/cache/CacheManager.java Mon Nov 28 11:44:28 2005
@@ -0,0 +1,311 @@
+/*
+ * CacheManager.java
+ *
+ * Created on September 30, 2005, 4:28 PM
+ */
+
+package org.roller.presentation.cache;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.roller.config.RollerConfig;
+import org.roller.pojos.BookmarkData;
+import org.roller.pojos.CommentData;
+import org.roller.pojos.FolderData;
+import org.roller.pojos.RefererData;
+import org.roller.pojos.UserData;
+import org.roller.pojos.WeblogCategoryData;
+import org.roller.pojos.WeblogEntryData;
+import org.roller.pojos.WeblogTemplate;
+import org.roller.pojos.WebsiteData;
+
+
+/**
+ * A governing class for Roller cache objects.
+ *
+ * The purpose of the CacheManager is to provide a level of abstraction between
+ * classes that use a cache and the implementations of a cache. This allows
+ * us to create easily pluggable cache implementations.
+ *
+ * The other purpose is to provide a single interface for interacting with all
+ * Roller caches at the same time. This is beneficial because as data
+ * changes in the system we often need to notify all caches that some part of
+ * their cached data needs to be invalidated, and the CacheManager makes that
+ * process easier.
+ *
+ * @author Allen Gilliland
+ */
+public class CacheManager {
+
+ private static Log mLogger = LogFactory.getLog(CacheManager.class);
+
+ private static final String DEFAULT_FACTORY =
+ "org.roller.presentation.cache.ExpiringLRUCacheFactoryImpl";
+
+ // a reference to the cache factory in use
+ private static CacheFactory mCacheFactory = null;
+
+ // a list of all cache handlers who have obtained a cache
+ private static Set cacheHandlers = new HashSet();
+
+
+ static {
+ // lookup what cache factory we want to use
+ String classname = RollerConfig.getProperty("cache.factory.classname");
+
+ // use reflection to instantiate our factory class
+ try {
+ Class factoryClass = Class.forName(classname);
+ mCacheFactory = (CacheFactory) factoryClass.newInstance();
+ } catch(ClassCastException cce) {
+ mLogger.error("It appears that your factory does not implement "+
+ "the CacheFactory interface",cce);
+ } catch(Exception e) {
+ mLogger.error("Unable to instantiate cache factory ["+classname+"]"+
+ " falling back on default", e);
+ }
+
+ if(mCacheFactory == null) try {
+ // hmm ... failed to load the specified cache factory
+ // lets try our default
+ Class factoryClass = Class.forName(DEFAULT_FACTORY);
+ mCacheFactory = (CacheFactory) factoryClass.newInstance();
+ } catch(Exception e) {
+ mLogger.fatal("Failed to instantiate a cache factory", e);
+ }
+
+ mLogger.info("Cache Manager Initialized.");
+ mLogger.info("Default cache factory = "+mCacheFactory.getClass().getName());
+
+ // finally, add custom handlers
+ String customHandlers = RollerConfig.getProperty("cache.customHandlers");
+ if(customHandlers != null && customHandlers.trim().length() > 0) {
+
+ String[] cHandlers = customHandlers.split(",");
+ for(int i=0; i < cHandlers.length; i++) {
+ // use reflection to instantiate the handler class
+ try {
+ Class handlerClass = Class.forName(cHandlers[i]);
+ CacheHandler customHandler =
+ (CacheHandler) handlerClass.newInstance();
+
+ cacheHandlers.add(customHandler);
+ } catch(ClassCastException cce) {
+ mLogger.error("It appears that your handler does not implement "+
+ "the CacheHandler interface",cce);
+ } catch(Exception e) {
+ mLogger.error("Unable to instantiate cache handler ["+cHandlers[i]+"]", e);
+ }
+ }
+ }
+
+ }
+
+
+ // a non-instantiable class
+ private CacheManager() {}
+
+
+ /**
+ * Ask the CacheManager to construct a cache.
+ *
+ * Normally the CacheManager will use whatever CacheFactory has been
+ * chosen for the system via the cache.factory.classname property.
+ * However, it is possible to override the use of the default factory by
+ * supplying a cache.factory property to this method. The value should
+ * be the full classname for the factory you want to use for constructing
+ * the cache.
+ *
+ * example:
+ * cache.factory -> org.roller.presentation.cache.LRUCacheFactoryImpl
+ *
+ * This allows Roller admins the ability to choose a caching strategy to
+ * use for the whole system, but override it in certain places where they
+ * see fit. It also allows users to write their own caching modifications
+ * and have them used only by specific caches.
+ */
+ public static Cache constructCache(CacheHandler handler, Map properties) {
+
+ mLogger.debug("Constructing new cache with props "+properties);
+
+ Cache cache = null;
+
+ if(properties != null && properties.containsKey("cache.factory")) {
+ // someone wants a custom cache instance
+ String classname = (String) properties.get("cache.factory");
+
+ try {
+ // use reflection to instantiate the factory class
+ Class factoryClass = Class.forName(classname);
+ CacheFactory factory = (CacheFactory) factoryClass.newInstance();
+
+ // now ask for a new cache
+ cache = factory.constructCache(properties);
+ } catch(ClassCastException cce) {
+ mLogger.error("It appears that your factory ["+classname+
+ "] does not implement the CacheFactory interface",cce);
+ } catch(Exception e) {
+ mLogger.error("Unable to instantiate cache factory ["+classname+
+ "] falling back on default", e);
+ }
+ }
+
+ if(cache == null) {
+ // ask our default cache factory for a new cache instance
+ cache = mCacheFactory.constructCache(properties);
+ }
+
+ // register the handler for this new cache
+ cacheHandlers.add(handler);
+
+ return cache;
+ }
+
+
+ /**
+ * Register a CacheHandler to listen for object invalidations.
+ *
+ * This is here so that it's possible to to add classes which would respond
+ * to object invalidations without necessarily having to create a cache.
+ *
+ * An example would be a handler designed to notify other machines in a
+ * cluster when an object has been invalidated, or possibly the search
+ * index management classes are interested in knowing when objects are
+ * invalidated.
+ */
+ public static void registerHandler(CacheHandler handler) {
+
+ mLogger.debug("Registering handler "+handler);
+
+ cacheHandlers.add(handler);
+ }
+
+
+ public static void invalidate(WeblogEntryData entry) {
+
+ mLogger.debug("invalidating entry = "+entry.getAnchor());
+
+ Iterator handlers = cacheHandlers.iterator();
+ while(handlers.hasNext()) {
+ ((CacheHandler) handlers.next()).invalidate(entry);
+ }
+ }
+
+
+ public static void invalidate(WebsiteData website) {
+
+ mLogger.debug("invalidating website = "+website.getHandle());
+
+ Iterator handlers = cacheHandlers.iterator();
+ while(handlers.hasNext()) {
+ ((CacheHandler) handlers.next()).invalidate(website);
+ }
+ }
+
+
+ public static void invalidate(BookmarkData bookmark) {
+
+ mLogger.debug("invalidating bookmark = "+bookmark.getId());
+
+ Iterator handlers = cacheHandlers.iterator();
+ while(handlers.hasNext()) {
+ ((CacheHandler) handlers.next()).invalidate(bookmark);
+ }
+ }
+
+
+ public static void invalidate(FolderData folder) {
+
+ mLogger.debug("invalidating folder = "+folder.getId());
+
+ Iterator handlers = cacheHandlers.iterator();
+ while(handlers.hasNext()) {
+ ((CacheHandler) handlers.next()).invalidate(folder);
+ }
+ }
+
+
+ public static void invalidate(CommentData comment) {
+
+ mLogger.debug("invalidating comment = "+comment.getId());
+
+ Iterator handlers = cacheHandlers.iterator();
+ while(handlers.hasNext()) {
+ ((CacheHandler) handlers.next()).invalidate(comment);
+ }
+ }
+
+
+ public static void invalidate(RefererData referer) {
+
+ mLogger.debug("invalidating referer = "+referer.getId());
+
+ Iterator handlers = cacheHandlers.iterator();
+ while(handlers.hasNext()) {
+ ((CacheHandler) handlers.next()).invalidate(referer);
+ }
+ }
+
+
+ public static void invalidate(UserData user) {
+
+ mLogger.debug("invalidating user = "+user.getUserName());
+
+ Iterator handlers = cacheHandlers.iterator();
+ while(handlers.hasNext()) {
+ ((CacheHandler) handlers.next()).invalidate(user);
+ }
+ }
+
+
+ public static void invalidate(WeblogCategoryData category) {
+
+ mLogger.debug("invalidating category = "+category.getId());
+
+ Iterator handlers = cacheHandlers.iterator();
+ while(handlers.hasNext()) {
+ ((CacheHandler) handlers.next()).invalidate(category);
+ }
+ }
+
+
+ public static void invalidate(WeblogTemplate template) {
+
+ mLogger.debug("invalidating template = "+template.getId());
+
+ Iterator handlers = cacheHandlers.iterator();
+ while(handlers.hasNext()) {
+ ((CacheHandler) handlers.next()).invalidate(template);
+ }
+ }
+
+
+ /**
+ * Compile stats from all registered handlers.
+ *
+ * This is basically a hacky version of instrumentation which is being
+ * thrown in because we don't have a fully instrumentation strategy yet.
+ * This is here with the full expectation that it will be replaced by
+ * something a bit more elaborate, like JMX.
+ */
+ public static Map getStats() {
+
+ Map allStats = new HashMap();
+
+ CacheHandler handler = null;
+ Iterator handlers = cacheHandlers.iterator();
+ while(handlers.hasNext()) {
+ handler = (CacheHandler) handlers.next();
+
+ allStats.put(handler.getClass().getName(), handler.getStats());
+ }
+
+ return allStats;
+ }
+
+}
Added: incubator/roller/trunk/src/org/roller/presentation/cache/ExpiringCacheEntry.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/cache/ExpiringCacheEntry.java?rev=349478&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/cache/ExpiringCacheEntry.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/cache/ExpiringCacheEntry.java Mon Nov 28 11:44:28 2005
@@ -0,0 +1,70 @@
+/*
+ * ExpiringCacheEntry.java
+ *
+ * Created on November 4, 2005, 12:10 PM
+ */
+
+package org.roller.presentation.cache;
+
+/**
+ * A cache entry that expires.
+ *
+ * We use this class to wrap objects being cached and associate a timestamp
+ * and timeout period with them so we can know when they expire.
+ *
+ * @author Allen Gilliland
+ */
+public class ExpiringCacheEntry {
+
+ private Object value;
+ private long timeCached = -1;
+ private long timeout = 0;
+
+
+ public ExpiringCacheEntry(Object value, long timeout) {
+ this.value = value;
+
+ // make sure that we don't support negative values
+ if(timeout > 0) {
+ this.timeout = timeout;
+ }
+
+ this.timeCached = System.currentTimeMillis();
+ }
+
+
+ public long getTimeCached() {
+ return this.timeCached;
+ }
+
+
+ public long getTimeout() {
+ return this.timeout;
+ }
+
+
+ /**
+ * Retrieve the value of this cache entry.
+ *
+ * If the value has expired then we return null.
+ */
+ public Object getValue() {
+ if(this.hasExpired()) {
+ return null;
+ } else {
+ return this.value;
+ }
+ }
+
+
+ /**
+ * Determine if this cache entry has expired.
+ */
+ public boolean hasExpired() {
+
+ long now = System.currentTimeMillis();
+
+ return ((this.timeCached + this.timeout) < now);
+ }
+
+}
Added: incubator/roller/trunk/src/org/roller/presentation/cache/ExpiringLRUCacheFactoryImpl.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/cache/ExpiringLRUCacheFactoryImpl.java?rev=349478&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/cache/ExpiringLRUCacheFactoryImpl.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/cache/ExpiringLRUCacheFactoryImpl.java Mon Nov 28 11:44:28 2005
@@ -0,0 +1,56 @@
+/*
+ * LRUCacheFactoryImpl.java
+ *
+ * Created on October 26, 2005, 3:33 PM
+ */
+
+package org.roller.presentation.cache;
+
+import java.util.Map;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+
+/**
+ * Roller Expiring LRU cache factory.
+ *
+ * @author Allen Gilliland
+ */
+public class ExpiringLRUCacheFactoryImpl implements CacheFactory {
+
+ private static Log mLogger =
+ LogFactory.getLog(ExpiringLRUCacheFactoryImpl.class);
+
+
+ // protected so only the CacheManager can instantiate us
+ protected ExpiringLRUCacheFactoryImpl() {}
+
+
+ /**
+ * Construct a new instance of a Roller Expiring LRUCache.
+ */
+ public Cache constructCache(Map properties) {
+
+ int size = 100;
+ long timeout = 15 * 60;
+
+ try {
+ size = Integer.parseInt((String) properties.get("size"));
+ } catch(Exception e) {
+ // ignored
+ }
+
+ try {
+ timeout = Long.parseLong((String) properties.get("timeout"));
+ } catch(Exception e) {
+ // ignored
+ }
+
+ Cache cache = new ExpiringLRUCacheImpl(size, timeout);
+
+ mLogger.debug("new cache constructed. size="+size+", timeout="+timeout);
+
+ return cache;
+ }
+
+}
\ No newline at end of file
Added: incubator/roller/trunk/src/org/roller/presentation/cache/ExpiringLRUCacheImpl.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/cache/ExpiringLRUCacheImpl.java?rev=349478&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/cache/ExpiringLRUCacheImpl.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/cache/ExpiringLRUCacheImpl.java Mon Nov 28 11:44:28 2005
@@ -0,0 +1,85 @@
+/*
+ * ExpiringLRUCacheImpl.java
+ *
+ * Created on November 6, 2005, 10:33 AM
+ */
+
+package org.roller.presentation.cache;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+
+/**
+ * An LRU cache where entries expire after a given timeout period.
+ *
+ * @author Allen Gilliland
+ */
+public class ExpiringLRUCacheImpl extends LRUCacheImpl {
+
+ private static Log mLogger = LogFactory.getLog(ExpiringLRUCacheImpl.class);
+
+ private long timeout = 0;
+
+
+ public ExpiringLRUCacheImpl() {
+
+ super();
+ this.timeout = 60 * 60 * 1000;
+ }
+
+
+ public ExpiringLRUCacheImpl(int maxsize, long timeout) {
+
+ super(maxsize);
+
+ // timeout is specified in seconds; only positive values allowed
+ if(timeout > 0) {
+ this.timeout = timeout * 1000;
+ }
+ }
+
+
+ /**
+ * Store an entry in the cache.
+ *
+ * We wrap the cached object in our ExpiringCacheEntry object so that we
+ * can track when the entry has expired.
+ */
+ public synchronized void put(String key, Object value) {
+
+ ExpiringCacheEntry entry = new ExpiringCacheEntry(value, this.timeout);
+ super.put(key, entry);
+ }
+
+
+ /**
+ * Retrieve an entry from the cache.
+ *
+ * This LRU cache supports timeouts, so if the cached object has expired
+ * then we return null, just as if the entry wasn't found.
+ */
+ public Object get(String key) {
+
+ Object value = null;
+ ExpiringCacheEntry entry = null;
+
+ synchronized(this) {
+ entry = (ExpiringCacheEntry) super.get(key);
+ }
+
+ if (entry != null) {
+
+ value = entry.getValue();
+
+ // if the value is null then that means this entry expired
+ if (value == null) {
+ mLogger.debug("entry expired ["+key+"]");
+ super.remove(key);
+ }
+ }
+
+ return value;
+ }
+
+}
Added: incubator/roller/trunk/src/org/roller/presentation/cache/LRUCacheFactoryImpl.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/cache/LRUCacheFactoryImpl.java?rev=349478&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/cache/LRUCacheFactoryImpl.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/cache/LRUCacheFactoryImpl.java Mon Nov 28 11:44:28 2005
@@ -0,0 +1,48 @@
+/*
+ * LRUCacheFactoryImpl.java
+ *
+ * Created on November 6, 2005, 10:48 AM
+ */
+
+package org.roller.presentation.cache;
+
+import java.util.Map;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+
+/**
+ * Roller LRU Cache factory.
+ *
+ * @author Allen Gilliland
+ */
+public class LRUCacheFactoryImpl implements CacheFactory {
+
+ private static Log mLogger = LogFactory.getLog(LRUCacheFactoryImpl.class);
+
+
+ // protected so that only the CacheManager can instantiate us
+ protected LRUCacheFactoryImpl() {}
+
+
+ /**
+ * Construct a new instance of a Roller LRUCache.
+ */
+ public Cache constructCache(Map properties) {
+
+ int size = 100;
+
+ try {
+ size = Integer.parseInt((String) properties.get("size"));
+ } catch(Exception e) {
+ // ignored
+ }
+
+ Cache cache = new LRUCacheImpl(size);
+
+ mLogger.debug("new cache constructed. size="+size);
+
+ return cache;
+ }
+
+}
Added: incubator/roller/trunk/src/org/roller/presentation/cache/LRUCacheImpl.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/cache/LRUCacheImpl.java?rev=349478&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/cache/LRUCacheImpl.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/cache/LRUCacheImpl.java Mon Nov 28 11:44:28 2005
@@ -0,0 +1,104 @@
+/*
+ * LRUCacheImpl.java
+ *
+ * Created on November 6, 2005, 10:33 AM
+ */
+package org.roller.presentation.cache;
+
+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 = new LRULinkedHashMap(100);
+ }
+
+
+ protected LRUCacheImpl(int maxsize) {
+
+ this.cache = 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 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 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/roller/presentation/filters/FeedCacheFilter.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/filters/FeedCacheFilter.java?rev=349478&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/filters/FeedCacheFilter.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/filters/FeedCacheFilter.java Mon Nov 28 11:44:28 2005
@@ -0,0 +1,347 @@
+/*
+ * RssCacheFilter.java
+ *
+ * Created on November 5, 2005, 6:32 PM
+ */
+
+package org.roller.presentation.filters;
+
+import java.io.IOException;
+import java.util.Date;
+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.roller.config.RollerConfig;
+import org.roller.pojos.BookmarkData;
+import org.roller.pojos.CommentData;
+import org.roller.pojos.FolderData;
+import org.roller.pojos.RefererData;
+import org.roller.pojos.UserData;
+import org.roller.pojos.WeblogCategoryData;
+import org.roller.pojos.WeblogEntryData;
+import org.roller.pojos.WeblogTemplate;
+import org.roller.pojos.WebsiteData;
+import org.roller.presentation.WeblogFeedRequest;
+import org.roller.presentation.cache.Cache;
+import org.roller.presentation.cache.CacheHandler;
+import org.roller.presentation.cache.CacheManager;
+import org.roller.presentation.util.CacheHttpServletResponseWrapper;
+import org.roller.presentation.util.ResponseContent;
+
+
+/**
+ * A filter used for caching fully rendered xml feeds.
+ *
+ * This filter should only be applied to /rss/*, /atom/*, /flavor/*, /planetrss
+ *
+ * @web.filter name="FeedCacheFilter"
+ *
+ * @author Allen Gilliland
+ */
+public class FeedCacheFilter implements Filter, CacheHandler {
+
+ private static Log mLogger = LogFactory.getLog(FeedCacheFilter.class);
+
+ private Cache mFeedCache = null;
+
+ // 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 = "feedCache:"+this.generateKey(feedRequest);
+
+ ResponseContent respContent = (ResponseContent) this.mFeedCache.get(key);
+ if (respContent == null) {
+
+ mLogger.debug("MISS "+key);
+ this.misses++;
+
+ try {
+ 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.mFeedCache.put(key, rc);
+ } else {
+ mLogger.error("Display exception "+key);
+ }
+
+ } catch (java.net.SocketException se) {
+ // ignored
+ } catch (Exception e) {
+ // something unexpected and bad happened
+ mLogger.error("Error rendering feed "+key+" - "+e.getMessage());
+ }
+
+ } else {
+
+ mLogger.debug("HIT "+key);
+ this.hits++;
+
+ try {
+ respContent.writeTo(response);
+ } catch (java.net.SocketException se) {
+ // ignored
+ } catch (Exception e) {
+ mLogger.error("Error with cached response "+key+" - "+e.getMessage());
+ }
+
+ }
+
+ 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());
+ key.append("/").append(feedRequest.getFlavor());
+
+ if(feedRequest.getWeblogCategory() != null) {
+ String cat = feedRequest.getWeblogCategory();
+ if(cat.startsWith("/"))
+ cat = cat.substring(1).replaceAll("/","_");
+
+ key.append("/").append(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 void invalidate(WebsiteData website) {
+
+ mLogger.debug("invalidating website = "+website.getHandle());
+
+ // we need to remove the following cached items if they exist
+ // - the main feed
+ // - the planet feed
+ // - all weblog feeds
+
+ 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.mFeedCache.keySet().iterator();
+ while(allKeys.hasNext()) {
+ key = (String) allKeys.next();
+
+ if(key.startsWith("feedCache:main")) {
+ removeSet.add(key);
+ } else if(key.startsWith("feedCache:planet")) {
+ removeSet.add(key);
+ } else if(key.startsWith("feedCache:weblog/"+website.getHandle())) {
+ removeSet.add(key);
+ }
+ }
+
+ this.mFeedCache.remove(removeSet);
+ }
+
+
+ /**
+ * 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
+ }
+
+
+ public Map getStats() {
+
+ Map stats = new HashMap();
+ 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");
+
+ String factory = RollerConfig.getProperty("cache.feed.factory");
+ String size = RollerConfig.getProperty("cache.feed.size");
+ String timeout = RollerConfig.getProperty("cache.feed.timeout");
+
+ int cacheSize = 100;
+ try {
+ cacheSize = Integer.parseInt(size);
+ } catch (Exception e) {
+ mLogger.warn("Invalid cache size ["+size+"], using default");
+ }
+
+ long cacheTimeout = 30 * 60;
+ try {
+ cacheTimeout = Long.parseLong(timeout);
+ } catch (Exception e) {
+ mLogger.warn("Invalid cache timeout ["+timeout+
+ "], using default");
+ }
+
+
+ Map props = new HashMap();
+ props.put("timeout", ""+cacheTimeout);
+ props.put("size", ""+cacheSize);
+
+ if(factory != null && factory.trim().length() > 0)
+ props.put("cache.factory", factory);
+
+ mLogger.info(props);
+
+ mFeedCache = CacheManager.constructCache(this, props);
+ }
+
+}
Added: incubator/roller/trunk/src/org/roller/presentation/filters/IfModifiedFeedCacheFilter.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/filters/IfModifiedFeedCacheFilter.java?rev=349478&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/filters/IfModifiedFeedCacheFilter.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/filters/IfModifiedFeedCacheFilter.java Mon Nov 28 11:44:28 2005
@@ -0,0 +1,347 @@
+/*
+ * IfModifiedFeedCacheFilter.java
+ *
+ * Created on November 9, 2005, 2:47 PM
+ */
+
+package org.roller.presentation.filters;
+
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+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.roller.RollerException;
+import org.roller.config.RollerConfig;
+import org.roller.model.Roller;
+import org.roller.model.RollerFactory;
+import org.roller.model.UserManager;
+import org.roller.model.WeblogManager;
+import org.roller.pojos.BookmarkData;
+import org.roller.pojos.CommentData;
+import org.roller.pojos.FolderData;
+import org.roller.pojos.RefererData;
+import org.roller.pojos.UserData;
+import org.roller.pojos.WeblogCategoryData;
+import org.roller.pojos.WeblogEntryData;
+import org.roller.pojos.WeblogTemplate;
+import org.roller.pojos.WebsiteData;
+import org.roller.presentation.WeblogFeedRequest;
+import org.roller.presentation.cache.Cache;
+import org.roller.presentation.cache.CacheHandler;
+import org.roller.presentation.cache.CacheManager;
+
+
+/**
+ * A filter used for caching last modified dates.
+ *
+ * This may be applied to /rss/*, /atom/*, /flavor/*, and /planetrss
+ *
+ * @web.filter name="IfModifiedFeedCacheFilter"
+ *
+ * @author Allen Gilliland
+ */
+public class IfModifiedFeedCacheFilter implements Filter, CacheHandler {
+
+ private static Log mLogger =
+ LogFactory.getLog(IfModifiedFeedCacheFilter.class);
+
+ 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;
+
+ 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 = "ifmod:"+this.generateKey(feedRequest);
+
+ Date updateTime = null;
+ try {
+ updateTime = (Date) this.mCache.get(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, 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 (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());
+ key.append("/").append(feedRequest.getFlavor());
+
+ if(feedRequest.getWeblogCategory() != null) {
+ String cat = feedRequest.getWeblogCategory();
+ if(cat.startsWith("/"))
+ cat = cat.substring(1).replaceAll("/","_");
+
+ key.append("/").append(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 void invalidate(WebsiteData website) {
+
+ mLogger.debug("invalidating website = "+website.getHandle());
+
+ // we need to remove the following cached items if they exist
+ // - the main feed
+ // - the planet feed
+ // - all weblog feeds
+
+ 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:main")) {
+ removeSet.add(key);
+ } else if(key.startsWith("ifmod:planet")) {
+ removeSet.add(key);
+ } else if(key.startsWith("ifmod:weblog/"+website.getHandle())) {
+ removeSet.add(key);
+ }
+ }
+
+ this.mCache.remove(removeSet);
+ }
+
+
+ /**
+ * 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
+ }
+
+
+ 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");
+
+ String factory = RollerConfig.getProperty("cache.feed.ifmodified.factory");
+ String size = RollerConfig.getProperty("cache.feed.ifmodified.size");
+ String timeout = RollerConfig.getProperty("cache.feed.ifmodified.timeout");
+
+ int cacheSize = 100;
+ try {
+ cacheSize = Integer.parseInt(size);
+ } catch (Exception e) {
+ mLogger.warn("Invalid cache size ["+size+"], using default");
+ }
+
+ long cacheTimeout = 30 * 60;
+ try {
+ cacheTimeout = Long.parseLong(timeout);
+ } catch (Exception e) {
+ mLogger.warn("Invalid cache timeout ["+timeout+"], using default");
+ }
+
+
+ Map props = new HashMap();
+ props.put("timeout", ""+cacheTimeout);
+ props.put("size", ""+cacheSize);
+
+ if(factory != null && factory.trim().length() > 0)
+ props.put("cache.factory", factory);
+
+ mLogger.info(props);
+
+ mCache = CacheManager.constructCache(this, props);
+ }
+
+}
Added: incubator/roller/trunk/src/org/roller/presentation/filters/MainPageCacheFilter.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/filters/MainPageCacheFilter.java?rev=349478&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/filters/MainPageCacheFilter.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/filters/MainPageCacheFilter.java Mon Nov 28 11:44:28 2005
@@ -0,0 +1,304 @@
+/*
+ * MainPageCacheFilter.java
+ *
+ * Created on November 7, 2005, 2:32 PM
+ */
+package org.roller.presentation.filters;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.Date;
+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.roller.config.RollerConfig;
+import org.roller.pojos.BookmarkData;
+import org.roller.pojos.CommentData;
+import org.roller.pojos.FolderData;
+import org.roller.pojos.RefererData;
+import org.roller.pojos.UserData;
+import org.roller.pojos.WeblogCategoryData;
+import org.roller.pojos.WeblogEntryData;
+import org.roller.pojos.WeblogTemplate;
+import org.roller.pojos.WebsiteData;
+import org.roller.presentation.LanguageUtil;
+import org.roller.presentation.cache.Cache;
+import org.roller.presentation.cache.CacheHandler;
+import org.roller.presentation.cache.CacheManager;
+import org.roller.presentation.util.CacheHttpServletResponseWrapper;
+import org.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);
+
+ private boolean excludeOwnerPages = false;
+ private Cache mPageCache = null;
+
+ // 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;
+
+ String key = null;
+ Principal prince = null;
+
+ String servlet = request.getServletPath();
+ if(servlet.equals("/main.do")) {
+ key = "main/page";
+ } else if(servlet.equals("/planet.do")) {
+ key = "planet/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;
+ }
+
+
+ ResponseContent respContent = null;
+ if(!this.excludeOwnerPages || prince == null) {
+ respContent = (ResponseContent) this.mPageCache.get(key);
+ }
+
+ if (respContent == null) {
+
+ mLogger.debug("MISS "+key);
+ this.misses++;
+
+ try {
+ 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.mPageCache.put(key, rc);
+ } else {
+ mLogger.debug("SKIPPED "+key);
+ }
+ } else {
+ mLogger.error("Display exception "+key);
+ }
+
+ } catch (java.net.SocketException se) {
+ // ignored
+ } catch (Exception e) {
+ // something unexpected and bad happened
+ mLogger.error("Error rendering page "+key, e);
+ }
+
+ } else {
+
+ mLogger.debug("HIT "+key);
+ this.hits++;
+
+ try {
+ respContent.writeTo(response);
+ } catch (java.net.SocketException se) {
+ // ignored
+ } catch (Exception e) {
+ mLogger.error("Error with cached response "+key, e);
+ }
+
+ }
+
+ mLogger.debug("exiting");
+ }
+
+
+ /**
+ * A weblog entry has changed.
+ */
+ public void invalidate(WeblogEntryData entry) {
+ this.mPageCache.clear();
+ }
+
+
+ /**
+ * A weblog has changed.
+ */
+ public void invalidate(WebsiteData website) {
+ this.mPageCache.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
+ }
+
+
+ public Map getStats() {
+
+ Map stats = new HashMap();
+ 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 main page cache");
+
+ String factory = RollerConfig.getProperty("cache.mainpage.factory");
+ String size = RollerConfig.getProperty("cache.mainpage.size");
+ String timeout = RollerConfig.getProperty("cache.mainpage.timeout");
+ this.excludeOwnerPages =
+ RollerConfig.getBooleanProperty("cache.mainpage.excludeOwnerEditPages");
+
+ int cacheSize = 20;
+ try {
+ cacheSize = Integer.parseInt(size);
+ } catch (Exception e) {
+ mLogger.warn("Invalid cache size ["+size+"], using default");
+ }
+
+ long cacheTimeout = 30 * 60;
+ try {
+ cacheTimeout = Long.parseLong(timeout);
+ } catch (Exception e) {
+ mLogger.warn("Invalid cache timeout ["+timeout+
+ "], using default");
+ }
+
+
+ Map props = new HashMap();
+ props.put("timeout", ""+cacheTimeout);
+ props.put("size", ""+cacheSize);
+
+ if(factory != null && factory.trim().length() > 0)
+ props.put("cache.factory", factory);
+
+ mLogger.info(props);
+
+ mPageCache = CacheManager.constructCache(this, props);
+ }
+
+}