/** * Called immediately after an element has been put into the cache and the element already existed * in the cache. This is thus an update. * * <p>The {@link net.sf.ehcache.Cache#put(net.sf.ehcache.Element)} method will block until this * method returns. * * <p>Implementers may wish to have access to the Element's fields, including value, so the * element is provided. Implementers should be careful not to modify the element. The effect of * any modifications is undefined. * * @param cache the cache emitting the notification * @param element the element which was just put into the cache. */ public final void notifyElementUpdated(final Ehcache cache, final Element element) throws CacheException { if (notAlive()) { return; } if (!replicateUpdates) { return; } if (replicateUpdatesViaCopy) { if (!element.isSerializable()) { if (LOG.isWarnEnabled()) { LOG.warn( "Object with key " + element.getObjectKey() + " is not Serializable and cannot be updated via copy."); } return; } addToReplicationQueue(new CacheEventMessage(EventMessage.PUT, cache, element, null)); } else { if (!element.isKeySerializable()) { if (LOG.isWarnEnabled()) { LOG.warn( "Object with key " + element.getObjectKey() + " does not have a Serializable key and cannot be replicated via invalidate."); } return; } addToReplicationQueue( new CacheEventMessage(EventMessage.REMOVE, cache, null, element.getKey())); } }
/** * Both CacheEntryFactory can return an Element rather than just a regular value this method test * this, making a fresh Element otherwise. It also enforces the rule that the CacheEntryFactory * must provide the same key (via equals() not necessarily same object) if it is returning an * Element. * * @param key * @param value * @return the Element to be put back in the cache * @throws CacheException for various illegal states which could be harmful */ protected static Element makeAndCheckElement(Object key, Object value) throws CacheException { // check if null if (value == null) { return new Element(key, value); } // simply build a new element using the supplied key if (!(value instanceof Element)) { return new Element(key, value); } // It is already an element - perform sanity checks Element element = (Element) value; if ((element.getObjectKey() == null) && (key == null)) { return element; } else if (element.getObjectKey() == null) { throw new CacheException("CacheEntryFactory returned an Element with a null key"); } else if (!element.getObjectKey().equals(key)) { throw new CacheException( "CacheEntryFactory returned an Element with a different key: " + element.getObjectKey() + " compared to the key that was requested: " + key); } else { return element; } }
/** * Puts an element into the disk store. * * <p>This method is not synchronized. It is however threadsafe. It uses fine-grained * synchronization on the spool. */ public final void put(final Element element) { try { checkActive(); // Spool the element if (spoolAndExpiryThread.isAlive()) { synchronized (spoolLock) { spool.put(element.getObjectKey(), element); } } else { LOG.error( name + "Cache: Elements cannot be written to disk store because the" + " spool thread has died."); synchronized (spoolLock) { spool.clear(); } } } catch (Exception e) { LOG.error( name + "Cache: Could not write disk store element for " + element.getObjectKey() + ". Initial cause was " + e.getMessage(), e); } }
/** Equals comparison with another element, based on the key. */ public final boolean equals(Object object) { if (object == null || !(object instanceof Element)) { return false; } Element element = (Element) object; if (key == null || element.getObjectKey() == null) { return false; } return key.equals(element.getObjectKey()); }
/** {@inheritDoc} */ public Element putIfAbsent(Element element) throws NullPointerException { LOG.debug("cache {} putIfAbsent {}", cache.getName(), element); XATransactionContext context = getOrCreateTransactionContext(); Element previous = getCurrentElement(element.getObjectKey(), context); if (previous == null) { Element oldElement = getQuietFromUnderlyingStore(element.getObjectKey()); Element elementForWrite = copyElementForWrite(element); context.addCommand(new StorePutCommand(oldElement, elementForWrite), elementForWrite); } return copyElementForRead(previous); }
/** {@inheritDoc} */ public Element removeElement(Element element, ElementValueComparator comparator) throws NullPointerException { LOG.debug("cache {} removeElement {}", cache.getName(), element); XATransactionContext context = getOrCreateTransactionContext(); Element previous = getCurrentElement(element.getKey(), context); Element elementForWrite = copyElementForWrite(element); if (previous != null && comparator.equals(previous, elementForWrite)) { Element oldElement = getQuietFromUnderlyingStore(element.getObjectKey()); context.addCommand( new StoreRemoveCommand(element.getObjectKey(), oldElement), elementForWrite); return copyElementForRead(previous); } return null; }
/** * Tests expiry notification is hooked up to searchInDiskStore. This test is not exact, because * the expiry thread may also expire some. * * @throws InterruptedException * @throws CacheException */ @Test public void testExpiryViaDiskStoreCheckingOnGet() throws InterruptedException, CacheException, IOException { // Overflow 10 elements to disk store for (int i = 0; i < 20; i++) { Element element = new Element("" + i, new Date()); cache.put(element); } // Wait for expiry Thread.sleep(5999); // Trigger expiry for (int i = 0; i < 20; i++) { cache.get("" + i); } CountingCacheEventListener listener = getCountingCacheEventListener(cache); List<CacheEvent> notifications = listener.getCacheElementsExpired(); for (int i = 0; i < notifications.size(); i++) { Element element = notifications.get(i).getElement(); element.getObjectKey(); } assertThat(notifications, hasSize(greaterThanOrEqualTo(10))); }
/** * Refresh a single element. * * @param element the Element to refresh * @param backingCache the underlying {@link Ehcache}. * @param quiet whether to use putQuiet or not, if true replication will not occur * @return the refreshed Element * @throws Exception * @since 1.6.1 */ protected Element refreshElement(final Element element, Ehcache backingCache, boolean quiet) throws Exception { Object key = element.getObjectKey(); if (LOG.isLoggable(Level.FINE)) { LOG.fine(getName() + ": refreshing element with key " + key); } final Element replacementElement; if (factory instanceof UpdatingCacheEntryFactory) { // update the value of the cloned Element in place replacementElement = element; ((UpdatingCacheEntryFactory) factory) .updateEntryValue(key, replacementElement.getObjectValue()); // put the updated element back into the backingCache, without updating stats // It is not usually necessary to do this. We do this in case the element expired // or idles out of the backingCache. In that case we hold a reference to it but the // backingCache no longer does. } else { final Object value = factory.createEntry(key); replacementElement = makeAndCheckElement(key, value); } if (quiet) { backingCache.putQuiet(replacementElement); } else { backingCache.put(replacementElement); } return replacementElement; }
/** * Removes expired elements. * * <p>Note that the DiskStore cannot efficiently expire based on TTI. It does it on TTL. However * any gets out of the DiskStore are check for both before return. * * @noinspection SynchronizeOnNonFinalField */ public void expireElements() { final long now = System.currentTimeMillis(); // Clean up the spool synchronized (spoolLock) { for (Iterator iterator = spool.values().iterator(); iterator.hasNext(); ) { final Element element = (Element) iterator.next(); if (element.isExpired()) { // An expired element if (LOG.isDebugEnabled()) { LOG.debug(name + "Cache: Removing expired spool element " + element.getObjectKey()); } iterator.remove(); notifyExpiryListeners(element); } } } Element element = null; RegisteredEventListeners listeners = cache.getCacheEventNotificationService(); synchronized (diskElements) { // Clean up disk elements for (Iterator iterator = diskElements.entrySet().iterator(); iterator.hasNext(); ) { final Map.Entry entry = (Map.Entry) iterator.next(); final DiskElement diskElement = (DiskElement) entry.getValue(); if (now >= diskElement.expiryTime) { // An expired element if (LOG.isDebugEnabled()) { LOG.debug( name + "Cache: Removing expired spool element " + entry.getKey() + " from Disk Store"); } iterator.remove(); // only load the element from the file if there is a listener interested in hearing about // its expiration if (listeners.hasCacheEventListeners()) { try { element = loadElementFromDiskElement(diskElement); notifyExpiryListeners(element); } catch (Exception exception) { LOG.error( name + "Cache: Could not remove disk store entry for " + entry.getKey() + ". Error was " + exception.getMessage(), exception); } } freeBlock(diskElement); } } } }
/** {@inheritDoc} */ public boolean put(Element element) throws CacheException { LOG.debug("cache {} put {}", cache.getName(), element); // this forces enlistment so the XA transaction timeout can be propagated to the XA resource getOrCreateTransactionContext(); Element oldElement = getQuietFromUnderlyingStore(element.getObjectKey()); return internalPut(new StorePutCommand(oldElement, copyElementForWrite(element))); }
@Test public void testLFUEvictionFromDiskStore() throws IOException, InterruptedException { Cache cache = new Cache( "testNonPersistent", 1, MemoryStoreEvictionPolicy.LFU, true, null, false, 2000, 1000, false, 1, null, null, 10); manager.addCache(cache); Store store = cache.getStore(); Element element; assertEquals(0, store.getSize()); for (int i = 0; i < 11; i++) { element = new Element("key" + i, "value" + i); cache.put(element); if (i > 0) { cache.get("key" + i); cache.get("key" + i); cache.get("key" + i); cache.get("key" + i); } } // allow to move through spool Thread.sleep(220); assertEquals(10, store.getOnDiskSize()); element = new Element("keyNew", "valueNew"); store.put(element); // allow to get out of spool Thread.sleep(220); assertEquals(10, store.getOnDiskSize()); // check new element not evicted assertNotNull(store.get(element.getObjectKey())); // check evicted honours LFU policy assertNull(store.get("key0")); for (int i = 0; i < 2000; i++) { store.put(new Element("" + i, new Date())); } // wait for spool to empty waitLonger(); assertEquals(10, store.getOnDiskSize()); }
/** * Saves the key to our fast access AtomicReferenceArray * * @param elementJustAdded the new element */ protected void saveKey(Element elementJustAdded) { int index = incrementIndex(); Object key = keyArray.get(index); Element oldElement = null; if (key != null) { oldElement = (Element) map.get(key); } if (oldElement != null) { if (policy.compare(oldElement, elementJustAdded)) { // new one will always be more desirable for eviction as no gets yet, unless no gets on old // one. // Consequence of this algorithm keyArray.set(index, elementJustAdded.getObjectKey()); } } else { keyArray.set(index, elementJustAdded.getObjectKey()); } }
private void evictIfRequired(final K key, final V value) { if (maxEntriesLocalHeap == 0) { return; } int evictions = MAX_EVICTIONS; while (maxEntriesLocalHeap < mappingCount() && evictions-- > 0) { final Element evictionCandidate = findEvictionCandidate(key, value); if (evictionCandidate != null) { remove(evictionCandidate.getObjectKey(), evictionCandidate, callback); } } }
private void writeOrReplaceEntry(Object object) throws IOException { Element element = (Element) object; if (element == null) { return; } final Serializable key = (Serializable) element.getObjectKey(); removeOldEntryIfAny(key); if (maxElementsOnDisk > 0 && diskElements.size() >= maxElementsOnDisk) { evictLfuDiskElement(); } writeElement(element, key); }
/** * Removes the element chosen by the eviction policy * * @param elementJustAdded it is possible for this to be null */ protected void removeElementChosenByEvictionPolicy(Element elementJustAdded) { if (LOG.isLoggable(Level.FINEST)) { LOG.finest("Cache is full. Removing element ..."); } Element element = findEvictionCandidate(elementJustAdded); // this CAN happen rarely. Let the store get one bigger if (element == null) { LOG.info("Eviction selection miss. Selected element is null"); return; } // If the element is expired remove if (element.isExpired()) { remove(element.getObjectKey()); notifyExpiry(element); return; } evict(element); remove(element.getObjectKey()); }
/** {@inheritDoc} */ public boolean replace(Element old, Element element, ElementValueComparator comparator) throws NullPointerException, IllegalArgumentException { LOG.debug("cache {} replace2 {}", cache.getName(), element); XATransactionContext context = getOrCreateTransactionContext(); Element previous = getCurrentElement(element.getKey(), context); boolean replaced = false; if (previous != null && comparator.equals(previous, copyElementForWrite(old))) { Element oldElement = getQuietFromUnderlyingStore(element.getObjectKey()); Element elementForWrite = copyElementForWrite(element); context.addCommand(new StorePutCommand(oldElement, elementForWrite), elementForWrite); replaced = true; } return replaced; }
/** * Create a new write operation for a particular element and creation time * * @param element the element to write * @param creationTime the creation time of the operation */ public WriteOperation(Element element, long creationTime) { this.element = new Element( element.getObjectKey(), element.getObjectValue(), element.getVersion(), element.getCreationTime(), element.getLastAccessTime(), element.getHitCount(), false, element.getTimeToLive(), element.getTimeToIdle(), element.getLastUpdateTime()); this.creationTime = creationTime; }
/** * Called immediately after an element has been removed. The remove method will block until this * method returns. * * <p>This implementation queues the removal notification for in order replication to peers. * * @param cache the cache emitting the notification * @param element just deleted */ public final void notifyElementRemoved(final Ehcache cache, final Element element) throws CacheException { if (!replicateRemovals) { return; } if (!element.isKeySerializable()) { if (LOG.isWarnEnabled()) { LOG.warn( "Key " + element.getObjectKey() + " is not Serializable and cannot be replicated."); } return; } addToReplicationQueue( new CacheEventMessage(EventMessage.REMOVE, cache, null, element.getKey())); }
@Override public void after(Object target, Object result, Throwable throwable, Object[] args) { if (isDebug) { logger.afterInterceptor(target, args, result, throwable); } try { ((CacheNameAccessor) target)._$PINPOINT$_setCacheName(DEFAULT_FRONTCACHE_NAME); if (args[0] instanceof Element) { Element element = (Element) args[0]; ((CacheKeyAccessor) target)._$PINPOINT$_setCacheKey(element.getObjectKey()); } } catch (Exception e) { logger.error("failed to add metadata: {}", e); } }
private void notifyEvictionListeners(DiskElement diskElement) { RegisteredEventListeners listeners = cache.getCacheEventNotificationService(); // only load the element from the file if there is a listener interested in hearing about its // expiration if (listeners.hasCacheEventListeners()) { Element element = null; try { element = loadElementFromDiskElement(diskElement); cache.getCacheEventNotificationService().notifyElementEvicted(element, false); } catch (Exception exception) { LOG.error( name + "Cache: Could not notify disk store eviction of " + element.getObjectKey() + ". Error was " + exception.getMessage(), exception); } } }
/** * Spools all elements to disk, in preparation for shutdown. * * <p>Relies on being called from a synchronized method * * <p>This revised implementation is a little slower but avoids using increased memory during the * method. */ protected final void spoolAllToDisk() { Object[] keys = getKeyArray(); for (int i = 0; i < keys.length; i++) { Element element = (Element) map.get(keys[i]); if (element != null) { if (!element.isSerializable()) { if (LOG.isLoggable(Level.FINE)) { LOG.fine( "Object with key " + element.getObjectKey() + " is not Serializable and is not being overflowed to disk."); } } else { spoolToDisk(element); // Don't notify listeners. They are not being removed from the cache, only a store remove(keys[i]); } } } }
/** * {@inheritDoc} * * <p>This implementation queues the put notification for in-order replication to peers. * * @param cache the cache emitting the notification * @param element the element which was just put into the cache. */ public final void notifyElementPut(final Ehcache cache, final Element element) throws CacheException { if (notAlive()) { return; } if (!replicatePuts) { return; } if (!element.isSerializable()) { if (LOG.isWarnEnabled()) { LOG.warn( "Object with key " + element.getObjectKey() + " is not Serializable and cannot be replicated"); } return; } addToReplicationQueue(new CacheEventMessage(EventMessage.PUT, cache, element, null)); }
/** * Evict the <code>Element</code>. * * <p>Evict means that the <code>Element</code> is: * * <ul> * <li>if, the store is diskPersistent, the <code>Element</code> is spooled to the DiskStore * <li>if not, the <code>Element</code> is removed. * </ul> * * @param element the <code>Element</code> to be evicted. */ protected final void evict(Element element) throws CacheException { boolean spooled = false; if (cache.getCacheConfiguration().isOverflowToDisk()) { if (!element.isSerializable()) { if (LOG.isLoggable(Level.FINE)) { LOG.log( Level.FINE, new StringBuffer("Object with key ") .append(element.getObjectKey()) .append(" is not Serializable and cannot be overflowed to disk") .toString()); } } else { spoolToDisk(element); spooled = true; } } if (!spooled) { cache.getCacheEventNotificationService().notifyElementEvicted(element, false); } }
/** {@inheritDoc} */ public Object getKey() { return element.getObjectKey(); }
private void possiblyTriggerRefresh(Element elem, long timeToRefreshMillis) { if (checkForRefresh(elem, System.currentTimeMillis(), timeToRefreshMillis)) { // now add the key to the queue. smallest overhead we could get. refreshWorkQueue.offer(elem.getObjectKey()); } }
/** * Puts the element in the DiskStore. Should only be called if overflowToDisk is true * * <p>Relies on being called from a synchronized method * * @param element The Element */ protected void spoolToDisk(Element element) { diskStore.put(element); if (LOG.isLoggable(Level.FINE)) { LOG.fine(cache.getName() + "Cache: spool to disk done for: " + element.getObjectKey()); } }
@Test public void testTimeToLive() { Assert.assertEquals(_VALUE_1, _ehcachePortalCache.get(_KEY_1)); Assert.assertNull(_ehcachePortalCache.get(_KEY_2)); // Put int timeToLive = 600; _ehcachePortalCache.put(_KEY_2, _VALUE_2, timeToLive); Ehcache ehcache = _ehcachePortalCache.ehcache; Element element = ehcache.get(_KEY_2); Assert.assertEquals(_KEY_2, element.getObjectKey()); Assert.assertEquals(_VALUE_2, element.getObjectValue()); Assert.assertEquals(timeToLive, element.getTimeToLive()); _defaultCacheListener.assertPut(_KEY_2, _VALUE_2, timeToLive); _defaultCacheListener.reset(); // Put if absent ehcache.removeElement(element); _ehcachePortalCache.putIfAbsent(_KEY_2, _VALUE_2, timeToLive); element = ehcache.get(_KEY_2); Assert.assertEquals(_KEY_2, element.getObjectKey()); Assert.assertEquals(_VALUE_2, element.getObjectValue()); Assert.assertEquals(timeToLive, element.getTimeToLive()); _defaultCacheListener.assertPut(_KEY_2, _VALUE_2, timeToLive); _defaultCacheListener.reset(); // Replace 1 ehcache.removeElement(element); _ehcachePortalCache.replace(_KEY_1, _VALUE_2, timeToLive); element = ehcache.get(_KEY_1); Assert.assertEquals(_KEY_1, element.getObjectKey()); Assert.assertEquals(_VALUE_2, element.getObjectValue()); Assert.assertEquals(timeToLive, element.getTimeToLive()); _defaultCacheListener.assertUpdated(_KEY_1, _VALUE_2, timeToLive); _defaultCacheListener.reset(); // Replace 2 ehcache.removeElement(element); _ehcachePortalCache.put(_KEY_1, _VALUE_1); Assert.assertEquals(_VALUE_1, _ehcachePortalCache.get(_KEY_1)); Assert.assertNull(_ehcachePortalCache.get(_KEY_2)); _ehcachePortalCache.replace(_KEY_1, _VALUE_1, _VALUE_2, timeToLive); element = ehcache.get(_KEY_1); Assert.assertEquals(_KEY_1, element.getObjectKey()); Assert.assertEquals(_VALUE_2, element.getObjectValue()); Assert.assertEquals(timeToLive, element.getTimeToLive()); _defaultCacheListener.assertPut(_KEY_1, _VALUE_1); _defaultCacheListener.assertUpdated(_KEY_1, _VALUE_2, timeToLive); _defaultCacheListener.reset(); }
/** * Puts an item in the cache. Note that this automatically results in an eviction if the store is * full. * * @param element the element to add */ public final void put(Element element) throws CacheException { if (element != null) { map.put(element.getObjectKey(), element); doPut(element); } }