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