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