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