001/* 
002 * JKNIV, utils - Helper utilities for jdk code.
003 * 
004 * Copyright (C) 2017, the original author or authors.
005 *
006 * This library is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 2.1 of the License.
010 * 
011 * This library is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 * Lesser General Public License for more details.
015 * 
016 * You should have received a copy of the GNU Lesser General Public
017 * License along with this library; if not, write to the Free Software Foundation, Inc., 
018 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
019 */
020package net.sf.jkniv.cache;
021
022import java.util.HashMap;
023import java.util.Map;
024import java.util.Set;
025import java.util.concurrent.Executors;
026import java.util.concurrent.ScheduledExecutorService;
027import java.util.concurrent.ScheduledFuture;
028import java.util.concurrent.TimeUnit;
029
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032
033/**
034 * Manager the live of cache entries.
035 * 
036 * To enable logging for cache configure logger category:
037 * 
038 * <pre>
039 *   &#60;logger name="jkniv.utils.CACHE" additivity="false"&#62;
040 *     &#60;level value="debug" /&#62;
041 *     &#60;appender-ref ref="console" /&#62;
042 *   &#60;/logger&#62;
043 * </pre>
044 * 
045 * @author Alisson Gomes
046 * @since 0.6.0
047 */
048public class CacheManager<K, V>
049{
050    private static final Logger                LOG       = LoggerFactory.getLogger("jkniv.utils.CACHE");
051    private final ScheduledExecutorService     scheduler = Executors.newScheduledThreadPool(1);
052    private final Map<String, Cacheable<K, V>> caches;
053    private final Map<String, CachePolicy>     policies;
054    private ScheduledFuture<?>                 poolScheduler;
055    private final long                         delay;
056    private final long                         period;
057    private final CachePolicy                  policy;
058    
059    /**
060     * Create a new cache manager with 5 seconds for initial delay to first execution
061     * and 5 minutes for period between successive executions.
062     */
063    public CacheManager()
064    {
065        this(5L, TimeUnit.MINUTES.toSeconds(5L));
066    }
067    
068    /**
069     * Create a new cache manager with 5 seconds for initial delay to first execution
070     * and configurable parameter for {@code period} to between successive executions.
071     * @param period the period between successive executions
072     */
073    public CacheManager(long period)
074    {
075        this(5L, period);
076    }
077    
078    /**
079     * Create a new cache manager with configurable {@code initialDelay} and {@code period}.
080     * @param initialDelay seconds to the time to delay first execution
081     * @param period seconds to the period between successive executions
082     */
083    public CacheManager(long initialDelay, long period)
084    {
085        this(initialDelay, period,
086                new TTLCachePolicy(CachePolicy.DEFAULT_TTL, CachePolicy.DEFAULT_TTI, TimeUnit.MINUTES));
087    }
088    
089    /**
090     * Create a new cache manager with configurable {@code initialDelay} and {@code period}.
091     * @param delay seconds to the time to delay first execution
092     * @param period seconds to the period between successive executions
093     * @param policy default policy for cache entries {@link #add(String, Cacheable)}
094     */
095    public CacheManager(long delay, long period, CachePolicy policy)
096    {
097        this.delay = delay;
098        this.period = period;
099        this.caches = new HashMap<String, Cacheable<K, V>>();
100        this.policies = new HashMap<String, CachePolicy>();
101        this.policy = policy;
102    }
103    
104    /**
105     * Add new policy with specific name
106     * @param policyName name of policy
107     * @param policy policy to manager a cache
108     * @return the previous value associated with <code>policyName</code>, 
109     * or <code>null</code> if there was no mapping for <code>policyName</code>
110     */
111    public CachePolicy add(String policyName, CachePolicy policy)
112    {
113        return this.policies.put(policyName, policy);
114    }
115    
116    /**
117     * Add a new cache for manager
118     * @param key cache identify 
119     * @param cache the managed cache
120     * @return the previous value associated with <code>key</code>, 
121     * or <code>null</code> if there was no mapping for <code>key</code>
122     */
123    public Cacheable<K, V> add(String key, Cacheable<K, V> cache)
124    {
125        return this.caches.put(key, cache);
126    }
127    
128    /**
129     * Add a new cache for manager
130     * @param key cache identify
131     * @param policyName name of policy, the cache no exist a default cache is associated
132     * @param cache the managed cache
133     * @return the previous value associated with <code>key</code>, 
134     * or <code>null</code> if there was no mapping for <code>key</code>
135     */
136    public Cacheable<K, V> add(String key, String policyName, Cacheable<K, V> cache)
137    {
138        CachePolicy policy = this.policies.get(policyName);
139        if (policy == null)
140        {
141            LOG.warn("There is no a cache policy named [{}] associating default cache for [{}]", policyName, key);
142            policy = this.policy;
143        }
144        cache.setPolicy(policy);
145        return this.caches.put(key, cache);
146    }
147    
148    /**
149     * Create a new Memory cache to be managed with a default policy
150     * @param key cache identify
151     * @return the new Cache created
152     */
153    public Cacheable<K, V> add(String key)
154    {
155        Cacheable<K, V> cacheable = new MemoryCache<K, V>(this.policy, key);
156        this.caches.put(key, cacheable);
157        return cacheable;
158    }
159    
160    public void pooling()
161    {
162        //[jkniv-cache-man]
163        final Runnable watching = new WatchCaches(caches);
164        this.poolScheduler = scheduler.scheduleAtFixedRate(watching, this.delay, this.period, TimeUnit.SECONDS);
165        LOG.info("Watching cacheable data for [{}] caches", size());
166    }
167    
168    public void cancel()
169    {
170        LOG.info("Canceling cacheable manager");
171        this.clear();
172        if (this.poolScheduler != null)
173            this.poolScheduler.cancel(true);
174    }
175    
176    private void clear()
177    {
178        Set<Map.Entry<String, Cacheable<K, V>>> entries = caches.entrySet();
179        for (Map.Entry<String, Cacheable<K, V>> entry : entries)
180        {
181            LOG.info("Cache [{}] has {} objects removed", entry.getValue().getName(), entry.getValue().size());
182            Cacheable<K, V> cacheable = entry.getValue();
183            cacheable.clear();
184        }
185        caches.clear();
186    }
187    
188    /**
189     * Returns the total of elements in all cache entries in this manager. 
190     * @return the sum of all cache elements in this manager 
191     */
192    public long total()
193    {
194        long size = 0L;
195        for (Map.Entry<String, Cacheable<K, V>> entry : caches.entrySet())
196        {
197            size += entry.getValue().size();
198        }
199        return size;
200    }
201    
202    /**
203     * Returns the number of caches in this manager
204     * @return the number of cache.
205     */
206    public int size()
207    {
208        return this.caches.size();
209    }
210    
211    class WatchCaches implements Runnable
212    {
213        private final Map<String, Cacheable<K, V>> caches;
214        
215        public WatchCaches(Map<String, Cacheable<K, V>> caches)
216        {
217            this.caches = caches;
218        }
219        
220        @Override
221        public void run()
222        {
223            int i = 0;
224            LOG.info("Managing [{}] caches", caches.size());
225            Set<Map.Entry<String, Cacheable<K, V>>> entries = caches.entrySet();
226            for (Map.Entry<String, Cacheable<K, V>> entry : entries)
227            {
228                Cacheable<K, V> cacheable = entry.getValue();
229                Set<Map.Entry<K, Cacheable.Entry<V>>> entrySet = cacheable.entrySet();
230                for (Map.Entry<K, Cacheable.Entry<V>> cacheableEntry : entrySet)
231                {
232                    Cacheable.Entry<V> value = cacheableEntry.getValue();
233                    if (!cacheable.getPolicy().isAlive(value.getTimestamp().getTime()))
234                    {
235                        cacheable.remove(cacheableEntry.getKey());
236                        LOG.debug("The object [{}] was removed from cache {}", cacheableEntry.getKey(), cacheable.getName());
237                        i++;
238                    }
239                }
240                LOG.info("[{}] objects are removed from [{}] cache", i, cacheable.getName());
241            }
242        }
243    }
244    
245}