Return-Path: Delivered-To: apmail-incubator-roller-commits-archive@www.apache.org Received: (qmail 45698 invoked from network); 21 Oct 2005 22:02:42 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (209.237.227.199) by minotaur.apache.org with SMTP; 21 Oct 2005 22:02:42 -0000 Received: (qmail 79295 invoked by uid 500); 21 Oct 2005 21:53:32 -0000 Delivered-To: apmail-incubator-roller-commits-archive@incubator.apache.org Received: (qmail 75941 invoked by uid 500); 21 Oct 2005 21:53:11 -0000 Mailing-List: contact roller-commits-help@incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: roller-dev@incubator.apache.org Delivered-To: mailing list roller-commits@incubator.apache.org Received: (qmail 73187 invoked by uid 99); 21 Oct 2005 21:52:02 -0000 X-ASF-Spam-Status: No, hits=-8.6 required=10.0 tests=ALL_TRUSTED,INFO_TLD,NO_REAL_NAME X-Spam-Check-By: apache.org Received: from [209.237.227.194] (HELO minotaur.apache.org) (209.237.227.194) by apache.org (qpsmtpd/0.29) with SMTP; Fri, 21 Oct 2005 14:50:38 -0700 Received: (qmail 36967 invoked by uid 65534); 21 Oct 2005 21:50:17 -0000 Message-ID: <20051021215017.36966.qmail@minotaur.apache.org> Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r327589 [32/72] - in /incubator/roller/branches/roller_1.x: ./ contrib/ contrib/lib/ contrib/plugins/ contrib/plugins/src/ contrib/plugins/src/org/ contrib/plugins/src/org/roller/ contrib/plugins/src/org/roller/presentation/ contrib/plugins... Date: Fri, 21 Oct 2005 21:46:28 -0000 To: roller-commits@incubator.apache.org From: snoopdave@apache.org X-Mailer: svnmailer-1.0.5 X-Virus-Checked: Checked by ClamAV on apache.org X-Spam-Rating: minotaur.apache.org 1.6.2 0/1000/N Added: incubator/roller/branches/roller_1.x/src/org/roller/presentation/pagecache/rollercache/LRUCacheHandler.java URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/presentation/pagecache/rollercache/LRUCacheHandler.java?rev=327589&view=auto ============================================================================== --- incubator/roller/branches/roller_1.x/src/org/roller/presentation/pagecache/rollercache/LRUCacheHandler.java (added) +++ incubator/roller/branches/roller_1.x/src/org/roller/presentation/pagecache/rollercache/LRUCacheHandler.java Fri Oct 21 14:27:36 2005 @@ -0,0 +1,470 @@ +/* + * Created on Jun 15, 2004 + */ +package org.roller.presentation.pagecache.rollercache; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.roller.pojos.UserData; +import org.roller.presentation.LanguageUtil; +import org.roller.presentation.pagecache.FilterHandler; +import org.roller.util.LRUCache; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; +import java.util.TreeMap; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.roller.config.RollerConfig; + +/** + * Page cache implementation that uses a simple LRUCache. Can be configured + * using filter configuration parameters: + *
    + *
  • size: number of pages to keep in cache. Once cache reaches + * this size, each new cache entry will push out the LRU cache entry.
  • + * + *
  • timeoutInterval: interval to timeout pages in seconds + * (default is 1800 seconds). Sites with a large number of users, and thus a + * lot of cache churn which makes this check unnecessary, may wish to set this + * to -1 to disable timeout checking.
  • + * + *
  • timeoutRatio: portion of old pages to expire on timeout + * interval where 1.0 is 100% (default is 1.0). This only applies if the + * timeoutInterval is set to something other than -1.
  • + *
+ * @author David M Johnson + */ +public class LRUCacheHandler implements FilterHandler +{ + private static Log mLogger = + LogFactory.getFactory().getInstance(LRUCacheHandler.class); + + private Map mPageCache = null; + + private String mName = null; + + /** Timeout interval: how often to run timeout task (default 30 mintes) */ + private long mTimeoutInterval = 30 * 60 * 1000; // milliseconds + + /** Timeout ratio: % of cache to expire on each timeout (default 1.0) */ + private float mTimeoutRatio = 1.0F; + + // Statistics + private int misses = 0; + private int hits = 0; + + private final static String FILE_SEPARATOR = "/"; + private final static char FILE_SEPARATOR_CHAR = FILE_SEPARATOR.charAt(0); + private final static short AVERAGE_KEY_LENGTH = 30; + private static final String m_strBase64Chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + + public LRUCacheHandler(FilterConfig config) + { + mName = config.getFilterName(); + mLogger.info("Initializing for: " + mName); + + String cacheSize = RollerConfig.getProperty("cache.filter.page.size"); + String cacheTimeout = RollerConfig.getProperty("cache.filter.page.timeout"); + + int size = 200; + try + { + size = Integer.parseInt(cacheSize); + } + catch (Exception e) + { + mLogger.warn(config.getFilterName() + + "Can't read cache size parameter, using default..."); + } + mLogger.info(mName + " size=" + size); + mPageCache = Collections.synchronizedMap(new LRUCache(size)); + + long intervalSeconds = mTimeoutInterval / 1000L; + try + { + mTimeoutInterval = 1000L * Long.parseLong(cacheTimeout); + + if (mTimeoutInterval == -1) + { + mLogger.info(config.getFilterName() + + "timeoutInterval of -1: timeouts are disabled"); + } + else if (mTimeoutInterval < (30 * 1000)) + { + mTimeoutInterval = 30 * 1000; + mLogger.warn(config.getFilterName() + + "timeoutInterval cannot be less than 30 seconds"); + } + } + catch (Exception e) + { + mLogger.warn(config.getFilterName() + + "Can't read timeoutInterval parameter, disabling timeout."); + mTimeoutInterval = -1; + } + mLogger.info(mName + " timeoutInterval=" + intervalSeconds); + + try + { + mTimeoutRatio = Float.parseFloat( + config.getInitParameter("timeoutRatio")); + } + catch (Exception e) + { + mLogger.warn(config.getFilterName() + + "Can't read timeoutRatio parameter, using default..."); + } + mLogger.info(mName + " timeoutRatio=" + mTimeoutRatio); + + if (mTimeoutInterval != -1 && mTimeoutRatio != 0.0) + { + Timer timer = new Timer(); + timer.scheduleAtFixedRate(new TimerTask() { + public void run() { + timeoutCache(); + } + }, mTimeoutInterval, mTimeoutInterval); + } + } + + /** + * @see org.roller.presentation.pagecache.FilterHandler#destroy() + */ + public void destroy() + { + } + + /** + * @see org.roller.presentation.pagecache.FilterHandler#doFilter( + * javax.servlet.ServletRequest, javax.servlet.ServletResponse, + * javax.servlet.FilterChain) + */ + public void doFilter(ServletRequest req, ServletResponse res, + FilterChain chain) throws ServletException, IOException + { + HttpServletRequest request = (HttpServletRequest) req; + HttpServletResponse response = (HttpServletResponse) res; + + // get locale + Locale locale = LanguageUtil.getViewLocale(request); + + // generate language-sensitive cache-key + String generatedKey = null; + if (locale != null) + { + generatedKey = generateEntryKey(null, + request, 1, locale.getLanguage()); + } + else + { + generatedKey = generateEntryKey(null, + request, 1, null); + } + + // Add authenticated user name, if there is one, to cache key + java.security.Principal prince = request.getUserPrincipal(); + StringBuffer keyb = new StringBuffer(); + keyb.append(generatedKey); + if (prince != null) + { + keyb.append("_"); + keyb.append(prince); + } + String key = keyb.toString(); + + ResponseContent respContent = (ResponseContent)getFromCache(key); + if (respContent == null) + { + try + { + CacheHttpServletResponseWrapper cacheResponse = + new CacheHttpServletResponseWrapper(response); + chain.doFilter(request, cacheResponse); + + // Store as the cache content the result of the response + // if no exception was noted by content generator. + if (request.getAttribute("DisplayException") == null) + { + ResponseContent rc = cacheResponse.getContent(); + putToCache(key, rc); + } + else + { + StringBuffer sb = new StringBuffer(); + sb.append("Display exception, cache, key="); + sb.append(key); + mLogger.error(sb.toString()); + } + } + catch (java.net.SocketException se) + { + // ignore socket exceptions + } + catch (Exception e) + { + // something unexpected and bad happened + StringBuffer sb = new StringBuffer(); + sb.append("Error rendering page, key="); + sb.append(key); + mLogger.error(sb.toString()); + } + } + else + { + try + { + respContent.writeTo(response); + } + catch (java.net.SocketException se) + { + // ignore socket exceptions + } + catch (Exception e) + { + if (mLogger.isDebugEnabled()) + { + StringBuffer sb = new StringBuffer(); + sb.append("Probably a client abort exception, key="); + sb.append(key); + mLogger.error(sb.toString()); + } + } + + } + } + + /** + * Purge entire cache. + */ + public synchronized void flushCache(HttpServletRequest req) + { + mPageCache.clear(); + } + + /** + * Purge user's entries from cache. + */ + public synchronized void removeFromCache(HttpServletRequest req, UserData user) + { + // TODO: can we make this a little more precise, perhaps via regex? + String rssString = "/rss/" + user.getUserName(); // user's pages + String pageString = "/page/" + user.getUserName(); // user's RSS feeds + String mainRssString = "/rss_"; // main RSS feed + List purgeList = new ArrayList(); + + Iterator keys = mPageCache.keySet().iterator(); + while (keys.hasNext()) + { + String key = (String) keys.next(); + + if (key.indexOf(rssString)!=-1 || key.indexOf(pageString)!=-1 || key.indexOf(mainRssString)!=-1) + { + purgeList.add(key); + } + } + + Iterator purgeIter = purgeList.iterator(); + while (purgeIter.hasNext()) + { + String key = (String) purgeIter.next(); + mPageCache.remove(key); + } + + if (mLogger.isDebugEnabled()) + { + StringBuffer sb = new StringBuffer(); + sb.append("Purged, count="); + sb.append(purgeList.size()); + sb.append(", user="); + sb.append(user.getUserName()); + mLogger.debug(sb.toString()); + } + } + + public synchronized void timeoutCache() + { + if (mTimeoutRatio == 1.0) + { + mLogger.debug("Timing out whole cache: " + mName); + mPageCache.clear(); + } + else + { + int numToTimeout = (int)(mTimeoutRatio * mPageCache.size()); + mLogger.debug( + "Timing out " + numToTimeout + " of " + mPageCache.size() + + " entries from cache: " + mName); + ArrayList allKeys = new ArrayList(mPageCache.keySet()); + for (int i=numToTimeout; i>0; i--) + { + mPageCache.remove(allKeys.get(i)); + } + } + } + + /** + * Get from cache. Synchronized because "In access-ordered linked hash + * maps, merely querying the map with get is a structural modification" + */ + public synchronized Object getFromCache(String key) + { + Object entry = mPageCache.get(key); + + if (entry != null && mLogger.isDebugEnabled()) + { + hits++; + } + return entry; + } + + public synchronized void putToCache(String key, Object entry) + { + mPageCache.put(key, entry); + if (mLogger.isDebugEnabled()) + { + misses++; + + StringBuffer sb = new StringBuffer(); + sb.append("Missed, cache size="); + sb.append(mPageCache.size()); + sb.append(", hits="); + sb.append(hits); + sb.append(", misses="); + sb.append(misses); + sb.append(", key="); + sb.append(key); + mLogger.debug(sb.toString()); + } + } + + public String generateEntryKey(String key, + HttpServletRequest request, int scope, String language) + { + StringBuffer cBuffer = new StringBuffer(AVERAGE_KEY_LENGTH); + // Append the language if available + if (language != null) + { + cBuffer.append(FILE_SEPARATOR).append(language); + } + + //cBuffer.append(FILE_SEPARATOR).append(request.getServerName()); + + if (key != null) + { + cBuffer.append(FILE_SEPARATOR).append(key); + } + else + { + String generatedKey = request.getRequestURI(); + if (generatedKey.charAt(0) != FILE_SEPARATOR_CHAR) + { + cBuffer.append(FILE_SEPARATOR_CHAR); + } + cBuffer.append(generatedKey); + cBuffer.append("_").append(request.getMethod()).append("_"); + generatedKey = getSortedQueryString(request); + if (generatedKey != null) + { + try + { + java.security.MessageDigest digest = + java.security.MessageDigest.getInstance("MD5"); + byte[] b = digest.digest(generatedKey.getBytes()); + cBuffer.append("_"); + // Base64 encoding allows for unwanted slash characters. + cBuffer.append(toBase64(b).replace('/', '_')); + } + catch (Exception e) + { + // Ignore query string + } + } + } + return cBuffer.toString(); + } + + protected String getSortedQueryString(HttpServletRequest request) + { + Map paramMap = request.getParameterMap(); + if (paramMap.isEmpty()) + { + return null; + } + Set paramSet = new TreeMap(paramMap).entrySet(); + StringBuffer buf = new StringBuffer(); + boolean first = true; + for (Iterator it = paramSet.iterator(); it.hasNext();) + { + Map.Entry entry = (Map.Entry) it.next(); + String[] values = (String[]) entry.getValue(); + for (int i = 0; i < values.length; i++) + { + String key = (String) entry.getKey(); + if ((key.length() != 10) || !"jsessionid".equals(key)) + { + if (first) + { + first = false; + } + else + { + buf.append('&'); + } + buf.append(key).append('=').append(values[i]); + } + } + } + // We get a 0 length buffer if the only parameter was a jsessionid + if (buf.length() == 0) + { + return null; + } + else + { + return buf.toString(); + } + } + + /** + * Convert a byte array into a Base64 string (as used in mime formats) + */ + private static String toBase64(byte[] aValue) + { + int byte1; + int byte2; + int byte3; + int iByteLen = aValue.length; + StringBuffer tt = new StringBuffer(); + for (int i = 0; i < iByteLen; i += 3) + { + boolean bByte2 = (i + 1) < iByteLen; + boolean bByte3 = (i + 2) < iByteLen; + byte1 = aValue[i] & 0xFF; + byte2 = (bByte2) ? (aValue[i + 1] & 0xFF) : 0; + byte3 = (bByte3) ? (aValue[i + 2] & 0xFF) : 0; + tt.append(m_strBase64Chars.charAt(byte1 / 4)); + tt.append(m_strBase64Chars.charAt((byte2 / 16) + + ((byte1 & 0x3) * 16))); + tt.append(((bByte2) ? m_strBase64Chars.charAt((byte3 / 64) + + ((byte2 & 0xF) * 4)) : '=')); + tt.append(((bByte3) ? m_strBase64Chars.charAt(byte3 & 0x3F) : '=')); + } + return tt.toString(); + } +} Added: incubator/roller/branches/roller_1.x/src/org/roller/presentation/pagecache/rollercache/LRUCacheHandler2.java URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/presentation/pagecache/rollercache/LRUCacheHandler2.java?rev=327589&view=auto ============================================================================== --- incubator/roller/branches/roller_1.x/src/org/roller/presentation/pagecache/rollercache/LRUCacheHandler2.java (added) +++ incubator/roller/branches/roller_1.x/src/org/roller/presentation/pagecache/rollercache/LRUCacheHandler2.java Fri Oct 21 14:27:36 2005 @@ -0,0 +1,403 @@ +/* + * Created on Jun 15, 2004 + */ +package org.roller.presentation.pagecache.rollercache; +import java.io.IOException; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.roller.config.RollerConfig; +import org.roller.pojos.UserData; +import org.roller.presentation.LanguageUtil; +import org.roller.presentation.pagecache.FilterHandler; +import org.roller.util.LRUCache2; + +/** + * Page cache implementation that uses a simple LRUCache. Can be configured + * using filter configuration parameters: + *
    + *
  • size: number of pages to keep in cache. Once cache reaches + * this size, each new cache entry will push out the LRU cache entry.
  • + *
  • timeout: interval to timeout pages in milliseconds.
  • + *
+ * @author David M Johnson + */ +public class LRUCacheHandler2 implements FilterHandler +{ + private static Log mLogger = + LogFactory.getFactory().getInstance(LRUCacheHandler2.class); + + private LRUCache2 mPageCache = null; + private String mName = null; + + // Statistics + private int misses = 0; + private int hits = 0; + + private final static String FILE_SEPARATOR = "/"; + private final static char FILE_SEPARATOR_CHAR = FILE_SEPARATOR.charAt(0); + private final static short AVERAGE_KEY_LENGTH = 30; + private static final String m_strBase64Chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + public LRUCacheHandler2(FilterConfig config) + { + mName = config.getFilterName(); + mLogger.info("Initializing for: " + mName); + + String cacheSize = RollerConfig.getProperty("cache.filter.page.size"); + String cacheTimeout = RollerConfig.getProperty("cache.filter.page.timeout"); + + int size = 200; + try + { + size = Integer.parseInt(cacheSize); + } + catch (Exception e) + { + mLogger.warn(config.getFilterName() + + "Can't read cache size parameter, using default..."); + } + mLogger.info(mName + " size=" + size); + + long timeout = 30000; + try + { + timeout = Long.parseLong(cacheTimeout); + } + catch (Exception e) + { + mLogger.warn(config.getFilterName() + + "Can't read timeout parameter, using default."); + } + mLogger.info(mName + " timeout=" + timeout + "seconds"); + mPageCache = new LRUCache2(size, timeout*1000); + } + + /** + * @see org.roller.presentation.pagecache.FilterHandler#destroy() + */ + public void destroy() + { + } + + /** + * @see org.roller.presentation.pagecache.FilterHandler#doFilter( + * javax.servlet.ServletRequest, javax.servlet.ServletResponse, + * javax.servlet.FilterChain) + */ + public void doFilter(ServletRequest req, ServletResponse res, + FilterChain chain) throws ServletException, IOException + { + HttpServletRequest request = (HttpServletRequest) req; + HttpServletResponse response = (HttpServletResponse) res; + + // Get locale, needed for creating cache key. + // Unfortunately, getViewLocal works by parsing the request URL + // which we would like to avoid where possible. + Locale locale = null; + try + { + locale = LanguageUtil.getViewLocale(request); + } + catch (Exception e) + { + // An error parsjng the request is considered to be a 404 + mLogger.debug("Unable determine view local from request"); + response.sendError(HttpServletResponse.SC_NOT_FOUND); + return; + } + + // generate language-sensitive cache-key + String generatedKey = null; + if (locale != null) + { + generatedKey = generateEntryKey(null, + request, 1, locale.getLanguage()); + } + else + { + generatedKey = generateEntryKey(null, + request, 1, null); + } + + // Add authenticated user name, if there is one, to cache key + java.security.Principal prince = request.getUserPrincipal(); + StringBuffer keyb = new StringBuffer(); + keyb.append(generatedKey); + if (prince != null) + { + keyb.append("_"); + keyb.append(prince); + } + String key = keyb.toString(); + + ResponseContent respContent = (ResponseContent)getFromCache(key); + if (respContent == null) + { + try + { + CacheHttpServletResponseWrapper cacheResponse = + new CacheHttpServletResponseWrapper(response); + + chain.doFilter(request, cacheResponse); + 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(); + putToCache(key, rc); + } + else + { + StringBuffer sb = new StringBuffer(); + sb.append("Display exception, cache, key="); + sb.append(key); + mLogger.error(sb.toString()); + } + } + catch (java.net.SocketException se) + { + // ignore socket exceptions + } + catch (Exception e) + { + // something unexpected and bad happened + StringBuffer sb = new StringBuffer(); + sb.append("Error rendering page, key="); + sb.append(key); + mLogger.error(sb.toString()); + mLogger.debug(e); + } + } + else + { + try + { + respContent.writeTo(response); + } + catch (java.net.SocketException se) + { + // ignore socket exceptions + } + catch (Exception e) + { + if (mLogger.isDebugEnabled()) + { + StringBuffer sb = new StringBuffer(); + sb.append("Probably a client abort exception, key="); + sb.append(key); + mLogger.error(sb.toString()); + } + } + + } + } + + /** + * Purge entire cache. + */ + public synchronized void flushCache(HttpServletRequest req) + { + mPageCache.purge(); + } + + /** + * Purge user's entries from cache. + */ + public synchronized void removeFromCache(HttpServletRequest req, UserData user) + { + // TODO: can we make this a little more precise, perhaps via regex? + String rssString = "/rss/" + user.getUserName(); // user's pages + String pageString = "/page/" + user.getUserName(); // user's RSS feeds + String mainRssString = "/rss_"; // main RSS feed + String mainPageString = "/main.do"; // main page + String planetPageString = "/planet.do"; // planet page + + int beforeSize = mPageCache.size(); + mPageCache.purge(new String[] + { + rssString, + pageString, + mainRssString, + mainPageString, + planetPageString + }); + int afterSize = mPageCache.size(); + + if (mLogger.isDebugEnabled()) + { + StringBuffer sb = new StringBuffer(); + sb.append("Purged, count="); + sb.append(beforeSize - afterSize); + sb.append(", user="); + sb.append(user.getUserName()); + mLogger.debug(sb.toString()); + } + } + + /** + * Get from cache. Synchronized because "In access-ordered linked hash + * maps, merely querying the map with get is a structural modification" + */ + public synchronized Object getFromCache(String key) + { + Object entry = mPageCache.get(key); + + if (entry != null && mLogger.isDebugEnabled()) + { + hits++; + } + return entry; + } + + public synchronized void putToCache(String key, Object entry) + { + mPageCache.put(key, entry); + if (mLogger.isDebugEnabled()) + { + misses++; + + StringBuffer sb = new StringBuffer(); + sb.append("Missed, cache size="); + sb.append(mPageCache.size()); + sb.append(", hits="); + sb.append(hits); + sb.append(", misses="); + sb.append(misses); + sb.append(", key="); + sb.append(key); + mLogger.debug(sb.toString()); + } + } + + public String generateEntryKey(String key, + HttpServletRequest request, int scope, String language) + { + StringBuffer cBuffer = new StringBuffer(AVERAGE_KEY_LENGTH); + // Append the language if available + if (language != null) + { + cBuffer.append(FILE_SEPARATOR).append(language); + } + + //cBuffer.append(FILE_SEPARATOR).append(request.getServerName()); + + if (key != null) + { + cBuffer.append(FILE_SEPARATOR).append(key); + } + else + { + String generatedKey = request.getRequestURI(); + if (generatedKey.charAt(0) != FILE_SEPARATOR_CHAR) + { + cBuffer.append(FILE_SEPARATOR_CHAR); + } + cBuffer.append(generatedKey); + cBuffer.append("_").append(request.getMethod()).append("_"); + generatedKey = getSortedQueryString(request); + if (generatedKey != null) + { + try + { + java.security.MessageDigest digest = + java.security.MessageDigest.getInstance("MD5"); + byte[] b = digest.digest(generatedKey.getBytes()); + cBuffer.append("_"); + // Base64 encoding allows for unwanted slash characters. + cBuffer.append(toBase64(b).replace('/', '_')); + } + catch (Exception e) + { + // Ignore query string + } + } + } + return cBuffer.toString(); + } + + protected String getSortedQueryString(HttpServletRequest request) + { + Map paramMap = request.getParameterMap(); + if (paramMap.isEmpty()) + { + return null; + } + Set paramSet = new TreeMap(paramMap).entrySet(); + StringBuffer buf = new StringBuffer(); + boolean first = true; + for (Iterator it = paramSet.iterator(); it.hasNext();) + { + Map.Entry entry = (Map.Entry) it.next(); + String[] values = (String[]) entry.getValue(); + for (int i = 0; i < values.length; i++) + { + String key = (String) entry.getKey(); + if ((key.length() != 10) || !"jsessionid".equals(key)) + { + if (first) + { + first = false; + } + else + { + buf.append('&'); + } + buf.append(key).append('=').append(values[i]); + } + } + } + // We get a 0 length buffer if the only parameter was a jsessionid + if (buf.length() == 0) + { + return null; + } + else + { + return buf.toString(); + } + } + + /** + * Convert a byte array into a Base64 string (as used in mime formats) + */ + private static String toBase64(byte[] aValue) + { + int byte1; + int byte2; + int byte3; + int iByteLen = aValue.length; + StringBuffer tt = new StringBuffer(); + for (int i = 0; i < iByteLen; i += 3) + { + boolean bByte2 = (i + 1) < iByteLen; + boolean bByte3 = (i + 2) < iByteLen; + byte1 = aValue[i] & 0xFF; + byte2 = (bByte2) ? (aValue[i + 1] & 0xFF) : 0; + byte3 = (bByte3) ? (aValue[i + 2] & 0xFF) : 0; + tt.append(m_strBase64Chars.charAt(byte1 / 4)); + tt.append(m_strBase64Chars.charAt((byte2 / 16) + + ((byte1 & 0x3) * 16))); + tt.append(((bByte2) ? m_strBase64Chars.charAt((byte3 / 64) + + ((byte2 & 0xF) * 4)) : '=')); + tt.append(((bByte3) ? m_strBase64Chars.charAt(byte3 & 0x3F) : '=')); + } + return tt.toString(); + } +} Added: incubator/roller/branches/roller_1.x/src/org/roller/presentation/pagecache/rollercache/ResponseContent.java URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/presentation/pagecache/rollercache/ResponseContent.java?rev=327589&view=auto ============================================================================== --- incubator/roller/branches/roller_1.x/src/org/roller/presentation/pagecache/rollercache/ResponseContent.java (added) +++ incubator/roller/branches/roller_1.x/src/org/roller/presentation/pagecache/rollercache/ResponseContent.java Fri Oct 21 14:27:36 2005 @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2002-2003 by OpenSymphony + * All rights reserved. + */ +package org.roller.presentation.pagecache.rollercache; + +import java.io.*; + +import java.util.Locale; + +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletResponse; + +/** + * Holds the servlet response in a byte array so that it can be held + * in the cache (and, since this class is serializable, optionally + * persisted to disk). + * + * @version $Revision: 1.3 $ + * @author Serge Knystautas + */ +public class ResponseContent implements Serializable { + private transient ByteArrayOutputStream bout = new ByteArrayOutputStream(1000); + private Locale locale = null; + private String contentType = null; + private byte[] content = null; + private long lastModified = -1; + + /** + * Set the content type. We capture this so that when we serve this + * data from cache, we can set the correct content type on the response. + */ + public void setContentType(String value) { + contentType = value; + } + + public long getLastModified() { + return lastModified; + } + + public void setLastModified(long value) { + lastModified = value; + } + + /** + * Set the Locale. We capture this so that when we serve this data from + * cache, we can set the correct locale on the response. + */ + public void setLocale(Locale value) { + locale = value; + } + + /** + * Get an output stream. This is used by the {@link SplitServletOutputStream} + * to capture the original (uncached) response into a byte array. + */ + public OutputStream getOutputStream() { + return bout; + } + + /** + * Gets the size of this cached content. + * + * @return The size of the content, in bytes. If no content + * exists, this method returns -1. + */ + public int getSize() { + return (content != null) ? content.length : (-1); + } + + /** + * Called once the response has been written in its entirety. This + * method commits the response output stream by converting the output + * stream into a byte array. + */ + public void commit() { + content = bout.toByteArray(); + } + + /** + * Writes this cached data out to the supplied ServletResponse. + * + * @param response The servlet response to output the cached content to. + * @throws IOException + */ + public void writeTo(ServletResponse response) throws IOException { + //Send the content type and data to this response + if (contentType != null) { + response.setContentType(contentType); + } + + //if (response instanceof HttpServletResponse) { + //((HttpServletResponse) response).setDateHeader("Last-Modified", lastModified); + //} + + response.setContentLength(content.length); + + if (locale != null) { + response.setLocale(locale); + } + + OutputStream out = new BufferedOutputStream(response.getOutputStream()); + out.write(content); + out.flush(); + } +} Added: incubator/roller/branches/roller_1.x/src/org/roller/presentation/pagecache/rollercache/SplitPrintWriter.java URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/presentation/pagecache/rollercache/SplitPrintWriter.java?rev=327589&view=auto ============================================================================== --- incubator/roller/branches/roller_1.x/src/org/roller/presentation/pagecache/rollercache/SplitPrintWriter.java (added) +++ incubator/roller/branches/roller_1.x/src/org/roller/presentation/pagecache/rollercache/SplitPrintWriter.java Fri Oct 21 14:27:36 2005 @@ -0,0 +1,56 @@ +package org.roller.presentation.pagecache.rollercache; + +import java.io.PrintWriter; +import java.io.Writer; + +/** + * @author Dave Johnson + */ +public class SplitPrintWriter extends PrintWriter +{ + private PrintWriter captureWriter = null; + private PrintWriter passThroughWriter = null; + + public SplitPrintWriter(PrintWriter captureWriter, PrintWriter passThroughWriter) + { + super(passThroughWriter); + this.captureWriter = captureWriter; + this.passThroughWriter = passThroughWriter; + } + + public void write(char[] cbuf) + { + captureWriter.write(cbuf); + passThroughWriter.write(cbuf); + } + public void write(char[] cbuf, int off, int len) + { + captureWriter.write(cbuf,off,len); + passThroughWriter.write(cbuf,off,len); + } + public void write(int c) + { + captureWriter.write(c); + passThroughWriter.write(c); + } + public void write(String str) + { + captureWriter.write(str); + passThroughWriter.write(str); + } + public void write(String str, int off, int len) + { + captureWriter.write(str,off,len); + passThroughWriter.write(str,off,len); + } + public void flush() + { + captureWriter.flush(); + passThroughWriter.flush(); + } + public void close() + { + captureWriter.close(); + passThroughWriter.close(); + } +} Added: incubator/roller/branches/roller_1.x/src/org/roller/presentation/pagecache/rollercache/SplitServletOutputStream.java URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/presentation/pagecache/rollercache/SplitServletOutputStream.java?rev=327589&view=auto ============================================================================== --- incubator/roller/branches/roller_1.x/src/org/roller/presentation/pagecache/rollercache/SplitServletOutputStream.java (added) +++ incubator/roller/branches/roller_1.x/src/org/roller/presentation/pagecache/rollercache/SplitServletOutputStream.java Fri Oct 21 14:27:36 2005 @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2002-2003 by OpenSymphony + * All rights reserved. + */ +package org.roller.presentation.pagecache.rollercache; + +import java.io.IOException; +import java.io.OutputStream; + +import javax.servlet.ServletOutputStream; + +/** + * Extends the base ServletOutputStream class so that + * the stream can be captured as it gets written. This is achieved + * by overriding the write() methods and outputting + * the data to two streams - the original stream and a secondary stream + * that is designed to capture the written data. + * + * @version $Revision: 1.1 $ + * @author Serge Knystautas + */ +public class SplitServletOutputStream extends ServletOutputStream { + OutputStream captureStream = null; + OutputStream passThroughStream = null; + + /** + * Constructs a split output stream that both captures and passes through + * the servlet response. + * + * @param captureStream The stream that will be used to capture the data. + * @param passThroughStream The pass-through ServletOutputStream + * that will write the response to the client as originally intended. + */ + public SplitServletOutputStream(OutputStream captureStream, OutputStream passThroughStream) { + this.captureStream = captureStream; + this.passThroughStream = passThroughStream; + } + + /** + * Writes the incoming data to both the output streams. + * + * @param value The int data to write. + * @throws IOException + */ + public void write(int value) throws IOException { + captureStream.write(value); + passThroughStream.write(value); + } + + /** + * Writes the incoming data to both the output streams. + * + * @param value The bytes to write to the streams. + * @throws IOException + */ + public void write(byte[] value) throws IOException { + captureStream.write(value); + passThroughStream.write(value); + } + + /** + * Writes the incoming data to both the output streams. + * + * @param b The bytes to write out to the streams. + * @param off The offset into the byte data where writing should begin. + * @param len The number of bytes to write. + * @throws IOException + */ + public void write(byte[] b, int off, int len) throws IOException { + captureStream.write(b, off, len); + passThroughStream.write(b, off, len); + } +} Added: incubator/roller/branches/roller_1.x/src/org/roller/presentation/pagecache/rollercache/package.html URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/presentation/pagecache/rollercache/package.html?rev=327589&view=auto ============================================================================== --- incubator/roller/branches/roller_1.x/src/org/roller/presentation/pagecache/rollercache/package.html (added) +++ incubator/roller/branches/roller_1.x/src/org/roller/presentation/pagecache/rollercache/package.html Fri Oct 21 14:27:36 2005 @@ -0,0 +1,10 @@ + + + + + + +In-memory LRU implemtation for PageCacheFilter. + + + Added: incubator/roller/branches/roller_1.x/src/org/roller/presentation/pings/PingQueueProcessor.java URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/presentation/pings/PingQueueProcessor.java?rev=327589&view=auto ============================================================================== --- incubator/roller/branches/roller_1.x/src/org/roller/presentation/pings/PingQueueProcessor.java (added) +++ incubator/roller/branches/roller_1.x/src/org/roller/presentation/pings/PingQueueProcessor.java Fri Oct 21 14:27:36 2005 @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2005 + * Anil R. Gangolli. All rights reserved. + * + * Distributed with the Roller Weblogger Project under the terms of the Roller Software + * License + */ + +package org.roller.presentation.pings; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.roller.RollerException; +import org.roller.config.PingConfig; +import org.roller.model.PingQueueManager; +import org.roller.model.RollerFactory; +import org.roller.pojos.PingQueueEntryData; +import org.roller.pojos.PingTargetData; +import org.roller.pojos.WebsiteData; +import org.roller.presentation.RollerContext; + +import java.util.Iterator; +import java.util.List; + +/** + * Ping Queue Processor. Singleton encapsulating logic for processing the weblog update ping queue. + */ +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 RollerContext 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.storeQueueEntry(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/branches/roller_1.x/src/org/roller/presentation/pings/PingQueueTask.java URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/presentation/pings/PingQueueTask.java?rev=327589&view=auto ============================================================================== --- incubator/roller/branches/roller_1.x/src/org/roller/presentation/pings/PingQueueTask.java (added) +++ incubator/roller/branches/roller_1.x/src/org/roller/presentation/pings/PingQueueTask.java Fri Oct 21 14:27:36 2005 @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2005 + * Anil R. Gangolli. All rights reserved. + * + * Distributed with the Roller Weblogger Project under the terms of the Roller Software + * License + */ + +package org.roller.presentation.pings; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.roller.RollerException; +import org.roller.model.Roller; +import org.roller.model.RollerFactory; +import org.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.roller.config.PingConfig} at startup time only. + */ +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(); + roller.begin(); + PingQueueProcessor.getInstance().processQueue(); + roller.commit(); + } + 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(); + } + } +} Added: incubator/roller/branches/roller_1.x/src/org/roller/presentation/pings/WeblogUpdatePinger.java URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/presentation/pings/WeblogUpdatePinger.java?rev=327589&view=auto ============================================================================== --- incubator/roller/branches/roller_1.x/src/org/roller/presentation/pings/WeblogUpdatePinger.java (added) +++ incubator/roller/branches/roller_1.x/src/org/roller/presentation/pings/WeblogUpdatePinger.java Fri Oct 21 14:27:36 2005 @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2004-2005 + * Lance Lavandowska, Anil R. Gangolli. + * All rights reserved. + * + * Distributed with the Roller Weblogger Project under the terms of the Roller Software + * License. + */ + +package org.roller.presentation.pings; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.xmlrpc.XmlRpcClient; +import org.apache.xmlrpc.XmlRpcException; +import org.roller.RollerException; +import org.roller.model.RollerFactory; +import org.roller.pojos.PingTargetData; +import org.roller.pojos.WebsiteData; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.UnknownHostException; +import java.util.Hashtable; +import java.util.Vector; + +/** + * Utility for sending a weblog update ping. + * + * @author Anil Gangolli + * @author llavandowska (for code refactored from the now-defunct RollerXmlRpcClient) + */ +public class WeblogUpdatePinger +{ + public static final Log logger = LogFactory.getLog(WeblogUpdatePinger.class); + + /** + * Conveys a ping result. + */ + public static class PingResult + { + boolean error; + String message; + + public PingResult(Boolean error, String message) + { + this.error = error != null ? error.booleanValue() : false; + this.message = message; + } + + public boolean isError() + { + return error; + } + + public void setError(boolean error) + { + this.error = error; + } + + public String getMessage() + { + return message; + } + + public void setMessage(String message) + { + this.message = message; + } + + public String toString() + { + return "PingResult{" + + "error=" + error + + ", message='" + message + "'" + + "}"; + } + } + + // Inhibit construction + private WeblogUpdatePinger() + { + } + + /** + * Send a weblog update ping. + * + * @param absoluteContextUrl the absolute context url of the Roller site. + * @param pingTarget the target site to ping + * @param website the website that changed (from which the ping originates) + * @return the result message string sent by the server. + * @throws IOException + * @throws XmlRpcException + * @throws RollerException + */ + public static PingResult sendPing(String absoluteContextUrl, PingTargetData pingTarget, WebsiteData website) + throws RollerException, IOException, XmlRpcException + { + // Figure out the url of the user's website. + String websiteUrl = + RollerFactory.getRoller().getWeblogManager().getUrl(website.getUser(), absoluteContextUrl); + + // Set up the ping parameters. + Vector params = new Vector(); + params.addElement(website.getName()); + params.addElement(websiteUrl); + if (logger.isDebugEnabled()) + { + logger.debug("Executing ping to '" + pingTarget.getPingUrl() + "' for website '" + + websiteUrl + "' (" + website.getName() + ")"); + } + + // Send the ping + XmlRpcClient client = new XmlRpcClient(pingTarget.getPingUrl()); + Hashtable result = (Hashtable) client.execute("weblogUpdates.ping", params); + PingResult pingResult = new PingResult((Boolean) result.get("flerror"), (String) result.get("message")); + if (logger.isDebugEnabled()) logger.debug("Ping result is: " + pingResult); + return pingResult; + } + + /** + * Decide if the given exception appears to warrant later retrial attempts. + * + * @param ex an exception thrown by the sendPing operation + * @return true if the error warrants retrial + */ + public static boolean shouldRetry(Exception ex) + { + // Determine if error appears transient (warranting retrial) + // We give most errors the "benefit of the doubt" by considering them transient + // This picks out a few that we consider non-transient + if (ex instanceof UnknownHostException) + { + // User probably mistyped the url in the custom target. + return false; + } + else if (ex instanceof MalformedURLException) + { + // This should never happen due to validations but if we get here, retrial won't fix it. + return false; + } + return true; + } + +} Added: incubator/roller/branches/roller_1.x/src/org/roller/presentation/pings/package.html URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/presentation/pings/package.html?rev=327589&view=auto ============================================================================== --- incubator/roller/branches/roller_1.x/src/org/roller/presentation/pings/package.html (added) +++ incubator/roller/branches/roller_1.x/src/org/roller/presentation/pings/package.html Fri Oct 21 14:27:36 2005 @@ -0,0 +1,10 @@ + + + + + + +Classes for sending Weblogs.com-style pings. + + + Added: incubator/roller/branches/roller_1.x/src/org/roller/presentation/planet/PlanetAction.java URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/presentation/planet/PlanetAction.java?rev=327589&view=auto ============================================================================== --- incubator/roller/branches/roller_1.x/src/org/roller/presentation/planet/PlanetAction.java (added) +++ incubator/roller/branches/roller_1.x/src/org/roller/presentation/planet/PlanetAction.java Fri Oct 21 14:27:36 2005 @@ -0,0 +1,190 @@ +package org.roller.presentation.planet; + +import java.util.ArrayList; +import java.util.List; +import java.util.ResourceBundle; + +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.struts.action.Action; +import org.apache.struts.action.ActionForm; +import org.apache.struts.action.ActionForward; +import org.apache.struts.action.ActionMapping; +import org.roller.RollerException; +import org.roller.config.RollerRuntimeConfig; +import org.roller.model.Roller; +import org.roller.pojos.PlanetConfigData; +import org.roller.pojos.PlanetGroupData; +import org.roller.presentation.RollerContext; +import org.roller.presentation.RollerRequest; + +/** + * Main page action for Roller Planet. + * @struts.action name="main" path="/planet" scope="request" + * @struts.action-forward name="planet.page" path="/planet.jsp" + */ +public class PlanetAction extends Action +{ + private static Log mLogger = + LogFactory.getFactory().getInstance(PlanetAction.class); + private static ResourceBundle bundle = + ResourceBundle.getBundle("ApplicationResources"); + + /** + * Loads model and forwards to planet.page. + */ + public ActionForward execute( + ActionMapping mapping, ActionForm form, + HttpServletRequest req, HttpServletResponse res) + throws Exception + { + RollerContext rctx = RollerContext.getRollerContext(req); + req.setAttribute("version",rctx.getRollerVersion()); + req.setAttribute("buildTime",rctx.getRollerBuildTime()); + req.setAttribute("baseURL", rctx.getContextUrl(req)); + req.setAttribute("data", new PlanetPageData(req)); + + boolean allowNewUsers = + RollerRuntimeConfig.getBooleanProperty("users.registration.enabled"); + + java.security.Principal prince = req.getUserPrincipal(); + if (prince != null) + { + req.setAttribute("loggedIn",Boolean.TRUE); + req.setAttribute("userName",prince.getName()); + } + else if (allowNewUsers) + { + req.setAttribute("allowNewUsers",Boolean.TRUE); + } + req.setAttribute("leftPage","/theme/status.jsp"); + + return mapping.findForward("planet.page"); + } + + /** + * Page model. + */ + public static class PlanetPageData + { + private HttpServletRequest mRequest = null; + private String mTitle = + bundle.getString("planet.title.unconfigured"); + private String mDescription = + bundle.getString("planet.description.unconfigured"); + + public String getTitle() {return mTitle;} + public String getDescription() {return mDescription;} + + public PlanetPageData(HttpServletRequest req) throws RollerException + { + mRequest = req; + Roller roller = + RollerRequest.getRollerRequest(mRequest).getRoller(); + PlanetConfigData cfg = roller.getPlanetManager().getConfiguration(); + if (cfg != null) + { + mTitle = cfg.getTitle(); + mDescription = cfg.getDescription(); + } + } + + /** + * Get aggregation of entries in 'all' and 'external' groups + */ + public List getAggregation(int num) throws RollerException + { + Roller roller = + RollerRequest.getRollerRequest(mRequest).getRoller(); + return roller.getPlanetManager().getAggregation(num); + } + /** + * Get named group + */ + public PlanetGroupData getGroup(String name) throws RollerException + { + PlanetGroupData group = null; + try + { + Roller roller = + RollerRequest.getRollerRequest(mRequest).getRoller(); + group = roller.getPlanetManager().getGroup(name); + } + catch (RollerException e) + { + mLogger.error(e); + } + return group; + } + /** + * Get aggregation of entries in named group + */ + public List getAggregation(String name, int num) throws RollerException + { + List ret = new ArrayList(); + try + { + Roller roller = + RollerRequest.getRollerRequest(mRequest).getRoller(); + PlanetGroupData group= roller.getPlanetManager().getGroup(name); + ret = roller.getPlanetManager().getAggregation(group, num); + } + catch (RollerException e) + { + mLogger.error(e); + } + return ret; + } + /** + * Get top blogs according to Technorati + */ + public List getTopSubscriptions(int num) throws RollerException + { + List ret = new ArrayList(); + try + { + Roller roller = + RollerRequest.getRollerRequest(mRequest).getRoller(); + ret = roller.getPlanetManager().getTopSubscriptions(num); + } + catch (RollerException e) + { + mLogger.error(e); + } + return ret; + } + /** + * Get top blogs in a group according to Technorati + */ + public List getTopSubscriptions(String name, int num) + throws RollerException + { + List ret = new ArrayList(); + try + { + Roller roller = + RollerRequest.getRollerRequest(mRequest).getRoller(); + PlanetGroupData group= roller.getPlanetManager().getGroup(name); + ret = roller.getPlanetManager().getTopSubscriptions(group,num); + } + catch (RollerException e) + { + mLogger.error(e); + } + return ret; + } + /** + * Get list of most popular websites in terms of day hits. + */ + public List getPopularWebsites(int num) throws RollerException + { + Roller roller = + RollerRequest.getRollerRequest(mRequest).getRoller(); + return roller.getRefererManager().getDaysPopularWebsites(num); + } + } +} + Added: incubator/roller/branches/roller_1.x/src/org/roller/presentation/planet/PlanetConfigAction.java URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/presentation/planet/PlanetConfigAction.java?rev=327589&view=auto ============================================================================== --- incubator/roller/branches/roller_1.x/src/org/roller/presentation/planet/PlanetConfigAction.java (added) +++ incubator/roller/branches/roller_1.x/src/org/roller/presentation/planet/PlanetConfigAction.java Fri Oct 21 14:27:36 2005 @@ -0,0 +1,254 @@ +/* + * Copyright 2005 Sun Microsystems, Inc. + * + * Licensed 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. + */ +package org.roller.presentation.planet; + +import java.io.File; +import java.io.IOException; + +import javax.servlet.ServletException; +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.struts.action.ActionError; +import org.apache.struts.action.ActionErrors; +import org.apache.struts.action.ActionForm; +import org.apache.struts.action.ActionForward; +import org.apache.struts.action.ActionMapping; +import org.apache.struts.action.ActionMessage; +import org.apache.struts.action.ActionMessages; +import org.apache.struts.actions.DispatchAction; +import org.roller.presentation.forms.PlanetConfigForm; +import org.roller.config.RollerRuntimeConfig; +import org.roller.model.PlanetManager; +import org.roller.model.Roller; +import org.roller.pojos.PlanetConfigData; +import org.roller.pojos.PlanetGroupData; +import org.roller.presentation.RollerRequest; +import org.roller.presentation.planet.RefreshEntriesTask; +import org.roller.presentation.planet.SyncWebsitesTask; + +///////////////////////////////////////////////////////////////////////////// +/** + * Allows configuration of Planet Roller. + * + * @struts.action name="planetConfigForm" path="/admin/planetConfig" + * scope="request" parameter="method" + * + * @struts.action-forward name="planetConfig.page" + * path="/planet/PlanetConfig.jsp" + */ +public final class PlanetConfigAction extends DispatchAction +{ + private static Log logger = + LogFactory.getFactory().getInstance(PlanetConfigAction.class); + + /** Populate config form and forward to config page */ + public ActionForward getConfig(ActionMapping mapping, + ActionForm actionForm, HttpServletRequest request, + HttpServletResponse response) throws IOException, ServletException + { + ActionForward forward = mapping.findForward("planetConfig.page"); + try + { + RollerRequest rreq = RollerRequest.getRollerRequest(request); + if (rreq.isUserAuthorizedToEdit()) + { + Roller roller = rreq.getRoller(); + PlanetManager planet = roller.getPlanetManager(); + PlanetConfigData config = planet.getConfiguration(); + PlanetConfigForm form = (PlanetConfigForm)actionForm; + if (config != null) + { + form.copyFrom(config, request.getLocale()); + } + else + { + form.setTitle("Planet Roller"); + form.setAdminEmail(RollerRuntimeConfig.getProperty("site.adminemail")); + form.setSiteUrl(RollerRuntimeConfig.getProperty("site.absoluteurl")); + form.setCacheDir("/tmp"); + } + } + else + { + forward = mapping.findForward("access-denied"); + } + } + catch (Exception e) + { + request.getSession().getServletContext().log("ERROR", e); + throw new ServletException(e); + } + return forward; + } + + /** Save posted config form data */ + public ActionForward saveConfig(ActionMapping mapping, + ActionForm actionForm, HttpServletRequest request, + HttpServletResponse response) throws IOException, ServletException + { + ActionForward forward = mapping.findForward("planetConfig.page"); + try + { + RollerRequest rreq = RollerRequest.getRollerRequest(request); + if (rreq.isUserAuthorizedToEdit()) + { + Roller roller = rreq.getRoller(); + PlanetManager planet = roller.getPlanetManager(); + PlanetConfigData config = planet.getConfiguration(); + if (config == null) + { + config = new PlanetConfigData(); + } + PlanetConfigForm form = (PlanetConfigForm) actionForm; + ActionErrors errors = validate(form); + if (errors.isEmpty()) + { + form.copyTo(config, request.getLocale()); + planet.saveConfiguration(config); + if (planet.getGroup("external") == null) + { + PlanetGroupData group = new PlanetGroupData(); + group.setHandle("external"); + group.setTitle("external"); + planet.saveGroup(group); + } + roller.commit(); + ActionMessages messages = new ActionMessages(); + messages.add(null, new ActionMessage("planetConfig.success.saved")); + saveMessages(request, messages); + } + else + { + saveErrors(request, errors); + } + } + else + { + forward = mapping.findForward("access-denied"); + } + } + catch (Exception e) + { + request.getSession().getServletContext().log("ERROR", e); + throw new ServletException(e); + } + return forward; + } + + /** Refresh entries in backgrounded thread (for testing) */ + public ActionForward refreshEntries(ActionMapping mapping, + ActionForm actionForm, HttpServletRequest request, + HttpServletResponse response) throws IOException, ServletException + { + ActionForward forward = mapping.findForward("planetConfig.page"); + try + { + RollerRequest rreq = RollerRequest.getRollerRequest(request); + if (rreq.isUserAuthorizedToEdit()) + { + Roller roller = rreq.getRoller(); + RefreshEntriesTask task = new RefreshEntriesTask(); + task.init(roller, "dummy"); + roller.getThreadManager().executeInBackground(task); + + ActionMessages messages = new ActionMessages(); + messages.add(null, + new ActionMessage("planetConfig.success.refreshed")); + saveMessages(request, messages); + } + else + { + forward = mapping.findForward("access-denied"); + } + } + catch (Exception e) + { + request.getSession().getServletContext().log("ERROR", e); + throw new ServletException(e); + } + return forward; + } + + /** Sync websites in backgrounded thread (for testing) */ + public ActionForward syncWebsites(ActionMapping mapping, + ActionForm actionForm, HttpServletRequest request, + HttpServletResponse response) throws IOException, ServletException + { + ActionForward forward = mapping.findForward("planetConfig.page"); + try + { + RollerRequest rreq = RollerRequest.getRollerRequest(request); + if (rreq.isUserAuthorizedToEdit()) + { + Roller roller = (Roller)rreq.getRoller(); + SyncWebsitesTask task = new SyncWebsitesTask(); + task.init(roller, "dummy"); + roller.getThreadManager().executeInBackground(task); + ActionMessages messages = new ActionMessages(); + messages.add(null, + new ActionMessage("planetConfig.success.synced")); + saveMessages(request, messages); + } + else + { + forward = mapping.findForward("access-denied"); + } + } + catch (Exception e) + { + request.getSession().getServletContext().log("ERROR", e); + throw new ServletException(e); + } + return forward; + } + + /** Validate config form, returns empty collection if all OK */ + public ActionErrors validate(PlanetConfigForm form) + { + ActionErrors errors = new ActionErrors(); + if (form.getCacheDir()==null || form.getCacheDir().trim().length()==0) + { + errors.add(null, new ActionError("planetConfig.error.feedUrl")); + } + else + { + File file = new File(form.getCacheDir()); + if (!file.isDirectory()) + { + errors.add(null, new ActionError( + "planetConfig.error.cacheDirNotFound")); + } + if (!file.canWrite()) + { + errors.add(null, new ActionError( + "planetConfig.error.cacheDirNotWritable")); + } + } + if (form.getProxyHost()!=null && form.getProxyHost().trim().length()>0) + { + if (form.getProxyPort()<1) + { + errors.add(null, new ActionError( + "planetConfig.error.badProxyPort")); + } + } + return errors; + } +} + Added: incubator/roller/branches/roller_1.x/src/org/roller/presentation/planet/PlanetGroupsAction.java URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/presentation/planet/PlanetGroupsAction.java?rev=327589&view=auto ============================================================================== --- incubator/roller/branches/roller_1.x/src/org/roller/presentation/planet/PlanetGroupsAction.java (added) +++ incubator/roller/branches/roller_1.x/src/org/roller/presentation/planet/PlanetGroupsAction.java Fri Oct 21 14:27:36 2005 @@ -0,0 +1,303 @@ +/* + * Copyright 2005 Sun Microsystems, Inc. + * + * Licensed 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. + */ +package org.roller.presentation.planet; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import javax.servlet.ServletException; +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.struts.action.ActionError; +import org.apache.struts.action.ActionErrors; +import org.apache.struts.action.ActionForm; +import org.apache.struts.action.ActionForward; +import org.apache.struts.action.ActionMapping; +import org.apache.struts.action.ActionMessage; +import org.apache.struts.action.ActionMessages; +import org.apache.struts.actions.DispatchAction; +import org.roller.RollerException; +import org.roller.model.PlanetManager; +import org.roller.model.Roller; +import org.roller.pojos.PlanetGroupData; +import org.roller.presentation.BasePageModel; +import org.roller.presentation.RollerRequest; +import org.roller.presentation.forms.PlanetGroupForm; + + +///////////////////////////////////////////////////////////////////////////// +/** + * Add, remove, and view user defined groups. + * + * @struts.action name="planetGroupForm" path="/admin/planetGroups" + * scope="request" parameter="method" + * + * @struts.action-forward name="planetGroups.page" + * path="/planet/PlanetGroups.jsp" + */ +public final class PlanetGroupsAction extends DispatchAction +{ + private static Log logger = LogFactory.getFactory().getInstance( + PlanetGroupsAction.class); + + /** Populate page model and forward to subscription page */ + public ActionForward getGroups(ActionMapping mapping, + ActionForm actionForm, HttpServletRequest request, + HttpServletResponse response) throws IOException, ServletException + { + ActionForward forward = mapping.findForward("planetGroups.page"); + try + { + RollerRequest rreq = RollerRequest.getRollerRequest(request); + if (rreq.isUserAuthorizedToEdit()) + { + Roller roller = rreq.getRoller(); + PlanetManager planet = roller.getPlanetManager(); + PlanetGroupForm form = (PlanetGroupForm)actionForm; + if (request.getParameter("groupHandle") != null) + { + String feedUrl = request.getParameter("groupHandle"); + PlanetGroupData group = planet.getGroup(feedUrl); + form.copyFrom(group, request.getLocale()); + } + else + { + form.doReset(mapping, request); + } + request.setAttribute("model", + new GroupsPageModel(request, response, mapping)); + } + else + { + forward = mapping.findForward("access-denied"); + } + } + catch (Exception e) + { + request.getSession().getServletContext().log("ERROR", e); + throw new ServletException(e); + } + return forward; + } + + /** Cancel editing, reset form */ + public ActionForward cancelEditing(ActionMapping mapping, + ActionForm actionForm, HttpServletRequest request, + HttpServletResponse response) throws IOException, ServletException + { + ActionForward forward = mapping.findForward("planetGroups.page"); + try + { + RollerRequest rreq = RollerRequest.getRollerRequest(request); + if (rreq.isUserAuthorizedToEdit()) + { + Roller roller = rreq.getRoller(); + PlanetManager planet = roller.getPlanetManager(); + PlanetGroupForm form = (PlanetGroupForm)actionForm; + + form.doReset(mapping, request); + + request.setAttribute("model", + new GroupsPageModel(request, response, mapping)); + } + else + { + forward = mapping.findForward("access-denied"); + } + } + catch (Exception e) + { + request.getSession().getServletContext().log("ERROR", e); + throw new ServletException(e); + } + return forward; + } + + /** Delete subscription, reset form */ + public ActionForward deleteGroup(ActionMapping mapping, + ActionForm actionForm, HttpServletRequest request, + HttpServletResponse response) throws IOException, ServletException + { + ActionForward forward = mapping.findForward("planetGroups.page"); + try + { + RollerRequest rreq = RollerRequest.getRollerRequest(request); + if (rreq.isUserAuthorizedToEdit()) + { + Roller roller = rreq.getRoller(); + PlanetManager planet = roller.getPlanetManager(); + PlanetGroupForm form = (PlanetGroupForm)actionForm; + if (form.getHandle() != null) + { + PlanetGroupData group = planet.getGroup(form.getHandle()); + planet.deleteGroup(group); + roller.commit(); + roller.release(); + + roller.begin(); + form.doReset(mapping, request); + + request.setAttribute("model", + new GroupsPageModel(request, response, mapping)); + + ActionMessages messages = new ActionMessages(); + messages.add(null, + new ActionMessage("planetSubscription.success.deleted")); + saveMessages(request, messages); + } + } + else + { + forward = mapping.findForward("access-denied"); + } + } + catch (Exception e) + { + ActionErrors errors = new ActionErrors(); + errors.add(null, new ActionError("planetGroup.error.deleting")); + saveErrors(request, errors); + } + return forward; + } + + /** Save subscription, add to "external" group */ + public ActionForward saveGroup(ActionMapping mapping, + ActionForm actionForm, HttpServletRequest request, + HttpServletResponse response) throws IOException, ServletException + { + ActionForward forward = mapping.findForward("planetGroups.page"); + try + { + RollerRequest rreq = RollerRequest.getRollerRequest(request); + if (rreq.isUserAuthorizedToEdit()) + { + PlanetGroupForm form = (PlanetGroupForm)actionForm; + Roller roller = rreq.getRoller(); + PlanetManager planet = roller.getPlanetManager(); + ActionErrors errors = validate(planet, form); + if (errors.isEmpty()) + { + PlanetGroupData group = null; + if (form.getId() == null || form.getId().trim().length() == 0) + { + group = new PlanetGroupData(); + } + else + { + group = planet.getGroupById(form.getId()); + } + form.copyTo(group, request.getLocale()); + planet.saveGroup(group); + roller.commit(); + + ActionMessages messages = new ActionMessages(); + messages.add(null, + new ActionMessage("planetGroups.success.saved")); + saveMessages(request, messages); + form.doReset(mapping, request); + + request.setAttribute("model", + new GroupsPageModel(request, response, mapping)); + } + else + { + saveErrors(request, errors); + } + } + else + { + forward = mapping.findForward("access-denied"); + } + } + catch (RollerException e) + { + ActionErrors errors = new ActionErrors(); + errors.add(null, new ActionError( + "planetSubscriptions.error.duringSave",e.getRootCauseMessage())); + saveErrors(request, errors); + } + return forward; + } + + /** Validate posted group */ + private ActionErrors validate( + PlanetManager planet, PlanetGroupForm form) + { + ActionErrors errors = new ActionErrors(); + if (form.getTitle()==null || form.getTitle().trim().length()==0) + { + errors.add(null, new ActionError("planetGroups.error.title")); + } + if (form.getHandle()==null || form.getHandle().trim().length()==0) + { + errors.add(null, new ActionError("planetGroups.error.handle")); + } + if (form.getHandle() != null && + (form.getHandle().equals("all") || form.getHandle().equals("external"))) + { + errors.add(null, new ActionError("planetGroups.error.nameReserved")); + } + return errors; + } + + /** Page model */ + public class GroupsPageModel extends BasePageModel + { + private List groups = new ArrayList(); + private boolean unconfigured = false; + public GroupsPageModel( + HttpServletRequest request, + HttpServletResponse response, + ActionMapping mapping) throws RollerException + { + super(request, response, mapping); + RollerRequest rreq = RollerRequest.getRollerRequest(request); + Roller roller = rreq.getRoller(); + PlanetManager planet = roller.getPlanetManager(); + PlanetGroupData externalGroup = planet.getGroup("external"); + if (externalGroup != null) + { + Iterator allgroups = planet.getGroups().iterator(); + while (allgroups.hasNext()) + { + PlanetGroupData agroup = (PlanetGroupData)allgroups.next(); + if ( !agroup.getHandle().equals("external") + && !agroup.getHandle().equals("all")) + { + groups.add(agroup); + } + } + } + else + { + unconfigured = true; + } + } + public List getGroups() + { + return groups; + } + public boolean isUnconfigured() + { + return unconfigured; + } + } +} Added: incubator/roller/branches/roller_1.x/src/org/roller/presentation/planet/PlanetSubscriptionFormEx.java URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/presentation/planet/PlanetSubscriptionFormEx.java?rev=327589&view=auto ============================================================================== --- incubator/roller/branches/roller_1.x/src/org/roller/presentation/planet/PlanetSubscriptionFormEx.java (added) +++ incubator/roller/branches/roller_1.x/src/org/roller/presentation/planet/PlanetSubscriptionFormEx.java Fri Oct 21 14:27:36 2005 @@ -0,0 +1,28 @@ +package org.roller.presentation.planet; + +import java.util.Locale; +import org.roller.pojos.PlanetSubscriptionData; +import org.roller.presentation.forms.PlanetSubscriptionForm; + +/** + * @struts.form name="planetSubscriptionFormEx" + */ +public class PlanetSubscriptionFormEx + extends PlanetSubscriptionForm + implements java.io.Serializable +{ + private String groupHandle = null; + public PlanetSubscriptionFormEx() + { + super(); + } + public String getGroupHandle() + { + return groupHandle; + } + public void setGroupHandle(String groupHandle) + { + this.groupHandle = groupHandle; + } +} +