1 /**
2  *  Copyright 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.CacheEntry;
20 import net.sf.ehcache.CacheException;
21 import net.sf.ehcache.Ehcache;
22 import net.sf.ehcache.Element;
23 import net.sf.ehcache.Status;
24 import net.sf.ehcache.concurrent.CacheLockProvider;
25 import net.sf.ehcache.concurrent.ReadWriteLockSync;
26 import net.sf.ehcache.concurrent.Sync;
27 import net.sf.ehcache.config.CacheConfiguration;
28 import net.sf.ehcache.config.CacheConfigurationListener;
29 import net.sf.ehcache.config.PinningConfiguration;
30 import net.sf.ehcache.config.SizeOfPolicyConfiguration;
31 import net.sf.ehcache.event.RegisteredEventListeners;
32 import net.sf.ehcache.pool.Pool;
33 import net.sf.ehcache.pool.PoolAccessor;
34 import net.sf.ehcache.pool.PoolableStore;
35 import net.sf.ehcache.pool.Size;
36 import net.sf.ehcache.pool.impl.DefaultSizeOfEngine;
37 import net.sf.ehcache.store.chm.SelectableConcurrentHashMap;
38 import net.sf.ehcache.store.disk.StoreUpdateException;
39 import net.sf.ehcache.util.ratestatistics.AtomicRateStatistic;
40 import net.sf.ehcache.util.ratestatistics.RateStatistic;
41 import net.sf.ehcache.writer.CacheWriterManager;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45 import java.util.ArrayList;
46 import java.util.Collection;
47 import java.util.List;
48 import java.util.Set;
49 import java.util.concurrent.TimeUnit;
50 import java.util.concurrent.locks.Lock;
51
52 /**
53  * A Store implementation suitable for fast, concurrent in memory stores. The policy is determined by that
54  * configured in the cache.
55  *
56  * @author <a href="mailto:ssuravarapu@users.sourceforge.net">Surya Suravarapu</a>
57  * @version $Id: MemoryStore.java 7369 2013-04-10 17:49:29Z cdennis $
58  */

59 public class MemoryStore extends AbstractStore implements TierableStore, PoolableStore, CacheConfigurationListener {
60
61     /**
62      * This is the default from {@link java.util.concurrent.ConcurrentHashMap}. It should never be used, because we size
63      * the map to the max size of the store.
64      */

65     static final float DEFAULT_LOAD_FACTOR = 0.75f;
66
67     /**
68      * Set optimisation for 100 concurrent threads.
69      */

70     private static final int CONCURRENCY_LEVEL = 100;
71
72     private static final int MAX_EVICTION_RATIO = 5;
73
74     private static final Logger LOG = LoggerFactory.getLogger(MemoryStore.class.getName());
75
76     private final boolean alwaysPutOnHeap;
77
78     /**
79      * The cache this store is associated with.
80      */

81     private final Ehcache cache;
82
83     /**
84      * Map where items are stored by key.
85      */

86     private final SelectableConcurrentHashMap map;
87     private final PoolAccessor poolAccessor;
88
89     private final RateStatistic hitRate = new AtomicRateStatistic(1000, TimeUnit.MILLISECONDS);
90     private final RateStatistic missRate = new AtomicRateStatistic(1000, TimeUnit.MILLISECONDS);
91
92     private final boolean storePinned;
93     private final boolean elementPinningEnabled;
94
95     /**
96      * The maximum size of the store (0 == no limit)
97      */

98     private volatile int maximumSize;
99
100     /**
101      * status.
102      */

103     private volatile Status status;
104
105     /**
106      * The eviction policy to use
107      */

108     private volatile Policy policy;
109
110     /**
111      * The pool accessor
112      */

113
114     private volatile CacheLockProvider lockProvider;
115
116     /**
117      * Constructs things that all MemoryStores have in common.
118      *
119      * @param cache the cache
120      * @param pool the pool tracking the on-heap usage
121      * @param notify whether to notify the Cache's EventNotificationService on eviction and expiry
122      */

123     protected MemoryStore(Ehcache cache, Pool pool, boolean notify, BackingFactory factory) {
124         status = Status.STATUS_UNINITIALISED;
125         this.cache = cache;
126         this.maximumSize = (int) cache.getCacheConfiguration().getMaxEntriesLocalHeap();
127         this.policy = determineEvictionPolicy(cache);
128
129         this.poolAccessor = pool.createPoolAccessor(this,
130             SizeOfPolicyConfiguration.resolveMaxDepth(cache),
131             SizeOfPolicyConfiguration.resolveBehavior(cache).equals(SizeOfPolicyConfiguration.MaxDepthExceededBehavior.ABORT));
132
133         this.alwaysPutOnHeap = getAdvancedBooleanConfigProperty("alwaysPutOnHeap", cache.getCacheConfiguration().getName(), false);
134         this.storePinned = determineStorePinned(cache.getCacheConfiguration());
135
136         this.elementPinningEnabled = !cache.getCacheConfiguration().isOverflowToOffHeap();
137         int maximumCapacity = isClockEviction() && !storePinned ? maximumSize : 0;
138         RegisteredEventListeners eventListener = notify ? cache.getCacheEventNotificationService() : null;
139         if (Boolean.getBoolean(MemoryStore.class.getName() + ".presize")) {
140             // create the CHM with initialCapacity sufficient to hold maximumSize
141             final float loadFactor = maximumSize == 1 ? 1 : DEFAULT_LOAD_FACTOR;
142             int initialCapacity = getInitialCapacityForLoadFactor(maximumSize, loadFactor);
143             this.map = factory.newBackingMap(poolAccessor, elementPinningEnabled, initialCapacity,
144                     loadFactor, CONCURRENCY_LEVEL, maximumCapacity, eventListener);
145         } else {
146             this.map = factory.newBackingMap(poolAccessor, elementPinningEnabled, CONCURRENCY_LEVEL, maximumCapacity, eventListener);
147         }
148
149         this.status = Status.STATUS_ALIVE;
150
151         if (LOG.isDebugEnabled()) {
152             LOG.debug("Initialized " + this.getClass().getName() + for " + cache.getName());
153         }
154     }
155
156     private boolean determineStorePinned(CacheConfiguration cacheConfiguration) {
157         PinningConfiguration pinningConfiguration = cacheConfiguration.getPinningConfiguration();
158         if (pinningConfiguration == null) {
159             return false;
160         }
161
162         switch (pinningConfiguration.getStore()) {
163             case LOCALHEAP:
164                 return true;
165
166             case LOCALMEMORY:
167                 return !cacheConfiguration.isOverflowToOffHeap();
168
169             case INCACHE:
170                 return !cacheConfiguration.isOverflowToOffHeap() && !cacheConfiguration.isOverflowToDisk();
171
172             default:
173                 throw new IllegalArgumentException();
174         }
175     }
176
177     /**
178      * Calculates the initialCapacity for a desired maximumSize goal and loadFactor.
179      *
180      * @param maximumSizeGoal the desired maximum size goal
181      * @param loadFactor      the load factor
182      * @return the calculated initialCapacity. Returns 0 if the parameter <tt>maximumSizeGoal</tt> is less than or equal
183      *         to 0
184      */

185     protected static int getInitialCapacityForLoadFactor(int maximumSizeGoal, float loadFactor) {
186         double actualMaximum = Math.ceil(maximumSizeGoal / loadFactor);
187         return Math.max(0, actualMaximum >= Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) actualMaximum);
188     }
189
190     /**
191      * A factory method to create a MemoryStore.
192      *
193      * @param cache the cache
194      * @param pool the pool tracking the on-heap usage
195      * @return an instance of a MemoryStore, configured with the appropriate eviction policy
196      */

197     public static MemoryStore create(final Ehcache cache, Pool pool) {
198         MemoryStore memoryStore = new MemoryStore(cache, pool, falsenew BasicBackingFactory());
199         cache.getCacheConfiguration().addConfigurationListener(memoryStore);
200         return memoryStore;
201     }
202
203     /**
204      * {@inheritDoc}
205      */

206     public void unpinAll() {
207         if (elementPinningEnabled) {
208             map.unpinAll();
209         }
210     }
211
212     /**
213      * {@inheritDoc}
214      */

215     public void setPinned(Object key, boolean pinned) {
216         if (elementPinningEnabled) {
217             map.setPinned(key, pinned);
218         }
219     }
220
221     /**
222      * {@inheritDoc}
223      */

224     public boolean isPinned(Object key) {
225         return elementPinningEnabled && map.isPinned(key);
226     }
227
228     private boolean isPinningEnabled(Element element) {
229         return storePinned || isPinned(element.getObjectKey());
230     }
231
232     /**
233      * {@inheritDoc}
234      */

235     public void fill(Element element) {
236         if (alwaysPutOnHeap || isPinningEnabled(element) || remove(element.getObjectKey()) != null || canPutWithoutEvicting(element)) {
237             put(element);
238         }
239     }
240
241     /**
242      * {@inheritDoc}
243      */

244     public boolean removeIfNotPinned(final Object key) {
245         return !storePinned && !isPinned(key) && remove(key) != null;
246     }
247
248     /**
249      * Puts an item in the store. Note that this automatically results in an eviction if the store is full.
250      *
251      * @param element the element to add
252      */

253     public boolean put(final Element element) throws CacheException {
254         if (element == null) {
255             return false;
256         }
257
258         long delta = poolAccessor.add(element.getObjectKey(), element.getObjectValue(), map.storedObject(element), isPinningEnabled(element));
259         if (delta > -1) {
260             Element old = map.put(element.getObjectKey(), element, delta);
261             checkCapacity(element);
262             return old == null;
263         } else {
264             notifyDirectEviction(element);
265             return true;
266         }
267     }
268
269     /**
270      * {@inheritDoc}
271      */

272     public boolean putWithWriter(Element element, CacheWriterManager writerManager) throws CacheException {
273         long delta = poolAccessor.add(element.getObjectKey(), element.getObjectValue(), map.storedObject(element), isPinningEnabled(element));
274         if (delta > -1) {
275             Element old = map.put(element.getObjectKey(), element, delta);
276             if (writerManager != null) {
277                 try {
278                     writerManager.put(element);
279                 } catch (RuntimeException e) {
280                     throw new StoreUpdateException(e, old != null);
281                 }
282             }
283             checkCapacity(element);
284             return old == null;
285         } else {
286             notifyDirectEviction(element);
287             return true;
288         }
289     }
290
291     /**
292      * Gets an item from the cache.
293      * <p/>
294      * The last access time in {@link net.sf.ehcache.Element} is updated.
295      *
296      * @param key the key of the Element
297      * @return the element, or null if there was no match for the key
298      */

299     public final Element get(final Object key) {
300         if (key == null) {
301             return null;
302         } else {
303             final Element e = map.get(key);
304             if (e == null) {
305                 missRate.event();
306             } else {
307                 hitRate.event();
308             }
309             return e;
310         }
311     }
312
313     /**
314      * Gets an item from the cache, without updating statistics.
315      *
316      * @param key the cache key
317      * @return the element, or null if there was no match for the key
318      */

319     public final Element getQuiet(Object key) {
320         return get(key);
321     }
322
323     /**
324      * Removes an Element from the store.
325      *
326      * @param key the key of the Element, usually a String
327      * @return the Element if one was found, else null
328      */

329     public Element remove(final Object key) {
330         if (key == null) {
331             return null;
332         }
333
334         return map.remove(key);
335     }
336
337     /**
338      * {@inheritDoc}
339      */

340     public void removeNoReturn(final Object key) {
341         remove(key);
342     }
343
344     /**
345      * {@inheritDoc}
346      */

347     public boolean isTierPinned() {
348         return storePinned;
349     }
350
351     /**
352      * {@inheritDoc}
353      */

354     public Set getPresentPinnedKeys() {
355         return map.pinnedKeySet();
356     }
357
358     /**
359      * {@inheritDoc}
360      */

361     public boolean isPersistent() {
362         return false;
363     }
364
365     /**
366      * {@inheritDoc}
367      */

368     public final Element removeWithWriter(Object key, CacheWriterManager writerManager) throws CacheException {
369         if (key == null) {
370             return null;
371         }
372
373         // remove single item.
374         Element element = map.remove(key);
375         if (writerManager != null) {
376             writerManager.remove(new CacheEntry(key, element));
377         }
378         if (element == null && LOG.isDebugEnabled()) {
379             LOG.debug(cache.getName() + "Cache: Cannot remove entry as key " + key + " was not found");
380         }
381         return element;
382     }
383
384     /**
385      * Memory stores are never backed up and always return false
386      */

387     public final boolean bufferFull() {
388         return false;
389     }
390
391     /**
392      * Expire all elements.
393      * <p/>
394      * This is a default implementation which does nothing. Expiration on demand is only implemented for disk stores.
395      */

396     public void expireElements() {
397         for (Object key : map.keySet()) {
398             expireElement(key);
399         }
400     }
401
402     /**
403      * Evicts the element for the given key, if it exists and is expired
404      * @param key the key
405      * @return the evicted element, if any. Otherwise null
406      */

407     protected Element expireElement(final Object key) {
408         Element value = get(key);
409         return value != null && value.isExpired() && map.remove(key, value) ? value : null;
410     }
411
412     /**
413      * Chooses the Policy from the cache configuration
414      * @param cache the cache
415      * @return the chosen eviction policy
416      */

417     private static Policy determineEvictionPolicy(Ehcache cache) {
418         MemoryStoreEvictionPolicy policySelection = cache.getCacheConfiguration().getMemoryStoreEvictionPolicy();
419
420         if (policySelection.equals(MemoryStoreEvictionPolicy.LRU)) {
421             return new LruPolicy();
422         } else if (policySelection.equals(MemoryStoreEvictionPolicy.FIFO)) {
423             return new FifoPolicy();
424         } else if (policySelection.equals(MemoryStoreEvictionPolicy.LFU)) {
425             return new LfuPolicy();
426         } else if (policySelection.equals(MemoryStoreEvictionPolicy.CLOCK)) {
427             return null;
428         }
429
430         throw new IllegalArgumentException(policySelection + " isn't a valid eviction policy");
431     }
432
433     /**
434      * Remove all of the elements from the store.
435      */

436     public final void removeAll() throws CacheException {
437         for (Object key : map.keySet()) {
438             remove(key);
439         }
440     }
441
442     /**
443      * Prepares for shutdown.
444      */

445     public synchronized void dispose() {
446         if (status.equals(Status.STATUS_SHUTDOWN)) {
447             return;
448         }
449         status = Status.STATUS_SHUTDOWN;
450         flush();
451         poolAccessor.unlink();
452     }
453
454     /**
455      * Flush to disk only if the cache is diskPersistent.
456      */

457     public void flush() {
458         if (cache.getCacheConfiguration().isClearOnFlush()) {
459             removeAll();
460         }
461     }
462
463     /**
464      * Gets an Array of the keys for all elements in the memory cache.
465      * <p/>
466      * Does not check for expired entries
467      *
468      * @return An List
469      */

470     public final List<?> getKeys() {
471         return new ArrayList<Object>(map.keySet());
472     }
473
474     /**
475      * Returns the keySet for this store
476      * @return keySet
477      */

478     protected Set<?> keySet() {
479         return map.keySet();
480     }
481
482     /**
483      * Returns the current store size.
484      *
485      * @return The size value
486      */

487     public final int getSize() {
488         return map.size();
489     }
490
491     /**
492      * Returns nothing since a disk store isn't clustered
493      *
494      * @return returns 0
495      */

496     public final int getTerracottaClusteredSize() {
497         return 0;
498     }
499
500     /**
501      * A check to see if a key is in the Store. No check is made to see if the Element is expired.
502      *
503      * @param key The Element key
504      * @return true if found. If this method return false, it means that an Element with the given key is definitely not
505      *         in the MemoryStore. If it returns true, there is an Element there. An attempt to get it may return null if
506      *         the Element has expired.
507      */

508     public final boolean containsKey(final Object key) {
509         return map.containsKey(key);
510     }
511
512     /**
513      * Before eviction elements are checked.
514      *
515      * @param element the element to notify about its expiry
516      */

517     private void notifyExpiry(final Element element) {
518         cache.getCacheEventNotificationService().notifyElementExpiry(element, false);
519     }
520
521     /**
522      * Called when an element is evicted even before it could be installed inside the store
523      *
524      * @param element the evicted element
525      */

526     protected void notifyDirectEviction(final Element element) {
527     }
528
529     /**
530      * An algorithm to tell if the MemoryStore is at or beyond its carrying capacity.
531      *
532      * @return true if the store is full, false otherwise
533      */

534     public final boolean isFull() {
535         return maximumSize > 0 && map.quickSize() >= maximumSize;
536     }
537
538     /**
539      * Check if adding an element won't provoke an eviction.
540      *
541      * @param element the element
542      * @return true if the element can be added without provoking an eviction.
543      */

544     public final boolean canPutWithoutEvicting(Element element) {
545         if (element == null) {
546             return true;
547         }
548
549         return !isFull() && poolAccessor.canAddWithoutEvicting(element.getObjectKey(), element.getObjectValue(), map.storedObject(element));
550     }
551
552     /**
553      * If the store is over capacity, evict elements until capacity is reached
554      *
555      * @param elementJustAdded the element added by the action calling this check
556      */

557     private void checkCapacity(final Element elementJustAdded) {
558         if (maximumSize > 0 && !isClockEviction()) {
559             int evict = Math.min(map.quickSize() - maximumSize, MAX_EVICTION_RATIO);
560             for (int i = 0; i < evict; i++) {
561                 removeElementChosenByEvictionPolicy(elementJustAdded);
562             }
563         }
564     }
565
566     /**
567      * Removes the element chosen by the eviction policy
568      *
569      * @param elementJustAdded it is possible for this to be null
570      * @return true if an element was removed, false otherwise.
571      */

572     private boolean removeElementChosenByEvictionPolicy(final Element elementJustAdded) {
573
574         if (policy == null) {
575             return map.evict();
576         }
577
578         Element element = findEvictionCandidate(elementJustAdded);
579         if (element == null) {
580             LOG.debug("Eviction selection miss. Selected element is null");
581             return false;
582         }
583
584         // If the element is expired, remove
585         if (element.isExpired()) {
586             remove(element.getObjectKey());
587             notifyExpiry(element);
588             return true;
589         }
590
591         if (isPinningEnabled(element)) {
592             return false;
593         }
594
595         return evict(element);
596     }
597
598     /**
599      * Find a "relatively" unused element.
600      *
601      * @param elementJustAdded the element added by the action calling this check
602      * @return the element chosen as candidate for eviction
603      */

604     private Element findEvictionCandidate(final Element elementJustAdded) {
605         Object objectKey = elementJustAdded != null ? elementJustAdded.getObjectKey() : null;
606         Element[] elements = sampleElements(objectKey);
607         // this can return null. Let the cache get bigger by one.
608         return policy.selectedBasedOnPolicy(elements, elementJustAdded);
609     }
610
611     /**
612      * Uses random numbers to sample the entire map.
613      * <p/>
614      * This implemenation uses a key array.
615      *
616      * @param keyHint a key used as a hint indicating where the just added element is
617      * @return a random sample of elements
618      */

619     private Element[] sampleElements(Object keyHint) {
620         int size = AbstractPolicy.calculateSampleSize(map.quickSize());
621         return map.getRandomValues(size, keyHint);
622     }
623
624     /**
625      * {@inheritDoc}
626      */

627     public Object getInternalContext() {
628         if (lockProvider != null) {
629             return lockProvider;
630         } else {
631             lockProvider = new LockProvider();
632             return lockProvider;
633         }
634     }
635
636     /**
637      * Gets the status of the MemoryStore.
638      */

639     public final Status getStatus() {
640         return status;
641     }
642
643     /**
644      * {@inheritDoc}
645      */

646     public void timeToIdleChanged(long oldTti, long newTti) {
647         // no-op
648     }
649
650     /**
651      * {@inheritDoc}
652      */

653     public void timeToLiveChanged(long oldTtl, long newTtl) {
654         // no-op
655     }
656
657     /**
658      * {@inheritDoc}
659      */

660     public void diskCapacityChanged(int oldCapacity, int newCapacity) {
661         // no-op
662     }
663
664     /**
665      * {@inheritDoc}
666      */

667     public void loggingChanged(boolean oldValue, boolean newValue) {
668         // no-op
669     }
670
671     /**
672      * {@inheritDoc}
673      */

674     public void memoryCapacityChanged(int oldCapacity, int newCapacity) {
675         maximumSize = newCapacity;
676         if (isClockEviction() && !storePinned) {
677             map.setMaxSize(maximumSize);
678         }
679     }
680
681     private boolean isClockEviction() {
682         return policy == null;
683     }
684
685     /**
686      * {@inheritDoc}
687      */

688     public void registered(CacheConfiguration config) {
689         // no-op
690     }
691
692     /**
693      * {@inheritDoc}
694      */

695     public void deregistered(CacheConfiguration config) {
696         // no-op
697     }
698
699     /**
700      * {@inheritDoc}
701      */

702     public void maxBytesLocalHeapChanged(final long oldValue, final long newValue) {
703         this.poolAccessor.setMaxSize(newValue);
704     }
705
706     /**
707      * {@inheritDoc}
708      */

709     public void maxBytesLocalDiskChanged(final long oldValue, final long newValue) {
710         // no-op
711     }
712
713     /**
714      * {@inheritDoc}
715      */

716     public boolean containsKeyInMemory(Object key) {
717         return containsKey(key);
718     }
719
720     /**
721      * {@inheritDoc}
722      */

723     public boolean containsKeyOffHeap(Object key) {
724         return false;
725     }
726
727     /**
728      * {@inheritDoc}
729      */

730     public boolean containsKeyOnDisk(Object key) {
731         return false;
732     }
733
734     /**
735      * {@inheritDoc}
736      */

737     public Policy getInMemoryEvictionPolicy() {
738         return policy;
739     }
740
741     /**
742      * {@inheritDoc}
743      */

744     public int getInMemorySize() {
745         return getSize();
746     }
747
748     /**
749      * {@inheritDoc}
750      */

751     public long getInMemorySizeInBytes() {
752         if (poolAccessor.getSize() < 0) {
753             DefaultSizeOfEngine defaultSizeOfEngine = new DefaultSizeOfEngine(
754                 SizeOfPolicyConfiguration.resolveMaxDepth(cache),
755                 SizeOfPolicyConfiguration.resolveBehavior(cache).equals(SizeOfPolicyConfiguration.MaxDepthExceededBehavior.ABORT)
756             );
757             long sizeInBytes = 0;
758             for (Object o : map.values()) {
759                 Element element = (Element)o;
760                 if (element != null) {
761                     Size size = defaultSizeOfEngine.sizeOf(element.getObjectKey(), element, map.storedObject(element));
762                     sizeInBytes += size.getCalculated();
763                 }
764             }
765             return sizeInBytes;
766         }
767         return poolAccessor.getSize();
768     }
769
770     /**
771      * {@inheritDoc}
772      */

773     public int getOffHeapSize() {
774         return 0;
775     }
776
777     /**
778      * {@inheritDoc}
779      */

780     public long getOffHeapSizeInBytes() {
781         return 0;
782     }
783
784     /**
785      * {@inheritDoc}
786      */

787     public int getOnDiskSize() {
788         return 0;
789     }
790
791     /**
792      * {@inheritDoc}
793      */

794     public long getOnDiskSizeInBytes() {
795         return 0;
796     }
797
798     /**
799      * {@inheritDoc}
800      */

801     @Override
802     public boolean hasAbortedSizeOf() {
803         return poolAccessor.hasAbortedSizeOf();
804     }
805
806     /**
807      * {@inheritDoc}
808      */

809     public void setInMemoryEvictionPolicy(Policy policy) {
810         this.policy = policy;
811     }
812
813     /**
814      * {@inheritDoc}
815      */

816     public Element putIfAbsent(Element element) throws NullPointerException {
817         if (element == null) {
818             return null;
819         }
820
821         long delta = poolAccessor.add(element.getObjectKey(), element.getObjectValue(), map.storedObject(element), isPinningEnabled(element));
822         if (delta > -1) {
823             Element old = map.putIfAbsent(element.getObjectKey(), element, delta);
824             if (old == null) {
825               checkCapacity(element);
826             } else {
827               poolAccessor.delete(delta);
828             }
829             return old;
830         } else {
831             notifyDirectEviction(element);
832             return null;
833         }
834     }
835
836     /**
837      * Evicts the element from the store
838      * @param element the element to be evicted
839      * @return true if succeeded, false otherwise
840      */

841     protected boolean evict(final Element element) {
842         final Element remove = remove(element.getObjectKey());
843         RegisteredEventListeners cacheEventNotificationService = cache.getCacheEventNotificationService();
844         final FrontEndCacheTier frontEndCacheTier = cacheEventNotificationService.getFrontEndCacheTier();
845         if (remove != null && frontEndCacheTier != null && frontEndCacheTier.notifyEvictionFromCache(remove.getKey())) {
846             cacheEventNotificationService.notifyElementEvicted(remove, false);
847         }
848         return remove != null;
849     }
850
851     /**
852      * {@inheritDoc}
853      */

854     public Element removeElement(Element element, ElementValueComparator comparator) throws NullPointerException {
855         if (element == null || element.getObjectKey() == null) {
856             return null;
857         }
858
859         Object key = element.getObjectKey();
860
861         Lock lock = getWriteLock(key);
862         lock.lock();
863         try {
864             Element toRemove = map.get(key);
865             if (comparator.equals(element, toRemove)) {
866                 map.remove(key);
867                 return toRemove;
868             } else {
869                 return null;
870             }
871         } finally {
872             lock.unlock();
873         }
874     }
875
876     /**
877      * {@inheritDoc}
878      */

879     public boolean replace(Element old, Element element, ElementValueComparator comparator) throws NullPointerException,
880             IllegalArgumentException {
881         if (element == null || element.getObjectKey() == null) {
882             return false;
883         }
884
885         Object key = element.getObjectKey();
886
887         long delta = poolAccessor.add(element.getObjectKey(), element.getObjectValue(), map.storedObject(element), isPinningEnabled(element));
888         if (delta > -1) {
889             Lock lock = getWriteLock(key);
890             lock.lock();
891             try {
892                 Element toRemove = map.get(key);
893                 if (comparator.equals(old, toRemove)) {
894                     map.put(key, element, delta);
895                     return true;
896                 } else {
897                     poolAccessor.delete(delta);
898                     return false;
899                 }
900             } finally {
901                 lock.unlock();
902             }
903         } else {
904             notifyDirectEviction(element);
905             return false;
906         }
907     }
908
909     /**
910      * {@inheritDoc}
911      */

912     public Element replace(Element element) throws NullPointerException {
913         if (element == null || element.getObjectKey() == null) {
914             return null;
915         }
916
917         Object key = element.getObjectKey();
918
919         long delta = poolAccessor.add(element.getObjectKey(), element.getObjectValue(), map.storedObject(element), isPinningEnabled(element));
920         if (delta > -1) {
921             Lock lock = getWriteLock(key);
922             lock.lock();
923             try {
924                 Element toRemove = map.get(key);
925                 if (toRemove != null) {
926                     map.put(key, element, delta);
927                     return toRemove;
928                 } else {
929                     poolAccessor.delete(delta);
930                     return null;
931                 }
932             } finally {
933                 lock.unlock();
934             }
935         } else {
936             notifyDirectEviction(element);
937             return null;
938         }
939     }
940
941     /**
942      * {@inheritDoc}
943      */

944     public Object getMBean() {
945         return null;
946     }
947
948     /**
949      * {@inheritDoc}
950      */

951     public boolean evictFromOnHeap(int count, long size) {
952         if (storePinned) {
953             return false;
954         }
955
956         for (int i = 0; i < count; i++) {
957             boolean removed = removeElementChosenByEvictionPolicy(null);
958             if (!removed) {
959                 return false;
960             }
961         }
962         return true;
963     }
964
965     /**
966      * {@inheritDoc}
967      */

968     public boolean evictFromOnDisk(int count, long size) {
969         return false;
970     }
971
972     /**
973      * {@inheritDoc}
974      */

975     public float getApproximateDiskHitRate() {
976         return 0;
977     }
978
979     /**
980      * {@inheritDoc}
981      */

982     public float getApproximateDiskMissRate() {
983         return 0;
984     }
985
986     /**
987      * {@inheritDoc}
988      */

989     public long getApproximateDiskCountSize() {
990         return 0;
991     }
992
993     /**
994      * {@inheritDoc}
995      */

996     public long getApproximateDiskByteSize() {
997         return 0;
998     }
999
1000     /**
1001      * {@inheritDoc}
1002      */

1003     public float getApproximateHeapHitRate() {
1004         return hitRate.getRate();
1005     }
1006
1007     /**
1008      * {@inheritDoc}
1009      */

1010     public float getApproximateHeapMissRate() {
1011         return missRate.getRate();
1012     }
1013
1014     /**
1015      * {@inheritDoc}
1016      */

1017     public long getApproximateHeapCountSize() {
1018         return map.quickSize();
1019     }
1020
1021     /**
1022      * {@inheritDoc}
1023      */

1024     public long getApproximateHeapByteSize() {
1025         return poolAccessor.getSize();
1026     }
1027
1028     private Lock getWriteLock(Object key) {
1029         return map.lockFor(key).writeLock();
1030     }
1031
1032     /**
1033      * Get a collection of the elements in this store
1034      *
1035      * @return element collection
1036      */

1037     public Collection<Element> elementSet() {
1038         return map.values();
1039     }
1040
1041     /**
1042      * LockProvider implementation that uses the segment locks.
1043      */

1044     private class LockProvider implements CacheLockProvider {
1045
1046         /**
1047          * {@inheritDoc}
1048          */

1049         public Sync getSyncForKey(Object key) {
1050             return new ReadWriteLockSync(map.lockFor(key));
1051         }
1052     }
1053
1054     private static boolean getAdvancedBooleanConfigProperty(String property, String cacheName, boolean defaultValue) {
1055         String globalPropertyKey = "net.sf.ehcache.store.config." + property;
1056         String cachePropertyKey = "net.sf.ehcache.store." + cacheName + ".config." + property;
1057         return Boolean.parseBoolean(System.getProperty(cachePropertyKey, System.getProperty(globalPropertyKey, Boolean.toString(defaultValue))));
1058     }
1059
1060     @Override
1061     public void recalculateSize(Object key) {
1062         if (key == null) {
1063             return;
1064         }
1065         map.recalculateSize(key);
1066     }
1067
1068     /**
1069      * Factory interface to create a MemoryStore backing.
1070      */

1071     protected interface BackingFactory {
1072         /**
1073          * Create a MemoryStore backing map.
1074          *
1075          * @param poolAccessor on-heap pool accessor
1076          * @param elementPinning element pinning in this store
1077          * @param initialCapacity initial store capacity
1078          * @param loadFactor map load factor
1079          * @param concurrency map concurrency
1080          * @param maximumCapacity maximum store capacity
1081          * @param eventListener event listener (or {@code nullfor no notifications)
1082          * @return a backing map
1083          */

1084         @Deprecated
1085         SelectableConcurrentHashMap newBackingMap(PoolAccessor<?> poolAccessor, boolean elementPinning, int initialCapacity,
1086                 float loadFactor, int concurrency, int maximumCapacity, RegisteredEventListeners eventListener);
1087
1088         /**
1089          * Create a MemoryStore backing map.
1090          *
1091          * @param poolAccessor on-heap pool accessor
1092          * @param concurrency map concurrency
1093          * @param maximumCapacity maximum store capacity
1094          * @param eventListener event listener (or {@code nullfor no notifications)
1095          * @return a backing map
1096          */

1097         SelectableConcurrentHashMap newBackingMap(PoolAccessor<?> poolAccessor, boolean elementPinning, int concurrency,
1098                 int maximumCapacity, RegisteredEventListeners eventListener);
1099     }
1100
1101     /**
1102      * Simple backing map factory.
1103      */

1104     static class BasicBackingFactory implements BackingFactory {
1105
1106         @Override
1107         public SelectableConcurrentHashMap newBackingMap(PoolAccessor<?> poolAccessor, boolean elementPinning,
1108                 int concurrency, int maximumCapacity, RegisteredEventListeners eventListener) {
1109             return new SelectableConcurrentHashMap(poolAccessor, elementPinning, concurrency, maximumCapacity, eventListener);
1110         }
1111
1112         @Override
1113         public SelectableConcurrentHashMap newBackingMap(PoolAccessor poolAccessor, boolean elementPinning, int initialCapacity,
1114                 float loadFactor, int concurrency, int maximumCapacity, RegisteredEventListeners eventListener) {
1115             return new SelectableConcurrentHashMap(poolAccessor, elementPinning, initialCapacity,
1116                     loadFactor, concurrency, maximumCapacity, eventListener);
1117         }
1118     }
1119 }
1120
1121