/** * Copyright (C) 2008 Progress Software, Inc. All rights reserved. * http://fusesource.com * * The software in this package is published under the terms of the AGPL license * a copy of which has been included with this distribution in the license.txt file. */ package org.fusesource.cloudmix.controller.util; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * @version $Revision$ */ public class DefaultTimeoutMap<K, V> implements TimeoutMap<K, V>, Runnable { private static final Log LOG = LogFactory.getLog(DefaultTimeoutMap.class); private Map<K, TimeoutMapEntry<K, V>> map = new HashMap<K, TimeoutMapEntry<K, V>>(); private SortedSet<TimeoutMapEntry<K, V>> index = new TreeSet<TimeoutMapEntry<K, V>>(); private ScheduledExecutorService executor; private long purgePollTime; private TimeoutListener<K, V> timeoutListener; public DefaultTimeoutMap() { this(null, 1000L); } public DefaultTimeoutMap(ScheduledExecutorService executor, long requestMapPollTimeMillis) { this.executor = executor; this.purgePollTime = requestMapPollTimeMillis; schedulePoll(); } public V get(K key) { TimeoutMapEntry<K, V> entry = null; synchronized (map) { entry = map.get(key); if (entry == null) { return null; } index.remove(entry); updateExpireTime(entry); index.add(entry); } return entry.getValue(); } public void put(K key, V value, long timeoutMillis) { TimeoutMapEntry<K, V> entry = new TimeoutMapEntry<K, V>(key, value, timeoutMillis); synchronized (map) { Object oldValue = map.put(key, entry); if (oldValue != null) { index.remove(oldValue); } updateExpireTime(entry); index.add(entry); } } public void remove(K id) { synchronized (map) { TimeoutMapEntry<K, V> entry = map.remove(id); if (entry != null) { index.remove(entry); } } } public Collection<V> values() { List<V> answer = new ArrayList<V>(map.size()); synchronized (map) { Collection<TimeoutMapEntry<K, V>> values = map.values(); for (TimeoutMapEntry<K, V> value : values) { answer.add(value.getValue()); } } return answer; } /** * Returns a copy of the keys in the map */ public Object[] getKeys() { Object[] keys = null; synchronized (map) { Set<K> keySet = map.keySet(); keys = keySet.toArray(); } return keys; } /** * The timer task which purges old requests and schedules another poll */ public void run() { purge(); schedulePoll(); } /** * Purges any old entries from the map */ public void purge() { long now = currentTime(); synchronized (map) { for (Iterator<TimeoutMapEntry<K, V>> iter = index.iterator(); iter.hasNext();) { TimeoutMapEntry<K, V> entry = iter.next(); if (entry == null) { break; } if (entry.getExpireTime() < now) { if (isValidForEviction(entry)) { if (LOG.isDebugEnabled()) { LOG.debug("Evicting inactive TimeoutMap entry for key: " + entry.getKey()); } map.remove(entry.getKey()); onTimeout(entry.getKey(), entry.getValue()); iter.remove(); } } else { break; } } } } // Properties // ------------------------------------------------------------------------- public long getPurgePollTime() { return purgePollTime; } /** * Sets the next purge poll time in milliseconds */ public void setPurgePollTime(long purgePollTime) { this.purgePollTime = purgePollTime; } public ScheduledExecutorService getExecutor() { return executor; } /** * Sets the executor used to schedule purge events of inactive requests */ public void setExecutor(ScheduledExecutorService executor) { this.executor = executor; } public TimeoutListener<K, V> getTimeoutListener() { return timeoutListener; } public void setTimeoutListener(TimeoutListener<K, V> timeoutListener) { this.timeoutListener = timeoutListener; } // Implementation methods // ------------------------------------------------------------------------- /** * lets schedule each time to allow folks to change the time at runtime */ protected void schedulePoll() { if (executor != null) { executor.schedule(this, purgePollTime, TimeUnit.MILLISECONDS); } } /** * A hook to allow derivations to avoid evicting the current entry * * @param entry * @return */ protected boolean isValidForEviction(TimeoutMapEntry<K, V> entry) { return true; } protected void updateExpireTime(TimeoutMapEntry<K, V> entry) { long now = currentTime(); entry.setExpireTime(entry.getTimeout() + now); } protected long currentTime() { return System.currentTimeMillis(); } protected void onTimeout(K key, V value) { if (timeoutListener != null) { timeoutListener.onTimeout(key, value); } } }