Java tutorial
/** * Copyright (c) 2010 Martin Geisse * * This file is distributed under the terms of the MIT license. */ package name.martingeisse.sql.cache; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import name.martingeisse.common.util.Wrapper; import name.martingeisse.sql.IDatabaseDescriptor; import com.google.common.base.Function; import com.google.common.cache.CacheBuilder; import com.google.common.cache.LoadingCache; import com.mysema.query.sql.RelationalPath; import com.mysema.query.types.Expression; import com.mysema.query.types.Predicate; /** * Represents a group of caches using {@link RowLoader}s. These caches store the * same values using different keys. Whenever a value is stored in one of the caches, * it is also stored in the other caches. They are not guaranteed to *contain* the * same values since they may evict values independently. * * @param <V> the cache value type */ public class RowCacheGroup<V> { /** * the caches */ private final List<LoadingCache<?, Wrapper<V>>> caches = new ArrayList<LoadingCache<?, Wrapper<V>>>(); /** * the cacheBuilder */ private final CacheBuilder<?, ?> cacheBuilder; /** * the database */ private final IDatabaseDescriptor database; /** * the path */ private final RelationalPath<V> path; /** * the additionalPredicates */ private final Predicate[] additionalPredicates; /** * the loaderToCache */ private final Map<AbstractDatabaseCacheLoader<?, Wrapper<V>>, LoadingCache<?, Wrapper<V>>> loaderToCache = new HashMap<AbstractDatabaseCacheLoader<?, Wrapper<V>>, LoadingCache<?, Wrapper<V>>>(); /** * the cacheToValueToKeyMapping */ private final Map<LoadingCache<?, Wrapper<V>>, Function<V, ?>> cacheToValueToKeyMapping = new HashMap<LoadingCache<?, Wrapper<V>>, Function<V, ?>>(); /** * Constructor. * @param cacheBuilder the cache builder to use * @param path the table path * @param additionalPredicates additional predicates (if any) */ public RowCacheGroup(CacheBuilder<?, ?> cacheBuilder, RelationalPath<V> path, Predicate... additionalPredicates) { this.cacheBuilder = cacheBuilder; this.database = null; this.path = path; this.additionalPredicates = additionalPredicates; } /** * Constructor. * @param cacheBuilder the cache builder to use * @param database the database to query from * @param path the table path * @param additionalPredicates additional predicates (if any) */ public RowCacheGroup(CacheBuilder<?, ?> cacheBuilder, IDatabaseDescriptor database, RelationalPath<V> path, Predicate... additionalPredicates) { this.cacheBuilder = cacheBuilder; this.database = database; this.path = path; this.additionalPredicates = additionalPredicates; } /** * This method is a shortcut for distribute(wrapper, null). * @param wrapper the wrapper to distribute */ public void distribute(Wrapper<V> wrapper) { distribute(wrapper, null); } /** * Distributes the specified value to all caches. This adds the value to all * caches except the origin, because the origin will store the value itself. * Specify null for the origin to distribute the value to all caches. * * @param wrapper the wrapper to distribute * @param origin the origin of the value */ public void distribute(Wrapper<V> wrapper, LoadingCache<?, ?> origin) { distributeHelper(wrapper, origin, false); } /** * */ private void distributeHelper(Wrapper<V> wrapper, LoadingCache<?, ?> origin, boolean external) { for (LoadingCache<?, Wrapper<V>> cache : caches) { if (cache != origin) { Function<?, ?> valueToKeyMapping = cacheToValueToKeyMapping.get(cache); distributeHelper(wrapper, valueToKeyMapping, cache); prepareCachedValue(wrapper, external); } } } /** * */ @SuppressWarnings({ "unchecked", "rawtypes" }) private void distributeHelper(Wrapper wrapper, Function valueToKeyMapping, LoadingCache cache) { Object key = valueToKeyMapping.apply(wrapper.getValue()); if (key != null) { cache.put(key, wrapper); } } /** * Creates a new cache using the specified key expression to fetch missing values, and the * valueToKeyMapping to generate keys for the cache. * * @param keyExpression the key expression * @param valueToKeyMapping the mapping that generates keys for the cache * @return the cache */ public <K> LoadingCache<K, Wrapper<V>> addCache(Expression<?> keyExpression, Function<V, K> valueToKeyMapping) { // create the loader AbstractDatabaseCacheLoader<K, Wrapper<V>> rowLoader = new RowLoader<K, V>(path, keyExpression, additionalPredicates) { @Override protected Wrapper<V> transformValue(K key, V row) { Wrapper<V> wrapper = super.transformValue(key, row); if (wrapper.getValue() != null) { distributeHelper(wrapper, loaderToCache.get(this), true); } return wrapper; } }; if (database != null) { rowLoader = rowLoader.withDatabase(database); } // create the cache @SuppressWarnings("unchecked") CacheBuilder<K, Wrapper<V>> typedCacheBuilder = (CacheBuilder<K, Wrapper<V>>) cacheBuilder; LoadingCache<K, Wrapper<V>> cache = typedCacheBuilder.build(rowLoader); caches.add(cache); loaderToCache.put(rowLoader, cache); cacheToValueToKeyMapping.put(cache, valueToKeyMapping); return cache; } /** * This method can be used to prepare cached values for use. The default implementation * does nothing. * * @param wrapper the cached wrapper (never null) * @param external whether the value comes from an external source via one of the * distribute() methods. */ protected void prepareCachedValue(Wrapper<V> wrapper, boolean external) { } }