~ubuntu-branches/ubuntu/oneiric/ehcache/oneiric

« back to all changes in this revision

Viewing changes to core/src/main/java/net/sf/ehcache/store/LruMemoryStore.java

  • Committer: Bazaar Package Importer
  • Author(s): Torsten Werner
  • Date: 2010-06-23 10:35:31 UTC
  • mfrom: (1.1.5 upstream) (2.1.6 sid)
  • Revision ID: james.westby@ubuntu.com-20100623103531-ra0qdpmotoz6ygct
Tags: 2.1.0-1
Merge changes from Thierry's PPA and upload to Debian.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/**
2
 
 *  Copyright 2003-2009 Terracotta, Inc.
3
 
 *
4
 
 *  Licensed under the Apache License, Version 2.0 (the "License");
5
 
 *  you may not use this file except in compliance with the License.
6
 
 *  You may obtain a copy of the License at
7
 
 *
8
 
 *      http://www.apache.org/licenses/LICENSE-2.0
9
 
 *
10
 
 *  Unless required by applicable law or agreed to in writing, software
11
 
 *  distributed under the License is distributed on an "AS IS" BASIS,
12
 
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 
 *  See the License for the specific language governing permissions and
14
 
 *  limitations under the License.
15
 
 */
16
 
 
17
 
package net.sf.ehcache.store;
18
 
 
19
 
import net.sf.ehcache.CacheException;
20
 
import net.sf.ehcache.Ehcache;
21
 
import net.sf.ehcache.Element;
22
 
import net.sf.ehcache.Status;
23
 
 
24
 
import java.util.Iterator;
25
 
import java.util.Map;
26
 
import java.util.logging.Level;
27
 
import java.util.logging.Logger;
28
 
 
29
 
 
30
 
/**
31
 
 * An implementation of a LruMemoryStore.
32
 
 * <p/>
33
 
 * This uses {@link java.util.LinkedHashMap} as its backing map. It uses the {@link java.util.LinkedHashMap} LRU
34
 
 * feature. LRU for this implementation means least recently accessed.
35
 
 *
36
 
 * @author <a href="mailto:gluck@thoughtworks.com">Greg Luck</a>
37
 
 * @version $Id$
38
 
 */
39
 
public class LruMemoryStore implements Store {
40
 
 
41
 
    private static final Logger LOG = Logger.getLogger(LruMemoryStore.class.getName());
42
 
 
43
 
    /**
44
 
     * The cache this store is associated with.
45
 
     */
46
 
    protected Ehcache cache;
47
 
 
48
 
    /**
49
 
     * Map where items are stored by key.
50
 
     */
51
 
    protected Map map;
52
 
 
53
 
    /**
54
 
     * The DiskStore associated with this MemoryStore.
55
 
     */
56
 
    protected final Store diskStore;
57
 
 
58
 
    /**
59
 
     * status.
60
 
     */
61
 
    protected Status status;
62
 
 
63
 
    /**
64
 
     * The maximum size of the store
65
 
     */
66
 
    protected int maximumSize;
67
 
 
68
 
 
69
 
    /**
70
 
     * Constructor for the LruMemoryStore object
71
 
     * The backing {@link java.util.LinkedHashMap} is created with LRU by access order.
72
 
     */
73
 
    public LruMemoryStore(Ehcache cache, Store diskStore) {
74
 
        status = Status.STATUS_UNINITIALISED;
75
 
        this.maximumSize = cache.getCacheConfiguration().getMaxElementsInMemory();
76
 
        this.cache = cache;
77
 
        this.diskStore = diskStore;
78
 
        map = new SpoolingLinkedHashMap();
79
 
        status = Status.STATUS_ALIVE;
80
 
    }
81
 
 
82
 
 
83
 
    /**
84
 
     * Puts an item in the cache. Note that this automatically results in
85
 
     * {@link net.sf.ehcache.store.LruMemoryStore.SpoolingLinkedHashMap#removeEldestEntry} being called.
86
 
     *
87
 
     * @param element the element to add
88
 
     */
89
 
    public final synchronized void put(Element element) throws CacheException {
90
 
        if (element != null) {
91
 
            map.put(element.getObjectKey(), element);
92
 
            doPut(element);
93
 
        }
94
 
    }
95
 
 
96
 
    /**
97
 
     * Allow specialised actions over adding the element to the map.
98
 
     *
99
 
     * @param element
100
 
     */
101
 
    protected void doPut(Element element) throws CacheException {
102
 
        //empty
103
 
    }
104
 
 
105
 
    /**
106
 
     * Gets an item from the cache.
107
 
     * <p/>
108
 
     * The last access time in {@link net.sf.ehcache.Element} is updated.
109
 
     *
110
 
     * @param key the cache key
111
 
     * @return the element, or null if there was no match for the key
112
 
     */
113
 
    public final synchronized Element get(Object key) {
114
 
        Element element = (Element) map.get(key);
115
 
 
116
 
        if (element != null) {
117
 
            element.updateAccessStatistics();
118
 
        }
119
 
        return element;
120
 
    }
121
 
 
122
 
    /**
123
 
     * Gets an item from the cache, without updating statistics.
124
 
     *
125
 
     * @param key the cache key
126
 
     * @return the element, or null if there was no match for the key
127
 
     */
128
 
    public final synchronized Element getQuiet(Object key) {
129
 
        Element cacheElement = (Element) map.get(key);
130
 
 
131
 
//        if (cacheElement != null) {
132
 
            //cacheElement.updateAccessStatistics(); Don't update statistics
133
 
//        }
134
 
        return cacheElement;
135
 
    }
136
 
 
137
 
 
138
 
    /**
139
 
     * Removes an Element from the store.
140
 
     *
141
 
     * @param key the key of the Element, usually a String
142
 
     * @return the Element if one was found, else null
143
 
     */
144
 
    public final synchronized Element remove(Object key) {
145
 
 
146
 
        // remove single item.
147
 
        Element element = (Element) map.remove(key);
148
 
        if (element != null) {
149
 
            return element;
150
 
        } else {
151
 
            return null;
152
 
        }
153
 
    }
154
 
 
155
 
    /**
156
 
     * Remove all of the elements from the store.
157
 
     */
158
 
    public final synchronized void removeAll() throws CacheException {
159
 
        clear();
160
 
    }
161
 
 
162
 
    /**
163
 
     * Clears any data structures and places it back to its state when it was first created.
164
 
     */
165
 
    protected final void clear() {
166
 
        map.clear();
167
 
    }
168
 
 
169
 
    /**
170
 
     * Prepares for shutdown.
171
 
     */
172
 
    public final synchronized void dispose() {
173
 
        if (status.equals(Status.STATUS_SHUTDOWN)) {
174
 
            return;
175
 
        }
176
 
        status = Status.STATUS_SHUTDOWN;
177
 
        flush();
178
 
 
179
 
        //release reference to cache
180
 
        cache = null;
181
 
    }
182
 
 
183
 
 
184
 
    /**
185
 
     * Flush to disk only if the cache is diskPersistent.
186
 
     */
187
 
    public final void flush() {
188
 
        if (cache.getCacheConfiguration().isDiskPersistent()) {
189
 
            if (LOG.isLoggable(Level.FINE)) {
190
 
                LOG.log(Level.FINE, cache.getName() + " is persistent. Spooling " + map.size() + " elements to the disk store.");
191
 
            }
192
 
            spoolAllToDisk();
193
 
        }
194
 
 
195
 
        //should be emptied if clearOnFlush is true
196
 
        if (cache.getCacheConfiguration().isClearOnFlush()) {
197
 
            clear();
198
 
        }
199
 
    }
200
 
 
201
 
 
202
 
    /**
203
 
     * Spools all elements to disk, in preparation for shutdown.
204
 
     * <p/>
205
 
     * This revised implementation is a little slower but avoids using increased memory during the method.
206
 
     */
207
 
    protected final void spoolAllToDisk() {
208
 
        boolean clearOnFlush = cache.getCacheConfiguration().isClearOnFlush();
209
 
        Object[] keys = getKeyArray();
210
 
        for (Object key : keys) {
211
 
            Element element = (Element) map.get(key);
212
 
            if (element != null) {
213
 
                if (!element.isSerializable()) {
214
 
                    if (LOG.isLoggable(Level.FINE)) {
215
 
                        LOG.log(Level.FINE, "Object with key " + element.getObjectKey()
216
 
                                + " is not Serializable and is not being overflowed to disk.");
217
 
                    }
218
 
                } else {
219
 
                    spoolToDisk(element);
220
 
                    //Don't notify listeners. They are not being removed from the cache, only a store
221
 
                    //Leave it in the memory store for performance if do not want to clear on flush
222
 
                    if (clearOnFlush) {
223
 
                        remove(key);
224
 
                    }
225
 
                }
226
 
            }
227
 
        }
228
 
    }
229
 
 
230
 
    /**
231
 
     * Puts the element in the DiskStore.
232
 
     * Should only be called if isOverflowToDisk is true
233
 
     * <p/>
234
 
     * Relies on being called from a synchronized method
235
 
     *
236
 
     * @param element The Element
237
 
     */
238
 
    protected void spoolToDisk(Element element) {
239
 
        diskStore.put(element);
240
 
        if (LOG.isLoggable(Level.FINE)) {
241
 
            LOG.log(Level.FINE, cache.getName() + "Cache: spool to disk done for: " + element.getObjectKey());
242
 
        }
243
 
    }
244
 
 
245
 
    /**
246
 
     * Gets the status of the MemoryStore.
247
 
     */
248
 
    public final Status getStatus() {
249
 
        return status;
250
 
    }
251
 
 
252
 
    /**
253
 
     * Gets an Array of the keys for all elements in the memory cache.
254
 
     * <p/>
255
 
     * Does not check for expired entries
256
 
     *
257
 
     * @return An Object[]
258
 
     */
259
 
    public final synchronized Object[] getKeyArray() {
260
 
        return map.keySet().toArray();
261
 
    }
262
 
 
263
 
    /**
264
 
     * Returns the current cache size.
265
 
     *
266
 
     * @return The size value
267
 
     */
268
 
    public final int getSize() {
269
 
        return map.size();
270
 
    }
271
 
 
272
 
 
273
 
    /**
274
 
     * An unsynchronized check to see if a key is in the Store. No check is made to see if the Element is expired.
275
 
     *
276
 
     * @param key The Element key
277
 
     * @return true if found. If this method return false, it means that an Element with the given key is definitely not in the MemoryStore.
278
 
     *         If it returns true, there is an Element there. An attempt to get it may return null if the Element has expired.
279
 
     */
280
 
    public final boolean containsKey(Object key) {
281
 
        return map.containsKey(key);
282
 
    }
283
 
 
284
 
 
285
 
    /**
286
 
     * Measures the size of the memory store by measuring the serialized size of all elements.
287
 
     * If the objects are not Serializable they count as 0.
288
 
     * <p/>
289
 
     * Warning: This method can be very expensive to run. Allow approximately 1 second
290
 
     * per 1MB of entries. Running this method could create liveness problems
291
 
     * because the object lock is held for a long period
292
 
     *
293
 
     * @return the size, in bytes
294
 
     */
295
 
    public final synchronized long getSizeInBytes() throws CacheException {
296
 
        long sizeInBytes = 0;
297
 
        for (Iterator iterator = map.values().iterator(); iterator.hasNext();) {
298
 
            Element element = (Element) iterator.next();
299
 
            if (element != null) {
300
 
                sizeInBytes += element.getSerializedSize();
301
 
            }
302
 
        }
303
 
        return sizeInBytes;
304
 
    }
305
 
 
306
 
 
307
 
    /**
308
 
     * Evict the <code>Element</code>.
309
 
     * <p/>
310
 
     * Evict means that the <code>Element</code> is:
311
 
     * <ul>
312
 
     * <li>if, the store is diskPersistent, the <code>Element</code> is spooled to the DiskStore
313
 
     * <li>if not, the <code>Element</code> is removed.
314
 
     * </ul>
315
 
     *
316
 
     * @param element the <code>Element</code> to be evicted.
317
 
     */
318
 
    protected final void evict(Element element) throws CacheException {
319
 
        boolean spooled = false;
320
 
        if (cache.getCacheConfiguration().isOverflowToDisk()) {
321
 
            if (!element.isSerializable()) {
322
 
                if (LOG.isLoggable(Level.FINE)) {
323
 
                    LOG.log(Level.FINE, new StringBuffer("Object with key ").append(element.getObjectKey())
324
 
                            .append(" is not Serializable and cannot be overflowed to disk").toString());
325
 
                }
326
 
            } else {
327
 
                spoolToDisk(element);
328
 
                spooled = true;
329
 
            }
330
 
        }
331
 
 
332
 
        if (!spooled) {
333
 
            cache.getCacheEventNotificationService().notifyElementEvicted(element, false);
334
 
        }
335
 
    }
336
 
 
337
 
    /**
338
 
     * Before eviction elements are checked.
339
 
     *
340
 
     * @param element
341
 
     */
342
 
    protected final void notifyExpiry(Element element) {
343
 
        cache.getCacheEventNotificationService().notifyElementExpiry(element, false);
344
 
    }
345
 
 
346
 
 
347
 
    /**
348
 
     * An algorithm to tell if the MemoryStore is at or beyond its carrying capacity.
349
 
     */
350
 
    protected final boolean isFull() {
351
 
        return map.size() > maximumSize;
352
 
    }
353
 
 
354
 
    /**
355
 
     * Expire all elsments.
356
 
     * <p/>
357
 
     * This is a default implementation which does nothing. Expiry on demand is only
358
 
     * implemented for disk stores.
359
 
     */
360
 
    public void expireElements() {
361
 
        //empty implementation
362
 
    }
363
 
 
364
 
    /**
365
 
     * Memory stores are never backed up and always return false
366
 
     */
367
 
    public boolean bufferFull() {
368
 
        return false;
369
 
    }
370
 
 
371
 
 
372
 
    /**
373
 
     * Package local access to the map for testing
374
 
     */
375
 
    Map getBackingMap() {
376
 
        return map;
377
 
    }
378
 
 
379
 
    /**
380
 
     * An extension of LinkedHashMap which overrides {@link #removeEldestEntry}
381
 
     * to persist cache entries to the auxiliary cache before they are removed.
382
 
     * <p/>
383
 
     * This implementation also provides LRU by access order.
384
 
     */
385
 
    public final class SpoolingLinkedHashMap extends java.util.LinkedHashMap {
386
 
        private static final int INITIAL_CAPACITY = 100;
387
 
        private static final float GROWTH_FACTOR = .75F;
388
 
 
389
 
        /**
390
 
         * Default constructor.
391
 
         * Will create an initial capacity of 100, a loading of .75 and
392
 
         * LRU by access order.
393
 
         */
394
 
        public SpoolingLinkedHashMap() {
395
 
            super(INITIAL_CAPACITY, GROWTH_FACTOR, true);
396
 
        }
397
 
 
398
 
        /**
399
 
         * Returns <tt>true</tt> if this map should remove its eldest entry.
400
 
         * This method is invoked by <tt>put</tt> and <tt>putAll</tt> after
401
 
         * inserting a new entry into the map.  It provides the implementer
402
 
         * with the opportunity to remove the eldest entry each time a new one
403
 
         * is added.  This is useful if the map represents a cache: it allows
404
 
         * the map to reduce memory consumption by deleting stale entries.
405
 
         * <p/>
406
 
         * Will return true if:
407
 
         * <ol>
408
 
         * <li> the element has expired
409
 
         * <li> the cache size is greater than the in-memory actual.
410
 
         * In this case we spool to disk before returning.
411
 
         * </ol>
412
 
         *
413
 
         * @param eldest The least recently inserted entry in the map, or if
414
 
         *               this is an access-ordered map, the least recently accessed
415
 
         *               entry.  This is the entry that will be removed it this
416
 
         *               method returns <tt>true</tt>.  If the map was empty prior
417
 
         *               to the <tt>put</tt> or <tt>putAll</tt> invocation resulting
418
 
         *               in this invocation, this will be the entry that was just
419
 
         *               inserted; in other words, if the map contains a single
420
 
         *               entry, the eldest entry is also the newest.
421
 
         * @return true if the eldest entry should be removed
422
 
         *         from the map; <tt>false</t> if it should be retained.
423
 
         */
424
 
        protected final boolean removeEldestEntry(Map.Entry eldest) {
425
 
            Element element = (Element) eldest.getValue();
426
 
            return removeLeastRecentlyUsedElement(element);
427
 
        }
428
 
 
429
 
        /**
430
 
         * Relies on being called from a synchronized method
431
 
         *
432
 
         * @param element
433
 
         * @return true if the LRU element should be removed
434
 
         */
435
 
        private boolean removeLeastRecentlyUsedElement(Element element) throws CacheException {
436
 
            //check for expiry and remove before going to the trouble of spooling it
437
 
            if (element.isExpired()) {
438
 
                notifyExpiry(element);
439
 
                return true;
440
 
            }
441
 
 
442
 
            if (isFull()) {
443
 
                evict(element);
444
 
                return true;
445
 
            } else {
446
 
                return false;
447
 
            }
448
 
 
449
 
        }
450
 
    }
451
 
 
452
 
 
453
 
    /**
454
 
     * @return the current eviction policy. This may not be the configured policy, if it has been
455
 
     *         dynamically set.
456
 
     * @see #setEvictionPolicy(Policy)
457
 
     */
458
 
    public Policy getEvictionPolicy() {
459
 
        return new LruPolicy();
460
 
    }
461
 
 
462
 
    /**
463
 
     * Sets the eviction policy strategy. The Store will use a policy at startup. The store may allow changing
464
 
     * the eviction policy strategy dynamically. Otherwise implementations will throw an exception if this method
465
 
     * is called.
466
 
     *
467
 
     * @param policy the new policy
468
 
     */
469
 
    public void setEvictionPolicy(Policy policy) {
470
 
        throw new UnsupportedOperationException("This store is LRU only. It does not support changing the eviction" +
471
 
                " strategy.");
472
 
    }
473
 
 
474
 
 
475
 
}