File Source: SiteWideCache.java
1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. The ASF licenses this file to You
4 * under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License. For additional information regarding
15 * copyright in this work, please see the NOTICE file in the top level
16 * directory of this distribution.
17 */
18
19 package org.apache.roller.weblogger.ui.rendering.util.cache;
20
21 import java.io.UnsupportedEncodingException;
22 import java.net.URLEncoder;
23 import java.util.Date;
24 import java.util.Enumeration;
25 import java.util.HashMap;
26 import java.util.Iterator;
27 import java.util.Map;
28 import java.util.Set;
29 import java.util.TreeSet;
30 import org.apache.commons.logging.Log;
31 import org.apache.commons.logging.LogFactory;
32 import org.apache.roller.weblogger.config.WebloggerConfig;
33 import org.apache.roller.weblogger.config.WebloggerRuntimeConfig;
34 import org.apache.roller.weblogger.pojos.WeblogBookmark;
35 import org.apache.roller.weblogger.pojos.WeblogEntryComment;
36 import org.apache.roller.weblogger.pojos.WeblogBookmarkFolder;
37 import org.apache.roller.weblogger.pojos.WeblogReferrer;
38 import org.apache.roller.weblogger.pojos.User;
39 import org.apache.roller.weblogger.pojos.WeblogCategory;
40 import org.apache.roller.weblogger.pojos.WeblogEntry;
41 import org.apache.roller.weblogger.pojos.WeblogTemplate;
42 import org.apache.roller.weblogger.pojos.Weblog;
43 import org.apache.roller.weblogger.ui.rendering.util.WeblogFeedRequest;
44 import org.apache.roller.weblogger.ui.rendering.util.WeblogPageRequest;
45 import org.apache.roller.weblogger.util.Utilities;
46 import org.apache.roller.weblogger.util.cache.Cache;
47 import org.apache.roller.weblogger.util.cache.CacheHandler;
48 import org.apache.roller.weblogger.util.cache.CacheManager;
49 import org.apache.roller.weblogger.util.cache.ExpiringCacheEntry;
50
51
52 /**
53 * Cache for site-wide weblog content.
54 */
55 public class SiteWideCache implements CacheHandler {
56
/*
P/P * Method: org.apache.roller.weblogger.ui.rendering.util.cache.SiteWideCache__static_init
*
* Presumptions:
* org.apache.commons.logging.LogFactory:getLog(...)@57 != null
*
* Postconditions:
* (soft) log != null
* singletonInstance == &new SiteWideCache(SiteWideCache__static_init#1)
* new SiteWideCache(SiteWideCache__static_init#1) num objects == 1
* init'ed(singletonInstance.cacheEnabled)
* init'ed(singletonInstance.contentCache)
* singletonInstance.lastUpdateTime == null
* singletonInstance.timeout == 900_000
*/
57 private static Log log = LogFactory.getLog(SiteWideCache.class);
58
59 // a unique identifier for this cache, this is used as the prefix for
60 // roller config properties that apply to this cache
61 public static final String CACHE_ID = "cache.sitewide";
62
63 // keep cached content
64 private boolean cacheEnabled = true;
65 private Cache contentCache = null;
66
67 // keep a cached version of last expired time
68 private ExpiringCacheEntry lastUpdateTime = null;
69 private long timeout = 15 * 60 * 1000;
70
71 // reference to our singleton instance
72 private static SiteWideCache singletonInstance = new SiteWideCache();
73
74
/*
P/P * Method: void org.apache.roller.weblogger.ui.rendering.util.cache.SiteWideCache()
*
* Preconditions:
* log != null
*
* Presumptions:
* java.lang.String:length(...)@88 <= 232-2
* java.util.Enumeration:nextElement(...)@84 != null
* org.apache.roller.weblogger.config.WebloggerConfig:keys(...)@81 != null
*
* Postconditions:
* init'ed(this.cacheEnabled)
* init'ed(this.contentCache)
* this.lastUpdateTime == null
* this.timeout == 900_000
*
* Test Vectors:
* java.lang.String:startsWith(...)@87: {0}, {1}
* java.util.Enumeration:hasMoreElements(...)@83: {0}, {1}
* org.apache.roller.weblogger.config.WebloggerConfig:getBooleanProperty(...)@77: {0}, {1}
*/
75 private SiteWideCache() {
76
77 cacheEnabled = WebloggerConfig.getBooleanProperty(CACHE_ID+".enabled");
78
79 Map cacheProps = new HashMap();
80 cacheProps.put("id", CACHE_ID);
81 Enumeration allProps = WebloggerConfig.keys();
82 String prop = null;
83 while(allProps.hasMoreElements()) {
84 prop = (String) allProps.nextElement();
85
86 // we are only interested in props for this cache
87 if(prop.startsWith(CACHE_ID+".")) {
88 cacheProps.put(prop.substring(CACHE_ID.length()+1),
89 WebloggerConfig.getProperty(prop));
90 }
91 }
92
93 log.info(cacheProps);
94
95 if(cacheEnabled) {
96 contentCache = CacheManager.constructCache(this, cacheProps);
97 } else {
98 log.warn("Caching has been DISABLED");
99 }
100 }
101
102
103 public static SiteWideCache getInstance() {
/*
P/P * Method: SiteWideCache getInstance()
*
* Preconditions:
* init'ed(singletonInstance)
*
* Postconditions:
* return_value == singletonInstance
* init'ed(return_value)
*/
104 return singletonInstance;
105 }
106
107
108 public Object get(String key) {
109
/*
P/P * Method: Object get(String)
*
* Preconditions:
* init'ed(this.cacheEnabled)
* (soft) log != null
* (soft) this.contentCache != null
*
* Postconditions:
* init'ed(return_value)
*
* Test Vectors:
* this.cacheEnabled: {1}, {0}
* org.apache.roller.weblogger.util.cache.Cache:get(...)@113: Inverse{null}, Addr_Set{null}
*/
110 if(!cacheEnabled)
111 return null;
112
113 Object entry = contentCache.get(key);
114
115 if(entry == null) {
116 log.debug("MISS "+key);
117 } else {
118 log.debug("HIT "+key);
119 }
120
121 return entry;
122 }
123
124
125 public void put(String key, Object value) {
126
/*
P/P * Method: void put(String, Object)
*
* Preconditions:
* init'ed(this.cacheEnabled)
* (soft) log != null
* (soft) this.contentCache != null
*
* Test Vectors:
* this.cacheEnabled: {1}, {0}
*/
127 if(!cacheEnabled)
128 return;
129
130 contentCache.put(key, value);
131 log.debug("PUT "+key);
132 }
133
134
135 public void remove(String key) {
136
/*
P/P * Method: void remove(String)
*
* Preconditions:
* init'ed(this.cacheEnabled)
* (soft) log != null
* (soft) this.contentCache != null
*
* Test Vectors:
* this.cacheEnabled: {1}, {0}
*/
137 if(!cacheEnabled)
138 return;
139
140 contentCache.remove(key);
141 log.debug("REMOVE "+key);
142 }
143
144
145 public void clear() {
146
/*
P/P * Method: void clear()
*
* Preconditions:
* init'ed(this.cacheEnabled)
* (soft) log != null
* (soft) this.contentCache != null
*
* Postconditions:
* this.lastUpdateTime == One-of{old this.lastUpdateTime, null}
*
* Test Vectors:
* this.cacheEnabled: {1}, {0}
*/
147 if(!cacheEnabled)
148 return;
149
150 contentCache.clear();
151 this.lastUpdateTime = null;
152 log.debug("CLEAR");
153 }
154
155
156 public Date getLastModified() {
157
/*
P/P * Method: Date getLastModified()
*
* Preconditions:
* init'ed(this.lastUpdateTime)
* (soft) init'ed(this.timeout)
*
* Postconditions:
* return_value != null
* this.lastUpdateTime == One-of{old this.lastUpdateTime, &new ExpiringCacheEntry(getLastModified#2)}
* this.lastUpdateTime != null
* new Date(getLastModified#1) num objects <= 1
* new ExpiringCacheEntry(getLastModified#2) num objects <= 1
*
* Test Vectors:
* this.lastUpdateTime: Addr_Set{null}, Inverse{null}
*/
158 Date lastModified = null;
159
160 // first try our cached version
161 if(this.lastUpdateTime != null) {
162 lastModified = (Date) this.lastUpdateTime.getValue();
163 }
164
165 // still null, we need to get a fresh value
166 if(lastModified == null) {
167 lastModified = new Date();
168 this.lastUpdateTime = new ExpiringCacheEntry(lastModified, this.timeout);
169 }
170
171 return lastModified;
172 }
173
174
175 /**
176 * Generate a cache key from a parsed weblog page request.
177 * This generates a key of the form ...
178 *
179 * <handle>/<ctx>[/anchor][/language][/user]
180 * or
181 * <handle>/<ctx>[/weblogPage][/date][/category][/tags][/language][/user]
182 *
183 *
184 * examples ...
185 *
186 * foo/en
187 * foo/entry_anchor
188 * foo/20051110/en
189 * foo/MyCategory/en/user=myname
190 *
191 */
192 public String generateKey(WeblogPageRequest pageRequest) {
193
/*
P/P * Method: String generateKey(WeblogPageRequest)
*
* Preconditions:
* pageRequest != null
* pageRequest.customParams != null
* init'ed(pageRequest.locale)
* init'ed(pageRequest.weblogAnchor)
* init'ed(pageRequest.weblogHandle)
* (soft) init'ed(pageRequest.authenticUser)
* (soft) init'ed(pageRequest.context)
* (soft) init'ed(pageRequest.pageNum)
* (soft) init'ed(pageRequest.tags)
* (soft) init'ed(pageRequest.weblogCategoryName)
* ...
*
* Presumptions:
* java.util.Set:size(...)@236 >= 0
*
* Postconditions:
* init'ed(java.lang.StringBuffer:toString(...)._tainted)
* return_value == &java.lang.StringBuffer:toString(...)
*
* Test Vectors:
* pageRequest.locale: Addr_Set{null}, Inverse{null}
* pageRequest.tags: Addr_Set{null}, Inverse{null}
* pageRequest.weblogAnchor: Addr_Set{null}, Inverse{null}
* pageRequest.weblogCategoryName: Addr_Set{null}, Inverse{null}
* pageRequest.weblogDate: Addr_Set{null}, Inverse{null}
* pageRequest.weblogPageName: Addr_Set{null}, Inverse{null}
* java.lang.String:equals(...)@232: {0}, {1}
* java.util.List:size(...)@234: {-231..0}, {1..232-1}
* java.util.Map:size(...)@257: {-231..0}, {1..232-1}
*/
194 StringBuffer key = new StringBuffer();
195
196 key.append(this.CACHE_ID).append(":");
197 key.append("page/");
198 key.append(pageRequest.getWeblogHandle());
199
200 if(pageRequest.getWeblogAnchor() != null) {
201 String anchor = null;
202 try {
203 // may contain spaces or other bad chars
204 anchor = URLEncoder.encode(pageRequest.getWeblogAnchor(), "UTF-8");
205 } catch(UnsupportedEncodingException ex) {
206 // ignored
207 }
208
209 key.append("/entry/").append(anchor);
210 } else {
211
212 if(pageRequest.getWeblogPageName() != null) {
213 key.append("/page/").append(pageRequest.getWeblogPageName());
214 }
215
216 if(pageRequest.getWeblogDate() != null) {
217 key.append("/").append(pageRequest.getWeblogDate());
218 }
219
220 if(pageRequest.getWeblogCategoryName() != null) {
221 String cat = null;
222 try {
223 // may contain spaces or other bad chars
224 cat = URLEncoder.encode(pageRequest.getWeblogCategoryName(), "UTF-8");
225 } catch(UnsupportedEncodingException ex) {
226 // ignored
227 }
228
229 key.append("/").append(cat);
230 }
231
232 if("tags".equals(pageRequest.getContext())) {
233 key.append("/tags/");
234 if(pageRequest.getTags() != null && pageRequest.getTags().size() > 0) {
235 Set ordered = new TreeSet(pageRequest.getTags());
236 String[] tags = (String[]) ordered.toArray(new String[ordered.size()]);
237 key.append(Utilities.stringArrayToString(tags,"+"));
238 }
239 }
240 }
241
242 if(pageRequest.getLocale() != null) {
243 key.append("/").append(pageRequest.getLocale());
244 }
245
246 // add page number when applicable
247 if(pageRequest.getWeblogAnchor() == null) {
248 key.append("/page=").append(pageRequest.getPageNum());
249 }
250
251 // add login state
252 if(pageRequest.getAuthenticUser() != null) {
253 key.append("/user=").append(pageRequest.getAuthenticUser());
254 }
255
256 // we allow for arbitrary query params for custom pages
257 if(pageRequest.getCustomParams().size() > 0) {
258 String queryString = paramsToString(pageRequest.getCustomParams());
259
260 key.append("/qp=").append(queryString);
261 }
262
263 return key.toString();
264 }
265
266
267 /**
268 * Generate a cache key from a parsed weblog feed request.
269 * This generates a key of the form ...
270 *
271 * <handle>/<type>/<format>/[/category][/language][/excerpts]
272 *
273 * examples ...
274 *
275 * foo/entries/rss/en
276 * foo/comments/rss/MyCategory/en
277 * foo/entries/atom/en/excerpts
278 *
279 */
280 public String generateKey(WeblogFeedRequest feedRequest) {
281
/*
P/P * Method: String generateKey(WeblogFeedRequest)
*
* Preconditions:
* feedRequest != null
* init'ed(feedRequest.excerpts)
* init'ed(feedRequest.format)
* init'ed(feedRequest.locale)
* init'ed(feedRequest.tags)
* init'ed(feedRequest.type)
* init'ed(feedRequest.weblogCategoryName)
* init'ed(feedRequest.weblogHandle)
*
* Presumptions:
* java.util.List:size(...)@311 >= 0
*
* Postconditions:
* init'ed(java.lang.StringBuffer:toString(...)._tainted)
* return_value == &java.lang.StringBuffer:toString(...)
*
* Test Vectors:
* feedRequest.excerpts: {0}, {1}
* feedRequest.locale: Addr_Set{null}, Inverse{null}
* feedRequest.tags: Addr_Set{null}, Inverse{null}
* feedRequest.weblogCategoryName: Addr_Set{null}, Inverse{null}
* java.util.List:size(...)@310: {-231..0}, {1..232-1}
*/
282 StringBuffer key = new StringBuffer();
283
284 key.append(this.CACHE_ID).append(":");
285 key.append("feed/");
286 key.append(feedRequest.getWeblogHandle());
287
288 key.append("/").append(feedRequest.getType());
289 key.append("/").append(feedRequest.getFormat());
290
291 if(feedRequest.getWeblogCategoryName() != null) {
292 String cat = feedRequest.getWeblogCategoryName();
293 try {
294 cat = URLEncoder.encode(cat, "UTF-8");
295 } catch (UnsupportedEncodingException ex) {
296 // should never happen, utf-8 is always supported
297 }
298
299 key.append("/").append(cat);
300 }
301
302 if(feedRequest.getLocale() != null) {
303 key.append("/").append(feedRequest.getLocale());
304 }
305
306 if(feedRequest.isExcerpts()) {
307 key.append("/excerpts");
308 }
309
310 if(feedRequest.getTags() != null && feedRequest.getTags().size() > 0) {
311 String[] tags = new String[feedRequest.getTags().size()];
312 new TreeSet(feedRequest.getTags()).toArray(tags);
313 key.append("/tags/").append(Utilities.stringArrayToString(tags,"+"));
314 }
315
316 return key.toString();
317 }
318
319
320 /**
321 * A weblog entry has changed.
322 */
323 public void invalidate(WeblogEntry entry) {
324
/*
P/P * Method: void invalidate(WeblogEntry)
*
* Preconditions:
* init'ed(this.cacheEnabled)
* (soft) this.contentCache != null
*
* Postconditions:
* this.lastUpdateTime == One-of{old this.lastUpdateTime, null}
*
* Test Vectors:
* this.cacheEnabled: {1}, {0}
*/
325 if(!cacheEnabled)
326 return;
327
328 this.contentCache.clear();
329 this.lastUpdateTime = null;
330 }
331
332
333 /**
334 * A weblog has changed.
335 */
336 public void invalidate(Weblog website) {
337
/*
P/P * Method: void invalidate(Weblog)
*
* Preconditions:
* init'ed(this.cacheEnabled)
* (soft) this.contentCache != null
*
* Postconditions:
* this.lastUpdateTime == One-of{old this.lastUpdateTime, null}
*
* Test Vectors:
* this.cacheEnabled: {1}, {0}
*/
338 if(!cacheEnabled)
339 return;
340
341 this.contentCache.clear();
342 this.lastUpdateTime = null;
343 }
344
345
346 /**
347 * A bookmark has changed.
348 */
349 public void invalidate(WeblogBookmark bookmark) {
/*
P/P * Method: void invalidate(WeblogBookmark)
*
* Preconditions:
* bookmark != null
* (soft) init'ed(this.cacheEnabled)
* (soft) this.contentCache != null
*
* Presumptions:
* org.apache.roller.weblogger.pojos.WeblogBookmark:getWebsite(...)@350 != null
*
* Postconditions:
* this.lastUpdateTime == One-of{old this.lastUpdateTime, null}
*
* Test Vectors:
* org.apache.roller.weblogger.config.WebloggerRuntimeConfig:isSiteWideWeblog(...)@350: {0}, {1}
*/
350 if(WebloggerRuntimeConfig.isSiteWideWeblog(bookmark.getWebsite().getHandle())) {
351 invalidate(bookmark.getWebsite());
352 }
353 }
354
355
356 /**
357 * A folder has changed.
358 */
359 public void invalidate(WeblogBookmarkFolder folder) {
/*
P/P * Method: void invalidate(WeblogBookmarkFolder)
*
* Preconditions:
* folder != null
* (soft) init'ed(this.cacheEnabled)
* (soft) this.contentCache != null
*
* Presumptions:
* org.apache.roller.weblogger.pojos.WeblogBookmarkFolder:getWebsite(...)@360 != null
*
* Postconditions:
* this.lastUpdateTime == One-of{old this.lastUpdateTime, null}
*
* Test Vectors:
* org.apache.roller.weblogger.config.WebloggerRuntimeConfig:isSiteWideWeblog(...)@360: {0}, {1}
*/
360 if(WebloggerRuntimeConfig.isSiteWideWeblog(folder.getWebsite().getHandle())) {
361 invalidate(folder.getWebsite());
362 }
363 }
364
365
366 /**
367 * A comment has changed.
368 */
369 public void invalidate(WeblogEntryComment comment) {
/*
P/P * Method: void invalidate(WeblogEntryComment)
*
* Preconditions:
* comment != null
* (soft) init'ed(this.cacheEnabled)
* (soft) this.contentCache != null
*
* Presumptions:
* org.apache.roller.weblogger.pojos.WeblogEntry:getWebsite(...)@370 != null
* org.apache.roller.weblogger.pojos.WeblogEntryComment:getWeblogEntry(...)@370 != null
* org.apache.roller.weblogger.pojos.WeblogEntryComment:getWeblogEntry(...)@371 != null
*
* Postconditions:
* this.lastUpdateTime == One-of{old this.lastUpdateTime, null}
*
* Test Vectors:
* org.apache.roller.weblogger.config.WebloggerRuntimeConfig:isSiteWideWeblog(...)@370: {0}, {1}
*/
370 if(WebloggerRuntimeConfig.isSiteWideWeblog(comment.getWeblogEntry().getWebsite().getHandle())) {
371 invalidate(comment.getWeblogEntry().getWebsite());
372 }
373 }
374
375
376 /**
377 * A referer has changed.
378 */
379 public void invalidate(WeblogReferrer referer) {
380 // ignored
/*
P/P * Method: void invalidate(WeblogReferrer)
*/
381 }
382
383
384 /**
385 * A user profile has changed.
386 */
387 public void invalidate(User user) {
388 // ignored
/*
P/P * Method: void invalidate(User)
*/
389 }
390
391
392 /**
393 * A category has changed.
394 */
395 public void invalidate(WeblogCategory category) {
/*
P/P * Method: void invalidate(WeblogCategory)
*
* Preconditions:
* category != null
* (soft) init'ed(this.cacheEnabled)
* (soft) this.contentCache != null
*
* Presumptions:
* org.apache.roller.weblogger.pojos.WeblogCategory:getWebsite(...)@396 != null
*
* Postconditions:
* this.lastUpdateTime == One-of{old this.lastUpdateTime, null}
*
* Test Vectors:
* org.apache.roller.weblogger.config.WebloggerRuntimeConfig:isSiteWideWeblog(...)@396: {0}, {1}
*/
396 if(WebloggerRuntimeConfig.isSiteWideWeblog(category.getWebsite().getHandle())) {
397 invalidate(category.getWebsite());
398 }
399 }
400
401
402 /**
403 * A weblog template has changed.
404 */
405 public void invalidate(WeblogTemplate template) {
/*
P/P * Method: void invalidate(WeblogTemplate)
*
* Preconditions:
* template != null
* (soft) init'ed(this.cacheEnabled)
* (soft) this.contentCache != null
*
* Presumptions:
* org.apache.roller.weblogger.pojos.WeblogTemplate:getWebsite(...)@406 != null
*
* Postconditions:
* this.lastUpdateTime == One-of{old this.lastUpdateTime, null}
*
* Test Vectors:
* org.apache.roller.weblogger.config.WebloggerRuntimeConfig:isSiteWideWeblog(...)@406: {0}, {1}
*/
406 if(WebloggerRuntimeConfig.isSiteWideWeblog(template.getWebsite().getHandle())) {
407 invalidate(template.getWebsite());
408 }
409 }
410
411
412 private String paramsToString(Map map) {
413
/*
P/P * Method: String paramsToString(Map)
*
* Presumptions:
* java.util.Map:keySet(...)@422 != null
* value.length@425 >= 1
*
* Postconditions:
* init'ed(return_value)
*
* Test Vectors:
* map: Inverse{null}, Addr_Set{null}
* java.util.Iterator:hasNext(...)@423: {0}, {1}
* java.util.Map:get(...)@425: Addr_Set{null}, Inverse{null}
*/
414 if(map == null) {
415 return null;
416 }
417
418 StringBuffer string = new StringBuffer();
419
420 String key = null;
421 String[] value = null;
422 Iterator keys = map.keySet().iterator();
423 while(keys.hasNext()) {
424 key = (String) keys.next();
425 value = (String[]) map.get(key);
426
427 if(value != null) {
428 string.append(",").append(key).append("=").append(value[0]);
429 }
430 }
431
432 return Utilities.toBase64(string.toString().substring(1).getBytes());
433 }
434
435 }
SofCheck Inspector Build Version : 2.18479
| SiteWideCache.java |
2009-Jan-02 14:25:14 |
| SiteWideCache.class |
2009-Sep-04 03:12:45 |