~ubuntu-branches/ubuntu/gutsy/oscache/gutsy

« back to all changes in this revision

Viewing changes to src/core/java/com/opensymphony/oscache/web/ServletCacheAdministrator.java

  • Committer: Bazaar Package Importer
  • Author(s): Kalle Kivimaa
  • Date: 2004-08-13 14:00:00 UTC
  • Revision ID: james.westby@ubuntu.com-20040813140000-lyugvinublk1x8y2
Tags: upstream-2.0.2
ImportĀ upstreamĀ versionĀ 2.0.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
    private ServletCacheAdministrator(ServletContext context, Properties p) {
 
131
        super(p);
 
132
        config.set(HASH_KEY_CONTEXT_TMPDIR, context.getAttribute("javax.servlet.context.tempdir"));
 
133
 
 
134
        //this.context = context;
 
135
        flushTimes = new HashMap();
 
136
        initHostDomainInKey();
 
137
 
 
138
        if (log.isInfoEnabled()) {
 
139
            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
    public static ServletCacheAdministrator getInstance(ServletContext context) {
 
150
        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
    public static ServletCacheAdministrator getInstance(ServletContext context, Properties p) {
 
164
        ServletCacheAdministrator admin = null;
 
165
        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
        if (admin == null) {
 
170
            admin = new ServletCacheAdministrator(context, p);
 
171
            context.setAttribute(CACHE_ADMINISTRATOR_KEY, admin);
 
172
 
 
173
            if (log.isInfoEnabled()) {
 
174
                log.info("Created new instance of ServletCacheAdministrator");
 
175
            }
 
176
 
 
177
            admin.getAppScopeCache(context);
 
178
        }
 
179
 
 
180
        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
    public static void destroyInstance(ServletContext context) {
 
188
        ServletCacheAdministrator admin = null;
 
189
        admin = (ServletCacheAdministrator) context.getAttribute(CACHE_ADMINISTRATOR_KEY);
 
190
 
 
191
        if (admin != null) {
 
192
            // Finalize the application scope cache
 
193
            Cache cache = (Cache) context.getAttribute(admin.getCacheKey());
 
194
 
 
195
            if (cache != null) {
 
196
                admin.finalizeListeners(cache);
 
197
                context.removeAttribute(admin.getCacheKey());
 
198
                context.removeAttribute(CACHE_ADMINISTRATOR_KEY);
 
199
                cache = null;
 
200
 
 
201
                if (log.isInfoEnabled()) {
 
202
                    log.info("Shut down the ServletCacheAdministrator");
 
203
                }
 
204
            }
 
205
 
 
206
            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
    public Cache getCache(HttpServletRequest request, int scope) {
 
219
        if (scope == PageContext.APPLICATION_SCOPE) {
 
220
            return getAppScopeCache(request.getSession(true).getServletContext());
 
221
        }
 
222
 
 
223
        if (scope == PageContext.SESSION_SCOPE) {
 
224
            return getSessionScopeCache(request.getSession(true));
 
225
        }
 
226
 
 
227
        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
    public Cache getAppScopeCache(ServletContext context) {
 
238
        Cache cache = null;
 
239
        Object obj = context.getAttribute(getCacheKey());
 
240
 
 
241
        if ((obj == null) || !(obj instanceof Cache)) {
 
242
            if (log.isInfoEnabled()) {
 
243
                log.info("Created new application-scoped cache at key: " + getCacheKey());
 
244
            }
 
245
 
 
246
            cache = createCache(PageContext.APPLICATION_SCOPE, null);
 
247
            context.setAttribute(getCacheKey(), cache);
 
248
        } else {
 
249
            cache = (Cache) obj;
 
250
        }
 
251
 
 
252
        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
    public Cache getSessionScopeCache(HttpSession session) {
 
263
        Cache cache = null;
 
264
        Object obj = session.getAttribute(getCacheKey());
 
265
 
 
266
        if ((obj == null) || !(obj instanceof Cache)) {
 
267
            if (log.isInfoEnabled()) {
 
268
                log.info("Created new session-scoped cache in session " + session.getId() + " at key: " + getCacheKey());
 
269
            }
 
270
 
 
271
            cache = createCache(PageContext.SESSION_SCOPE, session.getId());
 
272
            session.setAttribute(getCacheKey(), cache);
 
273
        } else {
 
274
            cache = (Cache) obj;
 
275
        }
 
276
 
 
277
        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
    public String getCacheKey() {
 
287
        if (cacheKey == null) {
 
288
            cacheKey = getProperty(CACHE_KEY_KEY);
 
289
 
 
290
            if (cacheKey == null) {
 
291
                cacheKey = DEFAULT_CACHE_KEY;
 
292
            }
 
293
        }
 
294
 
 
295
        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
    public void setFlushTime(Date date, int scope) {
 
305
        if (log.isInfoEnabled()) {
 
306
            log.info("Flushing scope " + scope + " at " + date);
 
307
        }
 
308
 
 
309
        synchronized (flushTimes) {
 
310
            if (date != null) {
 
311
                // Trigger a SCOPE_FLUSHED event
 
312
                dispatchScopeEvent(ScopeEventType.SCOPE_FLUSHED, scope, date, null);
 
313
                flushTimes.put(new Integer(scope), date);
 
314
            } else {
 
315
                logError("setFlushTime called with a null date.");
 
316
                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
    public void setFlushTime(int scope) {
 
327
        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
    public Date getFlushTime(int scope) {
 
338
        synchronized (flushTimes) {
 
339
            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
    public Object getFromCache(int scope, HttpServletRequest request, String key, int refreshPeriod) throws NeedsRefreshException {
 
354
        Cache cache = getCache(request, scope);
 
355
        key = this.generateEntryKey(key, request, scope);
 
356
        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
    public boolean isScopeFlushed(CacheEntry cacheEntry, int scope) {
 
369
        Date flushDateTime = getFlushTime(scope);
 
370
 
 
371
        if (flushDateTime != null) {
 
372
            long lastUpdate = cacheEntry.getLastUpdate();
 
373
            return (flushDateTime.getTime() >= lastUpdate);
 
374
        } else {
 
375
            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
    public void addScopeEventListener(ScopeEventListener listener) {
 
385
        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
    public void cancelUpdate(int scope, HttpServletRequest request, String key) {
 
398
        Cache cache = getCache(request, scope);
 
399
        key = this.generateEntryKey(key, request, scope);
 
400
        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
    public void flushAll(Date date) {
 
409
        synchronized (flushTimes) {
 
410
            setFlushTime(date, PageContext.APPLICATION_SCOPE);
 
411
            setFlushTime(date, PageContext.SESSION_SCOPE);
 
412
            setFlushTime(date, PageContext.REQUEST_SCOPE);
 
413
            setFlushTime(date, PageContext.PAGE_SCOPE);
 
414
        }
 
415
 
 
416
        // Trigger a flushAll event
 
417
        dispatchScopeEvent(ScopeEventType.ALL_SCOPES_FLUSHED, -1, date, null);
 
418
    }
 
419
 
 
420
    /**
 
421
     * Flush all scopes instantly.
 
422
     */
 
423
    public void flushAll() {
 
424
        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
    public String generateEntryKey(String key, HttpServletRequest request, int scope) {
 
445
        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
    public String generateEntryKey(String key, HttpServletRequest request, int scope, String language) {
 
467
        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
    public String generateEntryKey(String key, HttpServletRequest request, int scope, String language, String suffix) {
 
490
        /**
 
491
         * Used for generating cache entry keys.
 
492
         */
 
493
        StringBuffer cBuffer = new StringBuffer(AVERAGE_KEY_LENGTH);
 
494
 
 
495
        // Append the language if available
 
496
        if (language != null) {
 
497
            cBuffer.append(FILE_SEPARATOR).append(language);
 
498
        }
 
499
 
 
500
        // Servers for multiple host domains need this distinction in the key
 
501
        if (useHostDomainInKey) {
 
502
            cBuffer.append(FILE_SEPARATOR).append(request.getServerName());
 
503
        }
 
504
 
 
505
        if (key != null) {
 
506
            cBuffer.append(FILE_SEPARATOR).append(key);
 
507
        } else {
 
508
            String generatedKey = request.getRequestURI();
 
509
 
 
510
            if (generatedKey.charAt(0) != FILE_SEPARATOR_CHAR) {
 
511
                cBuffer.append(FILE_SEPARATOR_CHAR);
 
512
            }
 
513
 
 
514
            cBuffer.append(generatedKey);
 
515
            cBuffer.append("_").append(request.getMethod()).append("_");
 
516
 
 
517
            generatedKey = getSortedQueryString(request);
 
518
 
 
519
            if (generatedKey != null) {
 
520
                try {
 
521
                    java.security.MessageDigest digest = java.security.MessageDigest.getInstance("MD5");
 
522
                    byte[] b = digest.digest(generatedKey.getBytes());
 
523
                    cBuffer.append("_");
 
524
 
 
525
                    // Base64 encoding allows for unwanted slash characters.
 
526
                    cBuffer.append(toBase64(b).replace('/', '_'));
 
527
                } catch (Exception e) {
 
528
                    // Ignore query string
 
529
                }
 
530
            }
 
531
        }
 
532
 
 
533
        // Do we want a suffix
 
534
        if ((suffix != null) && (suffix.length() > 0)) {
 
535
            cBuffer.append(suffix);
 
536
        }
 
537
 
 
538
        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
    protected String getSortedQueryString(HttpServletRequest request) {
 
550
        Map paramMap = request.getParameterMap();
 
551
 
 
552
        if (paramMap.isEmpty()) {
 
553
            return null;
 
554
        }
 
555
 
 
556
        Set paramSet = new TreeMap(paramMap).entrySet();
 
557
 
 
558
        StringBuffer buf = new StringBuffer();
 
559
 
 
560
        boolean first = true;
 
561
 
 
562
        for (Iterator it = paramSet.iterator(); it.hasNext();) {
 
563
            Map.Entry entry = (Map.Entry) it.next();
 
564
            String[] values = (String[]) entry.getValue();
 
565
 
 
566
            for (int i = 0; i < values.length; i++) {
 
567
                String key = (String) entry.getKey();
 
568
 
 
569
                if ((key.length() != 10) || !"jsessionid".equals(key)) {
 
570
                    if (first) {
 
571
                        first = false;
 
572
                    } else {
 
573
                        buf.append('&');
 
574
                    }
 
575
 
 
576
                    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
        if (buf.length() == 0) {
 
583
            return null;
 
584
        } else {
 
585
            return buf.toString();
 
586
        }
 
587
    }
 
588
 
 
589
    /**
 
590
     * Log error messages to commons logging.
 
591
     *
 
592
     * @param message   Message to log.
 
593
     */
 
594
    public void logError(String message) {
 
595
        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
    public void putInCache(int scope, HttpServletRequest request, String key, Object content) {
 
607
        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
    public void putInCache(int scope, HttpServletRequest request, String key, Object content, EntryRefreshPolicy policy) {
 
620
        Cache cache = getCache(request, scope);
 
621
        key = this.generateEntryKey(key, request, scope);
 
622
        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
    public void setCacheCapacity(int scope, HttpServletRequest request, int capacity) {
 
635
        setCacheCapacity(capacity);
 
636
        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
    public void removeScopeEventListener(ScopeEventListener listener) {
 
645
        listenerList.remove(ScopeEventListener.class, listener);
 
646
    }
 
647
 
 
648
    /**
 
649
     * Finalizes all the listeners that are associated with the given cache object
 
650
     */
 
651
    protected void finalizeListeners(Cache cache) {
 
652
        super.finalizeListeners(cache);
 
653
    }
 
654
 
 
655
    /**
 
656
     * Convert a byte array into a Base64 string (as used in mime formats)
 
657
     */
 
658
    private static String toBase64(byte[] aValue) {
 
659
        int byte1;
 
660
        int byte2;
 
661
        int byte3;
 
662
        int iByteLen = aValue.length;
 
663
        StringBuffer tt = new StringBuffer();
 
664
 
 
665
        for (int i = 0; i < iByteLen; i += 3) {
 
666
            boolean bByte2 = (i + 1) < iByteLen;
 
667
            boolean bByte3 = (i + 2) < iByteLen;
 
668
            byte1 = aValue[i] & 0xFF;
 
669
            byte2 = (bByte2) ? (aValue[i + 1] & 0xFF) : 0;
 
670
            byte3 = (bByte3) ? (aValue[i + 2] & 0xFF) : 0;
 
671
 
 
672
            tt.append(m_strBase64Chars.charAt(byte1 / 4));
 
673
            tt.append(m_strBase64Chars.charAt((byte2 / 16) + ((byte1 & 0x3) * 16)));
 
674
            tt.append(((bByte2) ? m_strBase64Chars.charAt((byte3 / 64) + ((byte2 & 0xF) * 4)) : '='));
 
675
            tt.append(((bByte3) ? m_strBase64Chars.charAt(byte3 & 0x3F) : '='));
 
676
        }
 
677
 
 
678
        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
    private ServletCache createCache(int scope, String sessionId) {
 
689
        if (log.isInfoEnabled()) {
 
690
            log.info("Created new cache in scope " + scope);
 
691
        }
 
692
 
 
693
        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
        config.set(HASH_KEY_SCOPE, "" + scope);
 
700
        config.set(HASH_KEY_SESSION_ID, sessionId);
 
701
 
 
702
        newCache = (ServletCache) configureStandardListeners(newCache);
 
703
 
 
704
        if (config.getProperty(CACHE_ENTRY_EVENT_LISTENERS) != null) {
 
705
            // Add any event listeners that have been specified in the configuration
 
706
            CacheEventListener[] listeners = getCacheEventListeners();
 
707
 
 
708
            for (int i = 0; i < listeners.length; i++) {
 
709
                if (listeners[i] instanceof ScopeEventListener) {
 
710
                    newCache.addCacheEventListener(listeners[i], ScopeEventListener.class);
 
711
                }
 
712
            }
 
713
        }
 
714
 
 
715
        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
    private void dispatchScopeEvent(ScopeEventType eventType, int scope, Date date, String origin) {
 
727
        // Create the event
 
728
        ScopeEvent event = new ScopeEvent(eventType, scope, date, origin);
 
729
 
 
730
        // Guaranteed to return a non-null array
 
731
        Object[] listeners = listenerList.getListenerList();
 
732
 
 
733
        // Process the listeners last to first, notifying
 
734
        // those that are interested in this event
 
735
        for (int i = listeners.length - 2; i >= 0; i -= 2) {
 
736
            if (listeners[i] == ScopeEventListener.class) {
 
737
                ((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
    private void initHostDomainInKey() {
 
747
        String propStr = getProperty(CACHE_USE_HOST_DOMAIN_KEY);
 
748
 
 
749
        useHostDomainInKey = "true".equalsIgnoreCase(propStr);
 
750
    }
 
751
}