Clover coverage report - Coverage timestamp: Sa Jul 7 2007 09:11:40 CEST FRAMES NO FRAMES |
file stats: | LOC: | 806 |  | Methods: | 32
| NCLOC: | 349 |  | Classes: | 1
Source file | Conditionals | Statements | Methods | TOTAL
ServletCacheAdministrator.java | 0% | 0% | 0% | 0% |

1 |  | /*
2 |  | * Copyright (c) 2002-2003 by OpenSymphony
3 |  | * All rights reserved.
4 |  | */
5 |  | package com.opensymphony.oscache.web;
6 |  |
7 |  | import com.opensymphony.oscache.base.*;
8 |  | import com.opensymphony.oscache.base.events.CacheEventListener;
9 |  | import com.opensymphony.oscache.base.events.ScopeEvent;
10 |  | import com.opensymphony.oscache.base.events.ScopeEventListener;
11 |  | import com.opensymphony.oscache.base.events.ScopeEventType;
12 |  |
13 |  | import org.apache.commons.logging.Log;
14 |  | import org.apache.commons.logging.LogFactory;
15 |  |
16 |  | import java.io.Serializable;
17 |  |
18 |  | import java.util.*;
19 |  |
20 |  | import javax.servlet.ServletContext;
21 |  | import javax.servlet.http.HttpServletRequest;
22 |  | import javax.servlet.http.HttpSession;
23 |  | import javax.servlet.jsp.PageContext;
24 |  |
25 |  | /**
26 |  | * A ServletCacheAdministrator creates, flushes and administers the cache.
27 |  | * <p>
28 |  | * This is a "servlet Singleton". This means it's not a Singleton in the traditional sense,
29 |  | * that is stored in a static instance. It's a Singleton _per web app context_.
30 |  | * <p>
31 |  | * Once created it manages the cache path on disk through the oscache.properties
32 |  | * file, and also keeps track of the flush times.
33 |  | *
34 |  | * @author <a href="mailto:mike@atlassian.com">Mike Cannon-Brookes</a>
35 |  | * @author <a href="mailto:tgochenour@peregrine.com">Todd Gochenour</a>
36 |  | * @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
37 |  | * @author <a href="mailto:abergevin@pyxis-tech.com">Alain Bergevin</a>
38 |  | * @author <a href="&#109;a&#105;&#108;&#116;&#111;:chris&#64;swebtec.&#99;&#111;&#109;">Chris Miller</a>
39 |  | * @version $Revision: 463 $
40 |  | */
41 |  | public class ServletCacheAdministrator extends AbstractCacheAdministrator implements Serializable {
42 |  | private static final transient Log log = LogFactory.getLog(ServletCacheAdministrator.class);
43 |  |
44 |  | /**
45 |  | * Constants for properties read/written from/to file
46 |  | */
47 |  | private final static String CACHE_USE_HOST_DOMAIN_KEY = "cache.use.host.domain.in.key";
48 |  | private final static String CACHE_KEY_KEY = "cache.key";
49 |  |
50 |  | /**
51 |  | * The default cache key that is used to store the cache in context.
52 |  | */
53 |  | private final static String DEFAULT_CACHE_KEY = "__oscache_cache";
54 |  |
55 |  | /**
56 |  | * Constants for scope's name
57 |  | */
58 |  | public final static String SESSION_SCOPE_NAME = "session";
59 |  | public final static String APPLICATION_SCOPE_NAME = "application";
60 |  |
61 |  | /**
62 |  | * The suffix added to the cache key used to store a
63 |  | * ServletCacheAdministrator will be stored in the ServletContext
64 |  | */
65 |  | private final static String CACHE_ADMINISTRATOR_KEY_SUFFIX = "_admin";
66 |  |
67 |  | /**
68 |  | * The key under which an array of all ServletCacheAdministrator objects
69 |  | * will be stored in the ServletContext
70 |  | */
71 |  | private final static String CACHE_ADMINISTRATORS_KEY = "__oscache_admins";
72 |  |
73 |  | /**
74 |  | * Key used to store the current scope in the configuration. This is a hack
75 |  | * to let the scope information get passed through to the DiskPersistenceListener,
76 |  | * and will be removed in a future release.
77 |  | */
78 |  | public final static String HASH_KEY_SCOPE = "scope";
79 |  |
80 |  | /**
81 |  | * Key used to store the current session ID in the configuration. This is a hack
82 |  | * to let the scope information get passed through to the DiskPersistenceListener,
83 |  | * and will be removed in a future release.
84 |  | */
85 |  | public final static String HASH_KEY_SESSION_ID = "sessionId";
86 |  |
87 |  | /**
88 |  | * Key used to store the servlet container temporary directory in the configuration.
89 |  | * This is a hack to let the scope information get passed through to the
90 |  | * DiskPersistenceListener, and will be removed in a future release.
91 |  | */
92 |  | public final static String HASH_KEY_CONTEXT_TMPDIR = "context.tempdir";
93 |  |
94 |  | /**
95 |  | * The string to use as a file separator.
96 |  | */
97 |  | private final static String FILE_SEPARATOR = "/";
98 |  |
99 |  | /**
100 |  | * The character to use as a file separator.
101 |  | */
102 |  | private final static char FILE_SEPARATOR_CHAR = FILE_SEPARATOR.charAt(0);
103 |  |
104 |  | /**
105 |  | * Constant for Key generation.
106 |  | */
107 |  | private final static short AVERAGE_KEY_LENGTH = 30;
108 |  |
109 |  | /**
110 |  | * Usable caracters for key generation
111 |  | */
112 |  | private static final String m_strBase64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
113 |  |
114 |  | /**
115 |  | * Map containing the flush times of different scopes
116 |  | */
117 |  | private Map flushTimes;
118 |  |
119 |  | /**
120 |  | * Required so we can look up the app scope cache without forcing a session creation.
121 |  | */
122 |  | private transient ServletContext context;
123 |  |
124 |  | /**
125 |  | * Key to use for storing and retrieving Object in contexts (Servlet, session).
126 |  | */
127 |  | private String cacheKey;
128 |  |
129 |  | /**
130 |  | * Set property cache.use.host.domain.in.key=true to add domain information to key
131 |  | * generation for hosting multiple sites.
132 |  | */
133 |  | private boolean useHostDomainInKey = false;
134 |  |
135 |  | /**
136 |  | * Create the cache administrator.
137 |  | *
138 |  | * This will reset all the flush times and load the properties file.
139 |  | */
140 | 0 | private ServletCacheAdministrator(ServletContext context, Properties p) {
141 | 0 | super(p);
142 | 0 | config.set(HASH_KEY_CONTEXT_TMPDIR, context.getAttribute("javax.servlet.context.tempdir"));
143 |  |
144 | 0 | flushTimes = new HashMap();
145 | 0 | initHostDomainInKey();
146 | 0 | this.context = context;
147 |  | }
148 |  |
149 |  | /**
150 |  | * Obtain an instance of the CacheAdministrator
151 |  | *
152 |  | * @param context The ServletContext that this CacheAdministrator is a Singleton under
153 |  | * @return Returns the CacheAdministrator instance for this context
154 |  | */
155 | 0 | public static ServletCacheAdministrator getInstance(ServletContext context) {
156 | 0 | return getInstance(context, null);
157 |  | }
158 |  |
159 |  | /**
160 |  | * Obtain an instance of the CacheAdministrator for the specified key
161 |  | *
162 |  | * @param context The ServletContext that this CacheAdministrator is a Singleton under
163 |  | * @param key the cachekey or admincachekey for the CacheAdministrator wanted
164 |  | * @return Returns the CacheAdministrator instance for this context, or null if no
165 |  | * CacheAdministrator exists with the key supplied
166 |  | */
167 | 0 | public static ServletCacheAdministrator getInstanceFromKey(ServletContext context, String key) {
168 |  | // Note we do not bother to check if the key is null because it mustn't.
169 | 0 | if (!key.endsWith(CACHE_ADMINISTRATOR_KEY_SUFFIX)) {
170 | 0 | key = key + CACHE_ADMINISTRATOR_KEY_SUFFIX;
171 |  | }
172 | 0 | return (ServletCacheAdministrator) context.getAttribute(key);
173 |  | }
174 |  |
175 |  | /**
176 |  | * Obtain an instance of the CacheAdministrator
177 |  | *
178 |  | * @param context The ServletContext that this CacheAdministrator is a Singleton under
179 |  | * @param p the properties to use for the cache if the cache administrator has not been
180 |  | * created yet. Once the administrator has been created, the properties parameter is
181 |  | * ignored for all future invocations. If a null value is passed in, then the properties
182 |  | * are loaded from the oscache.properties file in the classpath.
183 |  | * @return Returns the CacheAdministrator instance for this context
184 |  | */
185 | 0 | public synchronized static ServletCacheAdministrator getInstance(ServletContext context, Properties p)
186 |  | {
187 | 0 | String adminKey = null;
188 | 0 | if (p!= null) {
189 | 0 | adminKey = p.getProperty(CACHE_KEY_KEY);
190 |  | }
191 | 0 | if (adminKey == null) {
192 | 0 | adminKey = DEFAULT_CACHE_KEY;
193 |  | }
194 | 0 | adminKey += CACHE_ADMINISTRATOR_KEY_SUFFIX;
195 |  |
196 | 0 | ServletCacheAdministrator admin = (ServletCacheAdministrator) context.getAttribute(adminKey);
197 |  |
198 |  | // First time we need to create the administrator and store it in the
199 |  | // servlet context
200 | 0 | if (admin == null) {
201 | 0 | admin = new ServletCacheAdministrator(context, p);
202 | 0 | Map admins = (Map) context.getAttribute(CACHE_ADMINISTRATORS_KEY);
203 | 0 | if (admins == null) {
204 | 0 | admins = new HashMap();
205 |  | }
206 | 0 | admins.put(adminKey, admin);
207 | 0 | context.setAttribute(CACHE_ADMINISTRATORS_KEY, admins);
208 | 0 | context.setAttribute(adminKey, admin);
209 |  |
210 | 0 | if (log.isInfoEnabled()) {
211 | 0 | log.info("Created new instance of ServletCacheAdministrator with key "+adminKey);
212 |  | }
213 |  |
214 | 0 | admin.getAppScopeCache(context);
215 |  | }
216 |  |
217 | 0 | if (admin.context == null) {
218 | 0 | admin.context = context;
219 |  | }
220 |  |
221 | 0 | return admin;
222 |  | }
223 |  |
224 |  | /**
225 |  | * Shuts down all servlet cache administrators. This should usually only
226 |  | * be called when the controlling application shuts down.
227 |  | */
228 | 0 | public static void destroyInstance(ServletContext context)
229 |  | {
230 | 0 | ServletCacheAdministrator admin;
231 | 0 | Map admins = (Map) context.getAttribute(CACHE_ADMINISTRATORS_KEY);
232 | 0 | if (admins != null)
233 |  | {
234 | 0 | Set keys = admins.keySet();
235 | 0 | Iterator it = keys.iterator();
236 | 0 | while (it.hasNext())
237 |  | {
238 | 0 | String adminKey = (String) it.next();
239 | 0 | admin = (ServletCacheAdministrator) admins.get( adminKey );
240 | 0 | if (admin != null)
241 |  | {
242 |  | // Finalize the application scope cache
243 | 0 | Cache cache = (Cache) context.getAttribute(admin.getCacheKey());
244 | 0 | if (cache != null) {
245 | 0 | admin.finalizeListeners(cache);
246 | 0 | context.removeAttribute(admin.getCacheKey());
247 | 0 | context.removeAttribute(adminKey);
248 | 0 | cache = null;
249 | 0 | if (log.isInfoEnabled()) {
250 | 0 | log.info("Shut down the ServletCacheAdministrator "+adminKey);
251 |  | }
252 |  | }
253 | 0 | admin = null;
254 |  | }
255 |  | }
256 | 0 | context.removeAttribute(CACHE_ADMINISTRATORS_KEY);
257 |  | }
258 |  | }
259 |  |
260 |  |
261 |  | /**
262 |  | * Grabs the cache for the specified scope
263 |  | *
264 |  | * @param request The current request
265 |  | * @param scope The scope of this cache (<code>PageContext.APPLICATION_SCOPE</code>
266 |  | * or <code>PageContext.SESSION_SCOPE</code>)
267 |  | * @return The cache
268 |  | */
269 | 0 | public Cache getCache(HttpServletRequest request, int scope) {
270 | 0 | if (scope == PageContext.APPLICATION_SCOPE) {
271 | 0 | return getAppScopeCache(context);
272 |  | }
273 |  |
274 | 0 | if (scope == PageContext.SESSION_SCOPE) {
275 | 0 | return getSessionScopeCache(request.getSession(true));
276 |  | }
277 |  |
278 | 0 | throw new RuntimeException("The supplied scope value of " + scope + " is invalid. Acceptable values are PageContext.APPLICATION_SCOPE and PageContext.SESSION_SCOPE");
279 |  | }
280 |  |
281 |  | /**
282 |  | * A convenience method to retrieve the application scope cache
283 |  |
284 |  | * @param context the current <code>ServletContext</code>
285 |  | * @return the application scope cache. If none is present, one will
286 |  | * be created.
287 |  | */
288 | 0 | public Cache getAppScopeCache(ServletContext context) {
289 | 0 | Cache cache;
290 | 0 | Object obj = context.getAttribute(getCacheKey());
291 |  |
292 | 0 | if ((obj == null) || !(obj instanceof Cache)) {
293 | 0 | if (log.isInfoEnabled()) {
294 | 0 | log.info("Created new application-scoped cache at key: " + getCacheKey());
295 |  | }
296 |  |
297 | 0 | cache = createCache(PageContext.APPLICATION_SCOPE, null);
298 | 0 | context.setAttribute(getCacheKey(), cache);
299 |  | } else {
300 | 0 | cache = (Cache) obj;
301 |  | }
302 |  |
303 | 0 | return cache;
304 |  | }
305 |  |
306 |  | /**
307 |  | * A convenience method to retrieve the session scope cache
308 |  | *
309 |  | * @param session the current <code>HttpSession</code>
310 |  | * @return the session scope cache for this session. If none is present,
311 |  | * one will be created.
312 |  | */
313 | 0 | public Cache getSessionScopeCache(HttpSession session) {
314 | 0 | Cache cache;
315 | 0 | Object obj = session.getAttribute(getCacheKey());
316 |  |
317 | 0 | if ((obj == null) || !(obj instanceof Cache)) {
318 | 0 | if (log.isInfoEnabled()) {
319 | 0 | log.info("Created new session-scoped cache in session " + session.getId() + " at key: " + getCacheKey());
320 |  | }
321 |  |
322 | 0 | cache = createCache(PageContext.SESSION_SCOPE, session.getId());
323 | 0 | session.setAttribute(getCacheKey(), cache);
324 |  | } else {
325 | 0 | cache = (Cache) obj;
326 |  | }
327 |  |
328 | 0 | return cache;
329 |  | }
330 |  |
331 |  | /**
332 |  | * Get the cache key from the properties. Set it to a default value if it
333 |  | * is not present in the properties
334 |  | *
335 |  | * @return The cache.key property or the DEFAULT_CACHE_KEY
336 |  | */
337 | 0 | public String getCacheKey() {
338 | 0 | if (cacheKey == null) {
339 | 0 | cacheKey = getProperty(CACHE_KEY_KEY);
340 |  |
341 | 0 | if (cacheKey == null) {
342 | 0 | cacheKey = DEFAULT_CACHE_KEY;
343 |  | }
344 |  | }
345 |  |
346 | 0 | return cacheKey;
347 |  | }
348 |  |
349 |  | /**
350 |  | * Set the flush time for a specific scope to a specific time
351 |  | *
352 |  | * @param date The time to flush the scope
353 |  | * @param scope The scope to be flushed
354 |  | */
355 | 0 | public void setFlushTime(Date date, int scope) {
356 | 0 | if (log.isInfoEnabled()) {
357 | 0 | log.info("Flushing scope " + scope + " at " + date);
358 |  | }
359 |  |
360 | 0 | synchronized (flushTimes) {
361 | 0 | if (date != null) {
362 |  | // Trigger a SCOPE_FLUSHED event
363 | 0 | dispatchScopeEvent(ScopeEventType.SCOPE_FLUSHED, scope, date, null);
364 | 0 | flushTimes.put(new Integer(scope), date);
365 |  | } else {
366 | 0 | logError("setFlushTime called with a null date.");
367 | 0 | throw new IllegalArgumentException("setFlushTime called with a null date.");
368 |  | }
369 |  | }
370 |  | }
371 |  |
372 |  | /**
373 |  | * Set the flush time for a specific scope to the current time.
374 |  | *
375 |  | * @param scope The scope to be flushed
376 |  | */
377 | 0 | public void setFlushTime(int scope) {
378 | 0 | setFlushTime(new Date(), scope);
379 |  | }
380 |  |
381 |  | /**
382 |  | * Get the flush time for a particular scope.
383 |  | *
384 |  | * @param scope The scope to get the flush time for.
385 |  | * @return A date representing the time this scope was last flushed.
386 |  | * Returns null if it has never been flushed.
387 |  | */
388 | 0 | public Date getFlushTime(int scope) {
389 | 0 | synchronized (flushTimes) {
390 | 0 | return (Date) flushTimes.get(new Integer(scope));
391 |  | }
392 |  | }
393 |  |
394 |  | /**
395 |  | * Retrieve an item from the cache
396 |  | *
397 |  | * @param scope The cache scope
398 |  | * @param request The servlet request
399 |  | * @param key The key of the object to retrieve
400 |  | * @param refreshPeriod The time interval specifying if an entry needs refresh
401 |  | * @return The requested object
402 |  | * @throws NeedsRefreshException
403 |  | */
404 | 0 | public Object getFromCache(int scope, HttpServletRequest request, String key, int refreshPeriod) throws NeedsRefreshException {
405 | 0 | Cache cache = getCache(request, scope);
406 | 0 | key = this.generateEntryKey(key, request, scope);
407 | 0 | return cache.getFromCache(key, refreshPeriod);
408 |  | }
409 |  |
410 |  | /**
411 |  | * Checks if the given scope was flushed more recently than the CacheEntry provided.
412 |  | * Used to determine whether to refresh the particular CacheEntry.
413 |  | *
414 |  | * @param cacheEntry The cache entry which we're seeing whether to refresh
415 |  | * @param scope The scope we're checking
416 |  | *
417 |  | * @return Whether or not the scope has been flushed more recently than this cache entry was updated.
418 |  | */
419 | 0 | public boolean isScopeFlushed(CacheEntry cacheEntry, int scope) {
420 | 0 | Date flushDateTime = getFlushTime(scope);
421 |  |
422 | 0 | if (flushDateTime != null) {
423 | 0 | long lastUpdate = cacheEntry.getLastUpdate();
424 | 0 | return (flushDateTime.getTime() >= lastUpdate);
425 |  | } else {
426 | 0 | return false;
427 |  | }
428 |  | }
429 |  |
430 |  | /**
431 |  | * Register a listener for Cache Map events.
432 |  | *
433 |  | * @param listener The object that listens to events.
434 |  | */
435 | 0 | public void addScopeEventListener(ScopeEventListener listener) {
436 | 0 | listenerList.add(ScopeEventListener.class, listener);
437 |  | }
438 |  |
439 |  | /**
440 |  | * Cancels a pending cache update. This should only be called by a thread
441 |  | * that received a {@link NeedsRefreshException} and was unable to generate
442 |  | * some new cache content.
443 |  | *
444 |  | * @param scope The cache scope
445 |  | * @param request The servlet request
446 |  | * @param key The cache entry key to cancel the update of.
447 |  | */
448 | 0 | public void cancelUpdate(int scope, HttpServletRequest request, String key) {
449 | 0 | Cache cache = getCache(request, scope);
450 | 0 | key = this.generateEntryKey(key, request, scope);
451 | 0 | cache.cancelUpdate(key);
452 |  | }
453 |  |
454 |  | /**
455 |  | * Flush all scopes at a particular time
456 |  | *
457 |  | * @param date The time to flush the scope
458 |  | */
459 | 0 | public void flushAll(Date date) {
460 | 0 | synchronized (flushTimes) {
461 | 0 | setFlushTime(date, PageContext.APPLICATION_SCOPE);
462 | 0 | setFlushTime(date, PageContext.SESSION_SCOPE);
463 | 0 | setFlushTime(date, PageContext.REQUEST_SCOPE);
464 | 0 | setFlushTime(date, PageContext.PAGE_SCOPE);
465 |  | }
466 |  |
467 |  | // Trigger a flushAll event
468 | 0 | dispatchScopeEvent(ScopeEventType.ALL_SCOPES_FLUSHED, -1, date, null);
469 |  | }
470 |  |
471 |  | /**
472 |  | * Flush all scopes instantly.
473 |  | */
474 | 0 | public void flushAll() {
475 | 0 | flushAll(new Date());
476 |  | }
477 |  |
478 |  | /**
479 |  | * Generates a cache entry key.
480 |  | *
481 |  | * If the string key is not specified, the HTTP request URI and QueryString is used.
482 |  | * Operating systems that have a filename limitation less than 255 or have
483 |  | * filenames that are case insensitive may have issues with key generation where
484 |  | * two distinct pages map to the same key.
485 |  | * <p>
486 |  | * POST Requests (which have no distinguishing
487 |  | * query string) may also generate identical keys for what is actually different pages.
488 |  | * In these cases, specify an explicit key attribute for the CacheTag.
489 |  | *
490 |  | * @param key The key entered by the user
491 |  | * @param request The current request
492 |  | * @param scope The scope this cache entry is under
493 |  | * @return The generated cache key
494 |  | */
495 | 0 | public String generateEntryKey(String key, HttpServletRequest request, int scope) {
496 | 0 | return generateEntryKey(key, request, scope, null, null);
497 |  | }
498 |  |
499 |  | /**
500 |  | * Generates a cache entry key.
501 |  | *
502 |  | * If the string key is not specified, the HTTP request URI and QueryString is used.
503 |  | * Operating systems that have a filename limitation less than 255 or have
504 |  | * filenames that are case insensitive may have issues with key generation where
505 |  | * two distinct pages map to the same key.
506 |  | * <p>
507 |  | * POST Requests (which have no distinguishing
508 |  | * query string) may also generate identical keys for what is actually different pages.
509 |  | * In these cases, specify an explicit key attribute for the CacheTag.
510 |  | *
511 |  | * @param key The key entered by the user
512 |  | * @param request The current request
513 |  | * @param scope The scope this cache entry is under
514 |  | * @param language The ISO-639 language code to distinguish different pages in application scope
515 |  | * @return The generated cache key
516 |  | */
517 | 0 | public String generateEntryKey(String key, HttpServletRequest request, int scope, String language) {
518 | 0 | return generateEntryKey(key, request, scope, language, null);
519 |  | }
520 |  |
521 |  | /**
522 |  | * Generates a cache entry key.
523 |  | * <p>
524 |  | * If the string key is not specified, the HTTP request URI and QueryString is used.
525 |  | * Operating systems that have a filename limitation less than 255 or have
526 |  | * filenames that are case insensitive may have issues with key generation where
527 |  | * two distinct pages map to the same key.
528 |  | * <p>
529 |  | * POST Requests (which have no distinguishing
530 |  | * query string) may also generate identical keys for what is actually different pages.
531 |  | * In these cases, specify an explicit key attribute for the CacheTag.
532 |  | *
533 |  | * @param key The key entered by the user
534 |  | * @param request The current request
535 |  | * @param scope The scope this cache entry is under
536 |  | * @param language The ISO-639 language code to distinguish different pages in application scope
537 |  | * @param suffix The ability to put a suffix at the end of the key
538 |  | * @return The generated cache key
539 |  | */
540 | 0 | public String generateEntryKey(String key, HttpServletRequest request, int scope, String language, String suffix) {
541 |  | /**
542 |  | * Used for generating cache entry keys.
543 |  | */
544 | 0 | StringBuffer cBuffer = new StringBuffer(AVERAGE_KEY_LENGTH);
545 |  |
546 |  | // Append the language if available
547 | 0 | if (language != null) {
548 | 0 | cBuffer.append(FILE_SEPARATOR).append(language);
549 |  | }
550 |  |
551 |  | // Servers for multiple host domains need this distinction in the key
552 | 0 | if (useHostDomainInKey) {
553 | 0 | cBuffer.append(FILE_SEPARATOR).append(request.getServerName());
554 |  | }
555 |  |
556 | 0 | if (key != null) {
557 | 0 | cBuffer.append(FILE_SEPARATOR).append(key);
558 |  | } else {
559 | 0 | String generatedKey = request.getRequestURI();
560 |  |
561 | 0 | if (generatedKey.charAt(0) != FILE_SEPARATOR_CHAR) {
562 | 0 | cBuffer.append(FILE_SEPARATOR_CHAR);
563 |  | }
564 |  |
565 | 0 | cBuffer.append(generatedKey);
566 | 0 | cBuffer.append("_").append(request.getMethod()).append("_");
567 |  |
568 | 0 | generatedKey = getSortedQueryString(request);
569 |  |
570 | 0 | if (generatedKey != null) {
571 | 0 | try {
572 | 0 | java.security.MessageDigest digest = java.security.MessageDigest.getInstance("MD5");
573 | 0 | byte[] b = digest.digest(generatedKey.getBytes());
574 | 0 | cBuffer.append('_');
575 |  |
576 |  | // Base64 encoding allows for unwanted slash characters.
577 | 0 | cBuffer.append(toBase64(b).replace('/', '_'));
578 |  | } catch (Exception e) {
579 |  | // Ignore query string
580 |  | }
581 |  | }
582 |  | }
583 |  |
584 |  | // Do we want a suffix
585 | 0 | if ((suffix != null) && (suffix.length() > 0)) {
586 | 0 | cBuffer.append(suffix);
587 |  | }
588 |  |
589 | 0 | return cBuffer.toString();
590 |  | }
591 |  |
592 |  | /**
593 |  | * Creates a string that contains all of the request parameters and their
594 |  | * values in a single string. This is very similar to
595 |  | * <code>HttpServletRequest.getQueryString()</code> except the parameters are
596 |  | * sorted by name, and if there is a <code>jsessionid</code> parameter it is
597 |  | * filtered out.<p>
598 |  | * If the request has no parameters, this method returns <code>null</code>.
599 |  | */
600 | 0 | protected String getSortedQueryString(HttpServletRequest request) {
601 | 0 | Map paramMap = request.getParameterMap();
602 |  |
603 | 0 | if (paramMap.isEmpty()) {
604 | 0 | return null;
605 |  | }
606 |  |
607 | 0 | Set paramSet = new TreeMap(paramMap).entrySet();
608 |  |
609 | 0 | StringBuffer buf = new StringBuffer();
610 |  |
611 | 0 | boolean first = true;
612 |  |
613 | 0 | for (Iterator it = paramSet.iterator(); it.hasNext();) {
614 | 0 | Map.Entry entry = (Map.Entry) it.next();
615 | 0 | String[] values = (String[]) entry.getValue();
616 |  |
617 | 0 | for (int i = 0; i < values.length; i++) {
618 | 0 | String key = (String) entry.getKey();
619 |  |
620 | 0 | if ((key.length() != 10) || !"jsessionid".equals(key)) {
621 | 0 | if (first) {
622 | 0 | first = false;
623 |  | } else {
624 | 0 | buf.append('&');
625 |  | }
626 |  |
627 | 0 | buf.append(key).append('=').append(values[i]);
628 |  | }
629 |  | }
630 |  | }
631 |  |
632 |  | // We get a 0 length buffer if the only parameter was a jsessionid
633 | 0 | if (buf.length() == 0) {
634 | 0 | return null;
635 |  | } else {
636 | 0 | return buf.toString();
637 |  | }
638 |  | }
639 |  |
640 |  | /**
641 |  | * Log error messages to commons logging.
642 |  | *
643 |  | * @param message Message to log.
644 |  | */
645 | 0 | public void logError(String message) {
646 | 0 | log.error("[oscache]: " + message);
647 |  | }
648 |  |
649 |  | /**
650 |  | * Put an object in the cache. This should only be called by a thread
651 |  | * that received a {@link NeedsRefreshException}. Using session scope
652 |  | * the thread has to insure that the session wasn't invalidated in
653 |  | * the meantime. CacheTag and CacheFilter guarantee that the same
654 |  | * cache is used in cancelUpdate and getFromCache.
655 |  | *
656 |  | * @param scope The cache scope
657 |  | * @param request The servlet request
658 |  | * @param key The object key
659 |  | * @param content The object to add
660 |  | */
661 | 0 | public void putInCache(int scope, HttpServletRequest request, String key, Object content) {
662 | 0 | putInCache(scope, request, key, content, null);
663 |  | }
664 |  |
665 |  | /**
666 |  | * Put an object in the cache. This should only be called by a thread
667 |  | * that received a {@link NeedsRefreshException}. Using session scope
668 |  | * the thread has to insure that the session wasn't invalidated in
669 |  | * the meantime. CacheTag and CacheFilter guarantee that the same
670 |  | * cache is used in cancelUpdate and getFromCache.
671 |  | *
672 |  | * @param scope The cache scope
673 |  | * @param request The servlet request
674 |  | * @param key The object key
675 |  | * @param content The object to add
676 |  | * @param policy The refresh policy
677 |  | */
678 | 0 | public void putInCache(int scope, HttpServletRequest request, String key, Object content, EntryRefreshPolicy policy) {
679 | 0 | Cache cache = getCache(request, scope);
680 | 0 | key = this.generateEntryKey(key, request, scope);
681 | 0 | cache.putInCache(key, content, policy);
682 |  | }
683 |  |
684 |  | /**
685 |  | * Sets the cache capacity (number of items). If the cache contains
686 |  | * more than <code>capacity</code> items then items will be removed
687 |  | * to bring the cache back down to the new size.
688 |  | *
689 |  | * @param scope The cache scope
690 |  | * @param request The servlet request
691 |  | * @param capacity The new capacity
692 |  | */
693 | 0 | public void setCacheCapacity(int scope, HttpServletRequest request, int capacity) {
694 | 0 | setCacheCapacity(capacity);
695 | 0 | getCache(request, scope).setCapacity(capacity);
696 |  | }
697 |  |
698 |  | /**
699 |  | * Unregister a listener for Cache Map events.
700 |  | *
701 |  | * @param listener The object that currently listens to events.
702 |  | */
703 | 0 | public void removeScopeEventListener(ScopeEventListener listener) {
704 | 0 | listenerList.remove(ScopeEventListener.class, listener);
705 |  | }
706 |  |
707 |  | /**
708 |  | * Finalizes all the listeners that are associated with the given cache object
709 |  | */
710 | 0 | protected void finalizeListeners(Cache cache) {
711 | 0 | super.finalizeListeners(cache);
712 |  | }
713 |  |
714 |  | /**
715 |  | * Convert a byte array into a Base64 string (as used in mime formats)
716 |  | */
717 | 0 | private static String toBase64(byte[] aValue) {
718 | 0 | int byte1;
719 | 0 | int byte2;
720 | 0 | int byte3;
721 | 0 | int iByteLen = aValue.length;
722 | 0 | StringBuffer tt = new StringBuffer();
723 |  |
724 | 0 | for (int i = 0; i < iByteLen; i += 3) {
725 | 0 | boolean bByte2 = (i + 1) < iByteLen;
726 | 0 | boolean bByte3 = (i + 2) < iByteLen;
727 | 0 | byte1 = aValue[i] & 0xFF;
728 | 0 | byte2 = (bByte2) ? (aValue[i + 1] & 0xFF) : 0;
729 | 0 | byte3 = (bByte3) ? (aValue[i + 2] & 0xFF) : 0;
730 |  |
731 | 0 | tt.append(m_strBase64Chars.charAt(byte1 / 4));
732 | 0 | tt.append(m_strBase64Chars.charAt((byte2 / 16) + ((byte1 & 0x3) * 16)));
733 | 0 | tt.append(((bByte2) ? m_strBase64Chars.charAt((byte3 / 64) + ((byte2 & 0xF) * 4)) : '='));
734 | 0 | tt.append(((bByte3) ? m_strBase64Chars.charAt(byte3 & 0x3F) : '='));
735 |  | }
736 |  |
737 | 0 | return tt.toString();
738 |  | }
739 |  |
740 |  | /**
741 |  | * Create a cache
742 |  | *
743 |  | * @param scope The cache scope
744 |  | * @param sessionId The sessionId for with the cache will be created
745 |  | * @return A new cache
746 |  | */
747 | 0 | private ServletCache createCache(int scope, String sessionId) {
748 | 0 | ServletCache newCache = new ServletCache(this, algorithmClass, cacheCapacity, scope);
749 |  |
750 |  | // TODO - Fix me please!
751 |  | // Hack! This is nasty - if two sessions are created within a short
752 |  | // space of time it is possible they will end up with duplicate
753 |  | // session IDs being passed to the DiskPersistenceListener!...
754 | 0 | config.set(HASH_KEY_SCOPE, "" + scope);
755 | 0 | config.set(HASH_KEY_SESSION_ID, sessionId);
756 |  |
757 | 0 | newCache = (ServletCache) configureStandardListeners(newCache);
758 |  |
759 | 0 | if (config.getProperty(CACHE_ENTRY_EVENT_LISTENERS_KEY) != null) {
760 |  | // Add any event listeners that have been specified in the configuration
761 | 0 | CacheEventListener[] listeners = getCacheEventListeners();
762 |  |
763 | 0 | for (int i = 0; i < listeners.length; i++) {
764 | 0 | if (listeners[i] instanceof ScopeEventListener) {
765 | 0 | newCache.addCacheEventListener(listeners[i]);
766 |  | }
767 |  | }
768 |  | }
769 |  |
770 | 0 | return newCache;
771 |  | }
772 |  |
773 |  | /**
774 |  | * Dispatch a scope event to all registered listeners.
775 |  | *
776 |  | * @param eventType The type of event
777 |  | * @param scope Scope that was flushed (Does not apply for FLUSH_ALL event)
778 |  | * @param date Date of flushing
779 |  | * @param origin The origin of the event
780 |  | */
781 | 0 | private void dispatchScopeEvent(ScopeEventType eventType, int scope, Date date, String origin) {
782 |  | // Create the event
783 | 0 | ScopeEvent event = new ScopeEvent(eventType, scope, date, origin);
784 |  |
785 |  | // Guaranteed to return a non-null array
786 | 0 | Object[] listeners = listenerList.getListenerList();
787 |  |
788 |  | // Process the listeners last to first, notifying
789 |  | // those that are interested in this event
790 | 0 | for (int i = listeners.length - 2; i >= 0; i -= 2) {
791 | 0 | if (listeners[i+1] instanceof ScopeEventListener) {
792 | 0 | ((ScopeEventListener) listeners[i + 1]).scopeFlushed(event);
793 |  | }
794 |  | }
795 |  | }
796 |  |
797 |  | /**
798 |  | * Set property cache.use.host.domain.in.key=true to add domain information to key
799 |  | * generation for hosting multiple sites
800 |  | */
801 | 0 | private void initHostDomainInKey() {
802 | 0 | String propStr = getProperty(CACHE_USE_HOST_DOMAIN_KEY);
803 |  |
804 | 0 | useHostDomainInKey = "true".equalsIgnoreCase(propStr);
805 |  | }
806 |  | }