/**
* =============================================================================
*
* ORCID (R) Open Source
* http://orcid.org
*
* Copyright (c) 2012-2014 ORCID, Inc.
* Licensed under an MIT-Style License (MIT)
* http://orcid.org/open-source-license
*
* This copyright and license information (including a link to the full license)
* shall be included in its entirety in all copies or substantial portion of
* the software.
*
* =============================================================================
*/
package org.orcid.util;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.orcid.utils.listener.LastModifiedMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalListeners;
public class GenericExpiringQueue<T extends RemovalListener<String, LastModifiedMessage>> implements ApplicationListener<ContextClosedEvent> {
Logger LOG = LoggerFactory.getLogger(GenericExpiringQueue.class);
private final ExecutorService executor;
private final ScheduledExecutorService cleanup;
private final Cache<String, LastModifiedMessage> cacheQueue;
/**
* Create a LastUpdatedCacheQueue
*
* Creates a guava cache that expires entries five minutes after last
* access. On expiry, the listener is called which performs the update(s).
*
* Note that the cache performs cleanup and does removal as part of get/put
* operations (better performance in live env) I have made it do these
* ansynchronously so they do not block access to the cache In testing, set
* forceCleanup to true or call cache.cleanUp() manually. Forcecleanup
* delays 120 seconds before starting to clean.
*
* For more info on guava caches, see
* https://github.com/google/guava/wiki/CachesExplained Considered using
* DelayQueue, but this makes it far easier.
* http://stackoverflow.com/questions/27948867/efficiently-update-an-element
* -in-a-delayqueue
*
* This class registers itself to listen for context events to ensure
* threads close on exit
*
* @param secondsToWait
* how long the account should be inactive before processing
* @param forceCleanup
* if true, register a thread that automatically scans for
* inactive entries and evicts them.
* @param removalListener
* the logic to be applied when items are evicted from the cache.
*/
public GenericExpiringQueue(int secondsToWait, Boolean forceCleanup, T removalListener) {
LOG.info("Creating cacheQueue with " + secondsToWait + " seconds wait and forceCleanup = " + forceCleanup + " using "
+ removalListener.getClass().getSimpleName());
// create a thread that does the removal - we can fiddle with the
// Executor if we need more threads
executor = Executors.newCachedThreadPool();
// create the expiring cache
cacheQueue = CacheBuilder.newBuilder().expireAfterAccess(secondsToWait, TimeUnit.SECONDS)
.removalListener(RemovalListeners.asynchronous(removalListener, executor)).build();
// if we want to force cleanup regularly, we create a thread to do it
// here.
if (forceCleanup) {
cleanup = Executors.newSingleThreadScheduledExecutor();
cleanup.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
cacheQueue.cleanUp();
}
}, 120, 10, TimeUnit.SECONDS);
} else
cleanup = null;
}
/**
* Get the cache
*
* @return the underlying guava cache instance
*/
public Cache<String, LastModifiedMessage> getCache() {
return cacheQueue;
}
/**
* Add the executor thread to the Spring context shutdown so it doesn't
* prevent tomcat stopping.
*
*/
@Override
public void onApplicationEvent(ContextClosedEvent arg0) {
executor.shutdown();
if (cleanup != null)
cleanup.shutdown();
}
}