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