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