All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.anthavio.cache.CacheBase Maven / Gradle / Ivy

The newest version!
package net.anthavio.cache;

import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;

import net.anthavio.cache.Builders.CacheReadyRequestLoaderBuilder;
import net.anthavio.cache.CacheEntryLoader.CacheEntryLoadResult;
import net.anthavio.cache.CacheEntryLoader.CacheLoaderException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Abstract base Cache. Allows easier implementation of the Cache interface
 * 
 * @author martin.vanek
 *
 */
public abstract class CacheBase implements Cache {

	protected final Logger logger = LoggerFactory.getLogger(getClass());

	protected final String name;

	private Scheduler scheduler;

	private final CacheKeyProvider keyProvider;

	public CacheBase(String name, CacheKeyProvider keyProvider) {
		this(name, keyProvider, null);
	}

	public CacheBase(String name, CacheKeyProvider keyProvider, ExecutorService executor) {
		if (name == null || name.isEmpty()) {
			throw new IllegalArgumentException("Wrong name: " + name);
		}
		this.name = name;

		if (keyProvider == null) {
			throw new IllegalArgumentException("Null keyProvider");
		}
		this.keyProvider = keyProvider;

		if (executor != null) {
			this.scheduler = new Scheduler(this, executor);
		} else {
			this.scheduler = null;
		}

	}

	public String getName() {
		return this.name;
	}

	/**
	 * Close undelying scheduler (if created)
	 */
	@Override
	public void close() {
		logger.info("Cache close " + getName());
		if (scheduler != null) {
			scheduler.close();
		}
	}

	/**
	 * Cache implementation can use user provided key 
	 * - add prefix or namespace 
	 * - translate it to something else 
	 * - hash it when it is too long
	 */
	public String getCacheKey(K userKey) {
		return keyProvider.provideKey(userKey);
	}

	/**
	 * @return builder to create complex CacheRequest
	 */
	public CacheReadyRequestLoaderBuilder with(CacheEntryLoader key) {
		return new CacheReadyRequestLoaderBuilder(this, key);
	}

	@Override
	public final CacheEntry get(K userKey) {
		if (userKey == null) {
			throw new IllegalArgumentException("Key must not be null");
		}
		String cacheKey = getCacheKey(userKey);
		if (logger.isDebugEnabled()) {
			logger.debug("Cache get: " + userKey + " (" + cacheKey + ")");
		}

		CacheEntry entry;
		try {
			entry = doGet(cacheKey);
		} catch (Exception x) {
			logger.warn("Failed to get value for " + userKey + " (" + cacheKey + ")", x);
			return null;
		}

		if (entry != null && entry.isEvicted()) {
			logger.warn("Cache returned hard expired entry: " + entry + " for " + userKey + " (" + cacheKey + ")");
			//XXX maybe throw away hard expired entry and return null
		}
		if (logger.isDebugEnabled()) {
			if (entry != null) {
				logger.debug("Cache hit: " + userKey + " (" + cacheKey + ")" + " entry: " + entry);
			} else {
				logger.debug("Cache mis: " + userKey + " (" + cacheKey + ")");
			}
		}
		return entry;
	}

	protected abstract CacheEntry doGet(String cacheKey) throws Exception;

	@Override
	public Boolean set(K userKey, V data, long evictTtl, TimeUnit unit) {
		CacheEntry entry = new CacheEntry(data, evictTtl, evictTtl, unit);
		return set(userKey, entry);
	}

	@Override
	public final Boolean set(K userKey, CacheEntry entry) {
		if (userKey == null) {
			throw new IllegalArgumentException("Key must not be blank");
		}
		String cacheKey = getCacheKey(userKey);
		if (logger.isDebugEnabled()) {
			logger.debug("Cache set: " + userKey + " (" + cacheKey + ")");
		}
		if (entry.getEvictTtl() < 1) {
			throw new IllegalArgumentException("Evict TTL " + entry.getEvictTtl() + "is < 1");
		}
		try {
			entry.setStoredAt(new Date());
			return doSet(cacheKey, entry);
		} catch (Exception x) {
			entry.setStoredAt(null);
			logger.warn("Failed to set: " + userKey + " (" + cacheKey + ")", x);
			return Boolean.FALSE;
		}
	}

	protected abstract Boolean doSet(String cacheKey, CacheEntry entry) throws Exception;

	@Override
	public final Boolean remove(K userKey) {
		if (userKey == null) {
			throw new IllegalArgumentException("Key must not be blank");
		}
		String cacheKey = getCacheKey(userKey);
		if (logger.isDebugEnabled()) {
			logger.debug("Cache rem: " + userKey + " (" + cacheKey + ")");
		}
		try {
			return doRemove(cacheKey);
		} catch (Exception x) {
			logger.warn("Failed to remove: " + userKey + " (" + cacheKey + ")", x);
			return Boolean.FALSE;
		}
	}

	protected abstract Boolean doRemove(String cacheKey) throws Exception;

	/**
	 * Client Cache get 
	 */
	public CacheEntry get(CacheLoadRequest request) {
		if ((request.isMissingLoadAsync() || request.isExpiredLoadAsync()) && scheduler == null) {
			throw new IllegalStateException("Scheduler must be configured to execute asynchronous requests");
		}
		K userKey = request.getUserKey();
		CacheEntry entry = get(userKey);
		if (entry != null) {
			if (!entry.isStale()) {
				return entry; //fresh hit
			} else {
				return load(request, entry);
			}
		} else { //cache miss - we have nothing
			return load(request, entry);
		}
	}

	/**
	 * Client enforced Cache load
	 */
	public CacheEntry load(CacheLoadRequest request, CacheEntry expiredEntry) {
		if (expiredEntry != null) {
			//soft expired - refresh needed
			if (request.isExpiredLoadAsync()) {
				scheduler.startReload(request, expiredEntry); //start asynchronous refresh
				//logger.debug("Soft expired value returned: " + userKey);
				return expiredEntry;
			} else {
				//logger.debug("Sync refresh start " + cacheKey);
				return load(false, request, expiredEntry);
			}
		} else { //cache miss - we have nothing
			if (request.isMissingLoadAsync()) {
				scheduler.startReload(request, null);
				return CacheEntry.EMPTY;
			} else {
				return load(false, request, null);
			}
		}
	}

	/**
	 * Load and store value into cache.
	 */
	protected CacheEntry load(boolean async, CacheLoadRequest request, CacheEntry expiredEntry) {
		CacheEntryLoadResult entry;
		K userKey = request.getUserKey();
		try {
			entry = request.getLoader().load(request, async, expiredEntry);
			if (entry.getCacheSet()) {
				set(request.getUserKey(), entry);
			}
			if (request instanceof ScheduledRequest) {
				((ScheduledRequest) request).setLastRefresh(System.currentTimeMillis());
			}
		} catch (Exception exception) {
			//No exceptions should be here
			if (async) {
				logger.error("Reload failed for key: " + userKey + " request: " + request);
				entry = null;
			} else {
				if (exception instanceof CacheLoaderException) {
					throw (CacheLoaderException) exception;
				} else {
					throw new CacheLoaderException(exception, request);
				}
			}

		}
		return entry;
	}

	/**
	 * Schedule this CacheRequest to be updated(refreshed) automatically in the background.
	 * 
	 * Also starts scheduler thread if it is not running yet.
	 */
	public void schedule(CacheLoadRequest request) {
		if (this.scheduler == null) {
			throw new IllegalStateException("Scheduler for asynchronous refresh is not configured");
		}
		scheduler.schedule(request);
	}

	public Scheduler getScheduler() {
		return scheduler;
	}

	public void setScheduler(Scheduler scheduler) {
		this.scheduler = scheduler;
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy