001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. The ASF licenses this file to You
004: * under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License. For additional information regarding
015: * copyright in this work, please see the NOTICE file in the top level
016: * directory of this distribution.
017: */
018:
019: package org.apache.roller.ui.rendering.util.cache;
020:
021: import java.io.UnsupportedEncodingException;
022: import java.net.URLEncoder;
023: import java.util.Date;
024: import java.util.Enumeration;
025: import java.util.HashMap;
026: import java.util.Iterator;
027: import java.util.Map;
028: import java.util.TreeSet;
029: import org.apache.commons.logging.Log;
030: import org.apache.commons.logging.LogFactory;
031: import org.apache.roller.config.RollerConfig;
032: import org.apache.roller.config.RollerRuntimeConfig;
033: import org.apache.roller.pojos.BookmarkData;
034: import org.apache.roller.pojos.CommentData;
035: import org.apache.roller.pojos.FolderData;
036: import org.apache.roller.pojos.RefererData;
037: import org.apache.roller.pojos.UserData;
038: import org.apache.roller.pojos.WeblogCategoryData;
039: import org.apache.roller.pojos.WeblogEntryData;
040: import org.apache.roller.pojos.WeblogTemplate;
041: import org.apache.roller.pojos.WebsiteData;
042: import org.apache.roller.ui.rendering.util.WeblogFeedRequest;
043: import org.apache.roller.ui.rendering.util.WeblogPageRequest;
044: import org.apache.roller.util.Utilities;
045: import org.apache.roller.util.cache.Cache;
046: import org.apache.roller.util.cache.CacheHandler;
047: import org.apache.roller.util.cache.CacheManager;
048: import org.apache.roller.util.cache.ExpiringCacheEntry;
049:
050: /**
051: * Cache for site-wide weblog content.
052: */
053: public class SiteWideCache implements CacheHandler {
054:
055: private static Log log = LogFactory.getLog(SiteWideCache.class);
056:
057: // a unique identifier for this cache, this is used as the prefix for
058: // roller config properties that apply to this cache
059: public static final String CACHE_ID = "cache.sitewide";
060:
061: // keep cached content
062: private boolean cacheEnabled = true;
063: private Cache contentCache = null;
064:
065: // keep a cached version of last expired time
066: private ExpiringCacheEntry lastUpdateTime = null;
067: private long timeout = 15 * 60 * 1000;
068:
069: // reference to our singleton instance
070: private static SiteWideCache singletonInstance = new SiteWideCache();
071:
072: private SiteWideCache() {
073:
074: cacheEnabled = RollerConfig.getBooleanProperty(CACHE_ID
075: + ".enabled");
076:
077: Map cacheProps = new HashMap();
078: cacheProps.put("id", CACHE_ID);
079: Enumeration allProps = RollerConfig.keys();
080: String prop = null;
081: while (allProps.hasMoreElements()) {
082: prop = (String) allProps.nextElement();
083:
084: // we are only interested in props for this cache
085: if (prop.startsWith(CACHE_ID + ".")) {
086: cacheProps.put(prop.substring(CACHE_ID.length() + 1),
087: RollerConfig.getProperty(prop));
088: }
089: }
090:
091: log.info(cacheProps);
092:
093: if (cacheEnabled) {
094: contentCache = CacheManager
095: .constructCache(this , cacheProps);
096: } else {
097: log.warn("Caching has been DISABLED");
098: }
099: }
100:
101: public static SiteWideCache getInstance() {
102: return singletonInstance;
103: }
104:
105: public Object get(String key) {
106:
107: if (!cacheEnabled)
108: return null;
109:
110: Object entry = contentCache.get(key);
111:
112: if (entry == null) {
113: log.debug("MISS " + key);
114: } else {
115: log.debug("HIT " + key);
116: }
117:
118: return entry;
119: }
120:
121: public void put(String key, Object value) {
122:
123: if (!cacheEnabled)
124: return;
125:
126: contentCache.put(key, value);
127: log.debug("PUT " + key);
128: }
129:
130: public void remove(String key) {
131:
132: if (!cacheEnabled)
133: return;
134:
135: contentCache.remove(key);
136: log.debug("REMOVE " + key);
137: }
138:
139: public void clear() {
140:
141: if (!cacheEnabled)
142: return;
143:
144: contentCache.clear();
145: this .lastUpdateTime = null;
146: log.debug("CLEAR");
147: }
148:
149: public Date getLastModified() {
150:
151: Date lastModified = null;
152:
153: // first try our cached version
154: if (this .lastUpdateTime != null) {
155: lastModified = (Date) this .lastUpdateTime.getValue();
156: }
157:
158: // still null, we need to get a fresh value
159: if (lastModified == null) {
160: lastModified = new Date();
161: this .lastUpdateTime = new ExpiringCacheEntry(lastModified,
162: this .timeout);
163: }
164:
165: return lastModified;
166: }
167:
168: /**
169: * Generate a cache key from a parsed weblog page request.
170: * This generates a key of the form ...
171: *
172: * <handle>/<ctx>[/anchor][/language][/user]
173: * or
174: * <handle>/<ctx>[/weblogPage][/date][/category][/tags][/language][/user]
175: *
176: *
177: * examples ...
178: *
179: * foo/en
180: * foo/entry_anchor
181: * foo/20051110/en
182: * foo/MyCategory/en/user=myname
183: *
184: */
185: public String generateKey(WeblogPageRequest pageRequest) {
186:
187: StringBuffer key = new StringBuffer();
188:
189: key.append(this .CACHE_ID).append(":");
190: key.append("page/");
191: key.append(pageRequest.getWeblogHandle());
192:
193: if (pageRequest.getWeblogAnchor() != null) {
194: String anchor = null;
195: try {
196: // may contain spaces or other bad chars
197: anchor = URLEncoder.encode(pageRequest
198: .getWeblogAnchor(), "UTF-8");
199: } catch (UnsupportedEncodingException ex) {
200: // ignored
201: }
202:
203: key.append("/entry/").append(anchor);
204: } else {
205:
206: if (pageRequest.getWeblogPageName() != null) {
207: key.append("/page/").append(
208: pageRequest.getWeblogPageName());
209: }
210:
211: if (pageRequest.getWeblogDate() != null) {
212: key.append("/").append(pageRequest.getWeblogDate());
213: }
214:
215: if (pageRequest.getWeblogCategoryName() != null) {
216: String cat = null;
217: try {
218: // may contain spaces or other bad chars
219: cat = URLEncoder.encode(pageRequest
220: .getWeblogCategoryName(), "UTF-8");
221: } catch (UnsupportedEncodingException ex) {
222: // ignored
223: }
224:
225: key.append("/").append(cat);
226: }
227:
228: if (pageRequest.getTags() != null
229: && pageRequest.getTags().size() > 0) {
230: String[] tags = new String[pageRequest.getTags().size()];
231: new TreeSet(pageRequest.getTags()).toArray(tags);
232: key.append("/tags/").append(
233: Utilities.stringArrayToString(tags, "+"));
234: }
235: }
236:
237: if (pageRequest.getLocale() != null) {
238: key.append("/").append(pageRequest.getLocale());
239: }
240:
241: // add page number when applicable
242: if (pageRequest.getWeblogAnchor() == null) {
243: key.append("/page=").append(pageRequest.getPageNum());
244: }
245:
246: // add login state
247: if (pageRequest.getAuthenticUser() != null) {
248: key.append("/user=").append(pageRequest.getAuthenticUser());
249: }
250:
251: // we allow for arbitrary query params for custom pages
252: if (pageRequest.getCustomParams().size() > 0) {
253: String queryString = paramsToString(pageRequest
254: .getCustomParams());
255:
256: key.append("/qp=").append(queryString);
257: }
258:
259: return key.toString();
260: }
261:
262: /**
263: * Generate a cache key from a parsed weblog feed request.
264: * This generates a key of the form ...
265: *
266: * <handle>/<type>/<format>/[/category][/language][/excerpts]
267: *
268: * examples ...
269: *
270: * foo/entries/rss/en
271: * foo/comments/rss/MyCategory/en
272: * foo/entries/atom/en/excerpts
273: *
274: */
275: public String generateKey(WeblogFeedRequest feedRequest) {
276:
277: StringBuffer key = new StringBuffer();
278:
279: key.append(this .CACHE_ID).append(":");
280: key.append("feed/");
281: key.append(feedRequest.getWeblogHandle());
282:
283: key.append("/").append(feedRequest.getType());
284: key.append("/").append(feedRequest.getFormat());
285:
286: if (feedRequest.getWeblogCategoryName() != null) {
287: String cat = feedRequest.getWeblogCategoryName();
288: try {
289: cat = URLEncoder.encode(cat, "UTF-8");
290: } catch (UnsupportedEncodingException ex) {
291: // should never happen, utf-8 is always supported
292: }
293:
294: key.append("/").append(cat);
295: }
296:
297: if (feedRequest.getLocale() != null) {
298: key.append("/").append(feedRequest.getLocale());
299: }
300:
301: if (feedRequest.isExcerpts()) {
302: key.append("/excerpts");
303: }
304:
305: if (feedRequest.getTags() != null
306: && feedRequest.getTags().size() > 0) {
307: String[] tags = new String[feedRequest.getTags().size()];
308: new TreeSet(feedRequest.getTags()).toArray(tags);
309: key.append("/tags/").append(
310: Utilities.stringArrayToString(tags, "+"));
311: }
312:
313: return key.toString();
314: }
315:
316: /**
317: * A weblog entry has changed.
318: */
319: public void invalidate(WeblogEntryData entry) {
320:
321: if (!cacheEnabled)
322: return;
323:
324: this .contentCache.clear();
325: this .lastUpdateTime = null;
326: }
327:
328: /**
329: * A weblog has changed.
330: */
331: public void invalidate(WebsiteData website) {
332:
333: if (!cacheEnabled)
334: return;
335:
336: this .contentCache.clear();
337: this .lastUpdateTime = null;
338: }
339:
340: /**
341: * A bookmark has changed.
342: */
343: public void invalidate(BookmarkData bookmark) {
344: if (RollerRuntimeConfig.isSiteWideWeblog(bookmark.getWebsite()
345: .getHandle())) {
346: invalidate(bookmark.getWebsite());
347: }
348: }
349:
350: /**
351: * A folder has changed.
352: */
353: public void invalidate(FolderData folder) {
354: if (RollerRuntimeConfig.isSiteWideWeblog(folder.getWebsite()
355: .getHandle())) {
356: invalidate(folder.getWebsite());
357: }
358: }
359:
360: /**
361: * A comment has changed.
362: */
363: public void invalidate(CommentData comment) {
364: if (RollerRuntimeConfig.isSiteWideWeblog(comment
365: .getWeblogEntry().getWebsite().getHandle())) {
366: invalidate(comment.getWeblogEntry().getWebsite());
367: }
368: }
369:
370: /**
371: * A referer has changed.
372: */
373: public void invalidate(RefererData referer) {
374: // ignored
375: }
376:
377: /**
378: * A user profile has changed.
379: */
380: public void invalidate(UserData user) {
381: // ignored
382: }
383:
384: /**
385: * A category has changed.
386: */
387: public void invalidate(WeblogCategoryData category) {
388: if (RollerRuntimeConfig.isSiteWideWeblog(category.getWebsite()
389: .getHandle())) {
390: invalidate(category.getWebsite());
391: }
392: }
393:
394: /**
395: * A weblog template has changed.
396: */
397: public void invalidate(WeblogTemplate template) {
398: if (RollerRuntimeConfig.isSiteWideWeblog(template.getWebsite()
399: .getHandle())) {
400: invalidate(template.getWebsite());
401: }
402: }
403:
404: private String paramsToString(Map map) {
405:
406: if (map == null) {
407: return null;
408: }
409:
410: StringBuffer string = new StringBuffer();
411:
412: String key = null;
413: String[] value = null;
414: Iterator keys = map.keySet().iterator();
415: while (keys.hasNext()) {
416: key = (String) keys.next();
417: value = (String[]) map.get(key);
418:
419: if (value != null) {
420: string.append(",").append(key).append("=").append(
421: value[0]);
422: }
423: }
424:
425: return Utilities.toBase64(string.toString().substring(1)
426: .getBytes());
427: }
428:
429: }
|