1 /**
2 * Copyright 2003-2007 Greg Luck
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 import org.apache.commons.logging.Log;
24 import org.apache.commons.logging.LogFactory;
25
26 import java.util.Iterator;
27 import java.util.Map;
28
29 /**
30 * An abstract class for the Memory Stores. All Memory store implementations for different
31 * policies (e.g: FIFO, LFU, LRU, etc.) should extend this class.
32 *
33 * @author <a href="mailto:ssuravarapu@users.sourceforge.net">Surya Suravarapu</a>
34 * @version $Id: MemoryStore.java 512 2007-07-10 09:18:45Z gregluck $
35 */
36 public abstract class MemoryStore implements Store {
37
38 private static final Log LOG = LogFactory.getLog(MemoryStore.class.getName());
39
40 /**
41 * The cache this store is associated with.
42 */
43 protected Ehcache cache;
44
45 /**
46 * Map where items are stored by key.
47 */
48 protected Map map;
49
50 /**
51 * The DiskStore associated with this MemoryStore.
52 */
53 protected final Store diskStore;
54
55 /**
56 * status.
57 */
58 protected Status status;
59
60 /**
61 * Constructs things that all MemoryStores have in common.
62 *
63 * @param cache
64 * @param diskStore
65 */
66 protected MemoryStore(Ehcache cache, Store diskStore) {
67 status = Status.STATUS_UNINITIALISED;
68 this.cache = cache;
69 this.diskStore = diskStore;
70 status = Status.STATUS_ALIVE;
71
72 if (LOG.isDebugEnabled()) {
73 LOG.debug("Initialized " + this.getClass().getName() + " for " + cache.getName());
74 }
75 }
76
77
78 /**
79 * A factory method to create a MemoryStore.
80 *
81 * @param cache
82 * @param diskStore
83 * @return an instance of a MemoryStore, configured with the appropriate eviction policy
84 */
85 public static MemoryStore create(Ehcache cache, Store diskStore) {
86 MemoryStore memoryStore = null;
87 MemoryStoreEvictionPolicy policy = cache.getMemoryStoreEvictionPolicy();
88
89 if (policy.equals(MemoryStoreEvictionPolicy.LRU)) {
90 memoryStore = new LruMemoryStore(cache, diskStore);
91 } else if (policy.equals(MemoryStoreEvictionPolicy.FIFO)) {
92 memoryStore = new FifoMemoryStore(cache, diskStore);
93 } else if (policy.equals(MemoryStoreEvictionPolicy.LFU)) {
94 memoryStore = new LfuMemoryStore(cache, diskStore);
95 }
96 return memoryStore;
97 }
98
99 /**
100 * Puts an item in the cache. Note that this automatically results in
101 * {@link net.sf.ehcache.store.LruMemoryStore.SpoolingLinkedHashMap#removeEldestEntry} being called.
102 *
103 * @param element the element to add
104 */
105 public final synchronized void put(Element element) throws CacheException {
106 if (element != null) {
107 map.put(element.getObjectKey(), element);
108 doPut(element);
109 }
110 }
111
112 /**
113 * Allow specialised actions over adding the element to the map.
114 *
115 * @param element
116 */
117 protected void doPut(Element element) throws CacheException {
118 //empty
119 }
120
121 /**
122 * Gets an item from the cache.
123 * <p/>
124 * The last access time in {@link net.sf.ehcache.Element} is updated.
125 *
126 * @param key the cache key
127 * @return the element, or null if there was no match for the key
128 */
129 public final synchronized Element get(Object key) {
130 Element element = (Element) map.get(key);
131
132 if (element != null) {
133 element.updateAccessStatistics();
134 if (LOG.isTraceEnabled()) {
135 LOG.trace(cache.getName() + "Cache: " + cache.getName() + "MemoryStore hit for " + key);
136 }
137 } else if (LOG.isTraceEnabled()) {
138 LOG.trace(cache.getName() + "Cache: " + cache.getName() + "MemoryStore miss for " + key);
139 }
140 return element;
141 }
142
143 /**
144 * Gets an item from the cache, without updating Element statistics.
145 *
146 * @param key the cache key
147 * @return the element, or null if there was no match for the key
148 */
149 public final synchronized Element getQuiet(Object key) {
150 Element cacheElement = (Element) map.get(key);
151
152 if (cacheElement != null) {
153 //cacheElement.updateAccessStatistics(); Don't update statistics
154 if (LOG.isTraceEnabled()) {
155 LOG.trace(cache.getName() + "Cache: " + cache.getName() + "MemoryStore hit for " + key);
156 }
157 } else if (LOG.isTraceEnabled()) {
158 LOG.trace(cache.getName() + "Cache: " + cache.getName() + "MemoryStore miss for " + key);
159 }
160 return cacheElement;
161 }
162
163
164 /**
165 * Removes an Element from the store.
166 *
167 * @param key the key of the Element, usually a String
168 * @return the Element if one was found, else null
169 */
170 public final synchronized Element remove(Object key) {
171
172 // remove single item.
173 Element element = (Element) map.remove(key);
174 if (element != null) {
175 return element;
176 } else {
177 if (LOG.isDebugEnabled()) {
178 LOG.debug(cache.getName() + "Cache: Cannot remove entry as key " + key + " was not found");
179 }
180 return null;
181 }
182 }
183
184 /**
185 * Remove all of the elements from the store.
186 */
187 public final synchronized void removeAll() throws CacheException {
188 clear();
189 }
190
191 /**
192 * Clears any data structures and places it back to its state when it was first created.
193 */
194 protected final void clear() {
195 map.clear();
196 }
197
198 /**
199 * Prepares for shutdown.
200 */
201 public final synchronized void dispose() {
202 if (status.equals(Status.STATUS_SHUTDOWN)) {
203 return;
204 }
205 status = Status.STATUS_SHUTDOWN;
206 flush();
207
208 //release reference to cache
209 cache = null;
210 }
211
212 /**
213 * Flush to disk.
214 */
215 public final synchronized void flush() {
216 if (cache.isOverflowToDisk()) {
217 if (LOG.isDebugEnabled()) {
218 LOG.debug(cache.getName() + " is persistent. Spooling " + map.size() + " elements to the disk store.");
219 }
220 spoolAllToDisk();
221 //should be empty in any case
222 clear();
223 }
224 }
225
226 /**
227 * Spools all elements to disk, in preparation for shutdown.
228 * <p/>
229 * Relies on being called from a synchronized method
230 * <p/>
231 * This revised implementation is a little slower but avoids using increased memory during the method.
232 */
233 protected final void spoolAllToDisk() {
234 Object[] keys = getKeyArray();
235 for (int i = 0; i < keys.length; i++) {
236 Element element = (Element) map.get(keys[i]);
237 if (element != null) {
238 if (!element.isSerializable()) {
239 if (LOG.isDebugEnabled()) {
240 LOG.debug("Object with key " + element.getObjectKey()
241 + " is not Serializable and is not being overflowed to disk.");
242 }
243 } else {
244 spoolToDisk(element);
245 //Don't notify listeners. They are not being removed from the cache, only a store
246 remove(keys[i]);
247 }
248 }
249 }
250 }
251
252 /**
253 * Puts the element in the DiskStore.
254 * Should only be called if {@link Ehcache#isOverflowToDisk} is true
255 * <p/>
256 * Relies on being called from a synchronized method
257 *
258 * @param element The Element
259 */
260 protected void spoolToDisk(Element element) {
261 diskStore.put(element);
262 if (LOG.isDebugEnabled()) {
263 LOG.debug(cache.getName() + "Cache: spool to disk done for: " + element.getObjectKey());
264 }
265 }
266
267 /**
268 * Gets the status of the MemoryStore.
269 */
270 public final Status getStatus() {
271 return status;
272 }
273
274 /**
275 * Gets an Array of the keys for all elements in the memory cache.
276 * <p/>
277 * Does not check for expired entries
278 *
279 * @return An Object[]
280 */
281 public final synchronized Object[] getKeyArray() {
282 return map.keySet().toArray();
283 }
284
285 /**
286 * Returns the current cache size.
287 *
288 * @return The size value
289 */
290 public final int getSize() {
291 return map.size();
292 }
293
294
295 /**
296 * An unsynchronized check to see if a key is in the Store. No check is made to see if the Element is expired.
297 *
298 * @param key The Element key
299 * @return true if found. If this method return false, it means that an Element with the given key is definitely not in the MemoryStore.
300 * If it returns true, there is an Element there. An attempt to get it may return null if the Element has expired.
301 */
302 public final boolean containsKey(Object key) {
303 return map.containsKey(key);
304 }
305
306
307 /**
308 * Measures the size of the memory store by measuring the serialized size of all elements.
309 * If the objects are not Serializable they count as 0.
310 * <p/>
311 * Warning: This method can be very expensive to run. Allow approximately 1 second
312 * per 1MB of entries. Running this method could create liveness problems
313 * because the object lock is held for a long period
314 *
315 * @return the size, in bytes
316 */
317 public final synchronized long getSizeInBytes() throws CacheException {
318 long sizeInBytes = 0;
319 for (Iterator iterator = map.values().iterator(); iterator.hasNext();) {
320 Element element = (Element) iterator.next();
321 if (element != null) {
322 sizeInBytes += element.getSerializedSize();
323 }
324 }
325 return sizeInBytes;
326 }
327
328
329 /**
330 * Evict the <code>Element</code>.
331 * <p/>
332 * Evict means that the <code>Element</code> is:
333 * <ul>
334 * <li>if, the store is diskPersistent, the <code>Element</code> is spooled to the DiskStore
335 * <li>if not, the <code>Element</code> is removed.
336 * </ul>
337 *
338 * @param element the <code>Element</code> to be evicted.
339 */
340 protected final void evict(Element element) throws CacheException {
341 boolean spooled = false;
342 if (cache.isOverflowToDisk()) {
343 if (!element.isSerializable()) {
344 if (LOG.isDebugEnabled()) {
345 LOG.debug(new StringBuffer("Object with key ").append(element.getObjectKey())
346 .append(" is not Serializable and cannot be overflowed to disk"));
347 }
348 } else {
349 spoolToDisk(element);
350 spooled = true;
351 }
352 }
353
354 if (!spooled) {
355 cache.getCacheEventNotificationService().notifyElementEvicted(element, false);
356 }
357 }
358
359 /**
360 * Before eviction elements are checked.
361 *
362 * @param element
363 */
364 protected final void notifyExpiry(Element element) {
365 cache.getCacheEventNotificationService().notifyElementExpiry(element, false);
366 }
367
368 /**
369 * An algorithm to tell if the MemoryStore is at or beyond its carrying capacity.
370 */
371 protected final boolean isFull() {
372 return map.size() > cache.getMaxElementsInMemory();
373 }
374
375 /**
376 * Expire all elsments.
377 * <p/>
378 * This is a default implementation which does nothing. Expiry on demand is only
379 * implemented for disk stores.
380 */
381 public void expireElements() {
382 //empty implementation
383 }
384
385 /**
386 * Memory stores are never backed up and always return false
387 */
388 public boolean backedUp() {
389 return false;
390 }
391
392 /**
393 * Package local access to the map for testing
394 */
395 Map getBackingMap() {
396 return map;
397 }
398 }