Clover coverage report -
Coverage timestamp: do jan 22 2004 21:12:32 CET
file stats: LOC: 752   Methods: 31
NCLOC: 316   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.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