001    package echopoint.util.collections;
002    /* 
003     * This file is part of the Echo Point Project.  This project is a collection
004     * of Components that have extended the Echo Web Application Framework.
005     *
006     * Version: MPL 1.1/GPL 2.0/LGPL 2.1
007     *
008     * The contents of this file are subject to the Mozilla Public License Version
009     * 1.1 (the "License"); you may not use this file except in compliance with
010     * the License. You may obtain a copy of the License at
011     * http://www.mozilla.org/MPL/
012     *
013     * Software distributed under the License is distributed on an "AS IS" basis,
014     * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
015     * for the specific language governing rights and limitations under the
016     * License.
017     *
018     * Alternatively, the contents of this file may be used under the terms of
019     * either the GNU General Public License Version 2 or later (the "GPL"), or
020     * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
021     * in which case the provisions of the GPL or the LGPL are applicable instead
022     * of those above. If you wish to allow use of your version of this file only
023     * under the terms of either the GPL or the LGPL, and not to allow others to
024     * use your version of this file under the terms of the MPL, indicate your
025     * decision by deleting the provisions above and replace them with the notice
026     * and other provisions required by the GPL or the LGPL. If you do not delete
027     * the provisions above, a recipient may use your version of this file under
028     * the terms of any one of the MPL, the GPL or the LGPL.
029     */
030    
031    import java.lang.ref.WeakReference;
032    import java.util.ArrayList;
033    import java.util.Iterator;
034    import java.util.List;
035    import java.util.Set;
036    import java.util.Timer;
037    import java.util.TimerTask;
038    
039    
040    /**
041     * <code>TimerExpiryCache</code> is an a implementation
042     * of <code>ExpiryCache</code> that can contain
043     * objects that "expire".  A <b>shared</b> background task will 
044     * periodically "reap" objects that have "expired". 
045     * <p>
046     * By default, soft references are used to the cached data so that they can 
047     * be reclaimed in low memory conditions regardless of whether they 
048     * have expired or not.
049     * <p>
050     * The time-to-live and access-timeout is used to decide when an object 
051     * has expired and needs to be removed from the cache. 
052     * <p>
053     * Time-to-live is simple.  Once the specified period elapses, the object 
054     * is removed from the cache, regardless of how many times its been
055     * accessed.  
056     * <p>
057     * Access-timeout is a little more complicated.  Each time the object 
058     * is taken from the cache, its lastAccessTime is tracked.  If the
059     * access-timeout has expired (since its last access) then the object
060     * is taken from the cache.
061     * <p>
062     * If both the time-to-live and access-timeout is -1, then the object
063     * will never expire from the cache.
064     */
065    public class TimerExpiryCache extends ExpiryCache {
066    
067            /** 
068             * The reaper interval is 2 minutes.  Therefore the cached object 
069             * life span granulairty is +/- 1 minute. 
070             */
071            public static final long DEFAULT_REAPER_INTERVAL = 2 * 60 * 1000;
072    
073            /** This is an array of all ExpiryCaches that are to be reaped */
074            private static List allExpiryCaches;
075            
076            /** 
077             * The TimerExpiryCache reaper runs as a TimerTask under this static Timer.  
078             * All TimerExpiryCache instances share this common reaper.
079             */
080            private static Timer reaperTimer;
081            static {
082                    allExpiryCaches = new ArrayList();
083                    reaperTimer = new Timer(true);
084                    reaperTimer.schedule(new ReaperTimerTask(), DEFAULT_REAPER_INTERVAL, DEFAULT_REAPER_INTERVAL);
085            }
086    
087            /**
088             * Constructs a default <code>TimerExpiryCache</code>
089             */
090            public TimerExpiryCache() {
091                    this(DEFAULT_TIME_TO_LIVE,DEFAULT_ACCESS_TIMEOUT,true);
092            }
093    
094            /**
095             * Constructs a <code>TimerExpiryCache</code>
096             * 
097             * @param timeToLive - the default time-to-live for a cache entry
098             * @param accessTimeout - the default access timeout for a cache entry
099             */
100            public TimerExpiryCache(long timeToLive, long accessTimeout) {
101                    this(timeToLive,accessTimeout,true);
102            }
103    
104            /**
105             * Constructs a <code>TimerExpiryCache</code> with all the parameters
106             * 
107             * @param timeToLive - the default time-to-live for a cache entry
108             * @param accessTimeout - the default access timeout for a cache entry
109             * @param softReferences - whether soft refernces are used to cached data
110             */
111            public TimerExpiryCache(long timeToLive, long accessTimeout, boolean softReferences) {
112                    super(timeToLive,accessTimeout,softReferences);
113                    // add ourselves to the queue of TimerExpiryCaches for the reaper
114                    synchronized (allExpiryCaches) {
115                            allExpiryCaches.add(new WeakReference(this));
116                    }
117            }
118    
119            /** A shared TimrTask that reapers all of the various ExpiryCaches */
120            private static class ReaperTimerTask extends TimerTask {
121                    
122                    public void run() {
123                            //System.err.println("Cleanup");
124                            Thread currentThread = Thread.currentThread();
125                            if (currentThread.getPriority() != Thread.MIN_PRIORITY)
126                                    currentThread.setPriority(Thread.MIN_PRIORITY);
127                            
128                            synchronized (allExpiryCaches) {
129                                    for (Iterator iter = allExpiryCaches.iterator(); iter.hasNext();) {
130                                            WeakReference weakRef = (WeakReference) iter.next();
131                                            TimerExpiryCache expiryCache = (TimerExpiryCache) (weakRef == null ? null : weakRef.get());
132                                            //
133                                            // our global list is weakly referenced to the ExpiryCaches.  If
134                                            // its gone, then no one else was interested in the cache
135                                            // and neither are we
136                                            //
137                                            if (expiryCache == null) {
138                                                    iter.remove();
139                                                    continue;
140                                            }
141            
142                                            List list = new ArrayList();
143                                            synchronized (expiryCache) { 
144                                                    long now = System.currentTimeMillis();
145                                                    Set keys = expiryCache.keySet();
146                                                    Iterator itr = keys.iterator(); // Must be in synchronized block
147                                                    while (itr.hasNext()) {
148                                                            Object key = itr.next();
149                                                            if (expiryCache.hasExpired(key,now)) {
150                                                                    list.add(key);
151                                                            }
152                                                    }
153                                                    for (Iterator iter2 = list.iterator(); iter2.hasNext();) {
154                                                            //System.err.println("Removing :" + key);
155                                                            Object key = iter2.next();
156                                                            expiryCache.put(key,null);
157                                                    }
158                                            }
159                                    }       
160                            }                               
161                    }
162            };
163    }