View Javadoc

1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *  
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *  
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License. 
18   *  
19   */
20  package org.apache.mina.util;
21  
22  import java.util.Collection;
23  import java.util.Iterator;
24  import java.util.Map;
25  import java.util.Set;
26  
27  import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentHashMap;
28  import edu.emory.mathcs.backport.java.util.concurrent.CopyOnWriteArrayList;
29  import edu.emory.mathcs.backport.java.util.concurrent.locks.ReadWriteLock;
30  import edu.emory.mathcs.backport.java.util.concurrent.locks.ReentrantReadWriteLock;
31  
32  /**
33   * A map with expiration.
34   * 
35   * @author The Apache Directory Project (mina-dev@directory.apache.org)
36   */
37  public class ExpiringMap implements Map {
38      public static final int DEFAULT_TIME_TO_LIVE = 60;
39  
40      public static final int DEFAULT_EXPIRATION_INTERVAL = 1;
41  
42      private static volatile int expirerCount = 1;
43  
44      private final ConcurrentHashMap delegate;
45  
46      private final CopyOnWriteArrayList expirationListeners;
47  
48      private final Expirer expirer;
49  
50      public ExpiringMap() {
51          this(DEFAULT_TIME_TO_LIVE, DEFAULT_EXPIRATION_INTERVAL);
52      }
53  
54      public ExpiringMap(int timeToLive) {
55          this(timeToLive, DEFAULT_EXPIRATION_INTERVAL);
56      }
57  
58      public ExpiringMap(int timeToLive, int expirationInterval) {
59          this(new ConcurrentHashMap(), new CopyOnWriteArrayList(), timeToLive,
60                  expirationInterval);
61      }
62  
63      private ExpiringMap(ConcurrentHashMap delegate,
64              CopyOnWriteArrayList expirationListeners, int timeToLive,
65              int expirationInterval) {
66          this.delegate = delegate;
67          this.expirationListeners = expirationListeners;
68  
69          this.expirer = new Expirer();
70          expirer.setTimeToLive(timeToLive);
71          expirer.setExpirationInterval(expirationInterval);
72      }
73  
74      public Object put(Object key, Object value) {
75          return delegate.put(key, new ExpiringObject(key, value, System
76                  .currentTimeMillis()));
77      }
78  
79      public Object get(Object key) {
80          Object object = delegate.get(key);
81  
82          if (object != null) {
83              ExpiringObject expObject = (ExpiringObject) object;
84              expObject.setLastAccessTime(System.currentTimeMillis());
85  
86              return expObject.getValue();
87          }
88  
89          return object;
90      }
91  
92      public Object remove(Object key) {
93          return delegate.remove(key);
94      }
95  
96      public boolean containsKey(Object key) {
97          return delegate.containsKey(key);
98      }
99  
100     public boolean containsValue(Object value) {
101         return delegate.containsValue(value);
102     }
103 
104     public int size() {
105         return delegate.size();
106     }
107 
108     public boolean isEmpty() {
109         return delegate.isEmpty();
110     }
111 
112     public void clear() {
113         delegate.clear();
114     }
115 
116     public int hashCode() {
117         return delegate.hashCode();
118     }
119 
120     public Set keySet() {
121         return delegate.keySet();
122     }
123 
124     public boolean equals(Object obj) {
125         return delegate.equals(obj);
126     }
127 
128     public void putAll(Map inMap) {
129         synchronized (inMap) {
130             Iterator inMapKeysIt = inMap.keySet().iterator();
131 
132             while (inMapKeysIt.hasNext()) {
133                 Object key = inMapKeysIt.next();
134                 Object value = inMap.get(key);
135 
136                 if (value instanceof ExpiringObject) {
137                     delegate.put(key, value);
138                 }
139             }
140         }
141     }
142 
143     public Collection values() {
144         return delegate.values();
145     }
146 
147     public Set entrySet() {
148         return delegate.entrySet();
149     }
150 
151     public void addExpirationListener(ExpirationListener listener) {
152         expirationListeners.add(listener);
153     }
154 
155     public void removeExpirationListener(ExpirationListener listener) {
156         expirationListeners.remove(listener);
157     }
158 
159     public Expirer getExpirer() {
160         return expirer;
161     }
162 
163     public int getExpirationInterval() {
164         return expirer.getExpirationInterval();
165     }
166 
167     public int getTimeToLive() {
168         return expirer.getTimeToLive();
169     }
170 
171     public void setExpirationInterval(int expirationInterval) {
172         expirer.setExpirationInterval(expirationInterval);
173     }
174 
175     public void setTimeToLive(int timeToLive) {
176         expirer.setTimeToLive(timeToLive);
177     }
178 
179     private class ExpiringObject {
180         private Object key;
181 
182         private Object value;
183 
184         private long lastAccessTime;
185 
186         private ReadWriteLock lastAccessTimeLock = new ReentrantReadWriteLock();
187 
188         public ExpiringObject(Object key, Object value, long lastAccessTime) {
189             if (value == null) {
190                 throw new IllegalArgumentException(
191                         "An expiring object cannot be null.");
192             }
193 
194             this.key = key;
195             this.value = value;
196             this.lastAccessTime = lastAccessTime;
197         }
198 
199         public long getLastAccessTime() {
200             lastAccessTimeLock.readLock().lock();
201 
202             try {
203                 return lastAccessTime;
204             } finally {
205                 lastAccessTimeLock.readLock().unlock();
206             }
207         }
208 
209         public void setLastAccessTime(long lastAccessTime) {
210             lastAccessTimeLock.writeLock().lock();
211 
212             try {
213                 this.lastAccessTime = lastAccessTime;
214             } finally {
215                 lastAccessTimeLock.writeLock().unlock();
216             }
217         }
218 
219         public Object getKey() {
220             return key;
221         }
222 
223         public Object getValue() {
224             return value;
225         }
226 
227         public boolean equals(Object obj) {
228             return value.equals(obj);
229         }
230 
231         public int hashCode() {
232             return value.hashCode();
233         }
234     }
235 
236     public class Expirer implements Runnable {
237         private ReadWriteLock stateLock = new ReentrantReadWriteLock();
238 
239         private long timeToLiveMillis;
240 
241         private long expirationIntervalMillis;
242 
243         private boolean running = false;
244 
245         private final Thread expirerThread;
246 
247         public Expirer() {
248             expirerThread = new Thread(this, "ExpiringMapExpirer-"
249                     + (expirerCount++));
250             expirerThread.setDaemon(true);
251         }
252 
253         public void run() {
254             while (running) {
255                 processExpires();
256 
257                 try {
258                     Thread.sleep(expirationIntervalMillis);
259                 } catch (InterruptedException e) {
260                 }
261             }
262         }
263 
264         private void processExpires() {
265             long timeNow = System.currentTimeMillis();
266 
267             Iterator expiringObjectsIterator = delegate.values().iterator();
268 
269             while (expiringObjectsIterator.hasNext()) {
270                 ExpiringObject expObject = (ExpiringObject) expiringObjectsIterator
271                         .next();
272 
273                 if (timeToLiveMillis <= 0)
274                     continue;
275 
276                 long timeIdle = timeNow - expObject.getLastAccessTime();
277 
278                 if (timeIdle >= timeToLiveMillis) {
279                     delegate.remove(expObject.getKey());
280 
281                     Iterator listenerIterator = expirationListeners.iterator();
282 
283                     while (listenerIterator.hasNext()) {
284                         ExpirationListener listener = (ExpirationListener) listenerIterator
285                                 .next();
286 
287                         listener.expired(expObject.getValue());
288                     }
289                 }
290             }
291         }
292 
293         public void startExpiring() {
294             stateLock.writeLock().lock();
295 
296             try {
297                 if (!running) {
298                     running = true;
299                     expirerThread.start();
300                 }
301             } finally {
302                 stateLock.writeLock().unlock();
303             }
304         }
305 
306         public void startExpiringIfNotStarted() {
307             stateLock.readLock().lock();
308             try {
309                 if (running) {
310                     return;
311                 }
312             } finally {
313                 stateLock.readLock().unlock();
314             }
315 
316             stateLock.writeLock().lock();
317             try {
318                 if (!running) {
319                     running = true;
320                     expirerThread.start();
321                 }
322             } finally {
323                 stateLock.writeLock().unlock();
324             }
325         }
326 
327         public void stopExpiring() {
328             stateLock.writeLock().lock();
329 
330             try {
331                 if (running) {
332                     running = false;
333                     expirerThread.interrupt();
334                 }
335             } finally {
336                 stateLock.writeLock().unlock();
337             }
338         }
339 
340         public boolean isRunning() {
341             stateLock.readLock().lock();
342 
343             try {
344                 return running;
345             } finally {
346                 stateLock.readLock().unlock();
347             }
348         }
349 
350         public int getTimeToLive() {
351             stateLock.readLock().lock();
352 
353             try {
354                 return (int) timeToLiveMillis / 1000;
355             } finally {
356                 stateLock.readLock().unlock();
357             }
358         }
359 
360         public void setTimeToLive(long timeToLive) {
361             stateLock.writeLock().lock();
362 
363             try {
364                 this.timeToLiveMillis = timeToLive * 1000;
365             } finally {
366                 stateLock.writeLock().unlock();
367             }
368         }
369 
370         public int getExpirationInterval() {
371             stateLock.readLock().lock();
372 
373             try {
374                 return (int) expirationIntervalMillis / 1000;
375             } finally {
376                 stateLock.readLock().unlock();
377             }
378         }
379 
380         public void setExpirationInterval(long expirationInterval) {
381             stateLock.writeLock().lock();
382 
383             try {
384                 this.expirationIntervalMillis = expirationInterval * 1000;
385             } finally {
386                 stateLock.writeLock().unlock();
387             }
388         }
389     }
390 }