Coverage Report - net.admin4j.util.ExpiringCache
 
Classes in this File Line Coverage Branch Coverage Complexity
ExpiringCache
47%
27/57
37%
9/24
2.583
ExpiringCache$DatedValue
100%
4/4
N/A
2.583
 
 1  
 /*
 2  
  * This software is licensed under the Apache License, Version 2.0
 3  
  * (the "License") agreement; you may not use this file except in compliance with
 4  
  * the License.  You may obtain a copy of the License at
 5  
  * 
 6  
  *      http://www.apache.org/licenses/LICENSE-2.0
 7  
  * 
 8  
  * Unless required by applicable law or agreed to in writing, software
 9  
  * distributed under the License is distributed on an "AS IS" BASIS,
 10  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 11  
  * See the License for the specific language governing permissions and
 12  
  * limitations under the License.
 13  
  */
 14  
 package net.admin4j.util;
 15  
 
 16  
 import java.util.Iterator;
 17  
 import java.util.Set;
 18  
 import java.util.concurrent.ConcurrentHashMap;
 19  
 
 20  
 /**
 21  
  * Cache which will time and expire entries on a configurable basis.  This is
 22  
  * intended to be thread-safe.
 23  
  * 
 24  
  * <p>Note:  
 25  
  * <li>Purge and expiration intervals intentionally made immutable.</li>
 26  
  * <li>Nulling out a reference isn't enough to garbage-collect an ExpiringCache -- 
 27  
  * Once it's instantiated, it'll be permanently referenced by the JVM.</li>
 28  
  * 
 29  
  * @author D. Ashmore
 30  
  *
 31  
  * 
 32  
  */
 33  45
 public class ExpiringCache 
 34  
 {
 35  
     private final long        purgeIntervalInMillis;
 36  
     private final long  expirationTimeInMillis;
 37  
     
 38  
     private long nextPurgeTime;
 39  
     
 40  21
     private final ConcurrentHashMap<Object, Object> cacheMap = new ConcurrentHashMap<Object, Object>();
 41  
     
 42  
     public static final long DEFAULT_PURGE_INTERVAL_IN_MILLIS = 60000 * 60 * 4;         // 4 hours
 43  
     public static final long DEFAULT_EXPIRATION_TIME_IN_MILLIS = 60000 * 60;                 // 1 hour
 44  
     
 45  21
     private static final ExpiringCacheCleanupTask     CLEANUP_TASK = new ExpiringCacheCleanupTask();
 46  
     @SuppressWarnings("unused")
 47  21
     private static final Daemon CLEANUP_DAEMON = new Daemon(CLEANUP_TASK, "Admin4J-Expiring-Cache-Cleanup", 600000);
 48  
     
 49  
     
 50  
     /**
 51  
      * Purge and expiration times defaulted.
 52  
      *
 53  
      */
 54  
     public ExpiringCache()
 55  0
     {
 56  0
         this.purgeIntervalInMillis = DEFAULT_PURGE_INTERVAL_IN_MILLIS;
 57  0
         this.expirationTimeInMillis = DEFAULT_EXPIRATION_TIME_IN_MILLIS;
 58  0
         this.setNextPurgeTime();
 59  0
         CLEANUP_TASK.registerCache(this);
 60  0
     }
 61  
     
 62  
     public ExpiringCache(long expirationTimeMillis, long purgeIntervalMillis)
 63  21
     {
 64  21
         if (expirationTimeMillis < 0) 
 65  0
             throw new IllegalArgumentException("Negative expiration not allowed: " + expirationTimeMillis);
 66  21
         if (purgeIntervalMillis < 0) 
 67  0
             throw new IllegalArgumentException("Negative purge interval not allowed: " + expirationTimeMillis);
 68  
 
 69  21
         this.purgeIntervalInMillis = purgeIntervalMillis;
 70  21
         this.expirationTimeInMillis = expirationTimeMillis;
 71  21
         this.setNextPurgeTime();
 72  21
         CLEANUP_TASK.registerCache(this);
 73  21
     }
 74  
     
 75  
     private synchronized void setNextPurgeTime()
 76  
     {
 77  21
                this.nextPurgeTime = System.currentTimeMillis() + this.purgeIntervalInMillis;
 78  21
     }
 79  
     
 80  
      /**
 81  
      * Iterate through content and purge all expiring entries.
 82  
      *
 83  
      */
 84  
     @SuppressWarnings("rawtypes")
 85  
     protected synchronized void purgeExpiredContent()
 86  
     {
 87  0
         long currentTime = System.currentTimeMillis();
 88  0
         if (currentTime < this.nextPurgeTime)  return;
 89  
         
 90  0
         this.setNextPurgeTime();
 91  
 
 92  0
         Set entrySet = this.cacheMap.entrySet();
 93  0
         Iterator entryIt = entrySet.iterator();
 94  
         DatedValue dv;
 95  
         ConcurrentHashMap.Entry entry;
 96  
         
 97  
         // Iterate through the entries purging those that are expired.
 98  0
         while (entryIt.hasNext())
 99  
         {
 100  0
             entry = (ConcurrentHashMap.Entry) entryIt.next();
 101  0
             if (entry.getValue() != null)
 102  
             {
 103  0
                 dv = (DatedValue) entry.getValue();
 104  0
                 if (currentTime > dv.expirationTime)
 105  
                 {
 106  0
                     this.cacheMap.remove(entry.getKey(), entry.getValue());
 107  
                 }
 108  
             }
 109  
         }
 110  0
     }
 111  
     
 112  
     /**
 113  
      * Retrieves an object from the cache.
 114  
      * @param key
 115  
      * @return
 116  
      */
 117  
     public Object get(Object key)
 118  
     {
 119  48
         if (key == null)  throw new IllegalArgumentException("Null key not allowed.");
 120  
         
 121  48
         Object obj = this.cacheMap.get(key);
 122  48
         Object value = null;
 123  
         
 124  48
         if (obj != null)
 125  
         {
 126  12
             DatedValue dv = (DatedValue) obj;
 127  12
             if (System.currentTimeMillis() > dv.expirationTime)
 128  
             {
 129  3
                 this.cacheMap.remove(key, obj);     // Value past expiration
 130  
             }
 131  9
             else value = dv.value;
 132  
         }
 133  
         
 134  48
         return value;
 135  
     }
 136  
     
 137  
     /**
 138  
      * Puts an object in cache.
 139  
      * @param key
 140  
      * @param value
 141  
      */
 142  
     public void put(Object key, Object value)
 143  
     {
 144  45
         if (key == null)  throw new IllegalArgumentException("Null key not allowed.");
 145  45
         if (value == null)  throw new IllegalArgumentException("Null value not allowed.");
 146  
         
 147  45
         this.cacheMap.put(key, new DatedValue(value));
 148  45
     }
 149  
     
 150  
     /**
 151  
      * Removes an object from cache.
 152  
      * @param key
 153  
      * @return
 154  
      */
 155  
     public void remove(Object key)
 156  
     {
 157  0
         if (key == null)  throw new IllegalArgumentException("Null key not allowed.");
 158  
         
 159  0
         this.cacheMap.remove(key);
 160  0
     }
 161  
     
 162  
     public int size()
 163  
     {
 164  0
         return this.cacheMap.size();
 165  
     }
 166  
     
 167  
     /**
 168  
      * Clears all entries in cache.
 169  
      *
 170  
      */
 171  
     public synchronized void clear()
 172  
     {
 173  0
         this.setNextPurgeTime();
 174  0
         this.cacheMap.clear();
 175  0
     }
 176  
     
 177  
     /**
 178  
      * Clears all content for all expiring caches.  
 179  
      * 
 180  
      * <p>Warning:  Should only be called for administrative and testing purposes.
 181  
      *
 182  
      */
 183  
     public static synchronized void clearAll()
 184  
     {
 185  0
         CLEANUP_TASK.clearAllCacheContent();
 186  0
     }
 187  
     
 188  
     private class DatedValue
 189  
     {
 190  45
         public final long expirationTime = System.currentTimeMillis() + expirationTimeInMillis;
 191  
         public Object value;
 192  
         
 193  
         public DatedValue(Object v)
 194  45
         {
 195  45
             this.value = v;
 196  45
         }
 197  
     }
 198  
 
 199  
     public long getExpirationTimeInMillis() {
 200  0
         return expirationTimeInMillis;
 201  
     }
 202  
 
 203  
 }