blob: 08f7c88232499d5fd1d28fc566487569e68fb0ce [file] [log] [blame]
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.lisp.ctl.impl.map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Default implementation of ExpireMap.
*/
public class ExpireHashMap<K, V> implements ExpireMap<K, V> {
private final Logger log = LoggerFactory.getLogger(getClass());
private static final long DEFAULT_TTL = 60000L;
private final ConcurrentMap<K, ExpiredObject<K, V>> map = new ConcurrentHashMap<>();
private final Lock writeLock = new ReentrantLock();
private final Timer timer = new Timer("ExpireMapTimer", true);
/**
* An expired object that associates with a TimerTask instance.
*
* @param <K1> key type K1
* @param <V1> value type V1
*/
class ExpiredObject<K1, V1> {
private final V1 value;
private final ExpiryTask<K1> task;
private final long ttl;
public ExpiredObject(K1 key, V1 value) {
this(key, value, DEFAULT_TTL);
}
ExpiredObject(K1 key, V1 value, long ttl) {
this.value = value;
this.task = new ExpiryTask<>(key);
this.ttl = ttl;
timer.schedule(this.task, ttl);
}
ExpiryTask<K1> getTask() {
return task;
}
V1 getValue() {
return value;
}
long getTtl() {
return ttl;
}
}
/**
* A TimerTask that removes its associated map entry from the internal map.
*
* @param <K2> object key
*/
class ExpiryTask<K2> extends TimerTask {
private final K2 key;
ExpiryTask(K2 key) {
this.key = key;
}
K2 getKey() {
return key;
}
@Override
public void run() {
log.info("Removing element with key [{}]", key);
try {
writeLock.lock();
if (map.containsKey(key)) {
map.remove(getKey());
}
} finally {
writeLock.unlock();
}
}
}
@Override
public void put(K key, V value, long expireMs) {
try {
writeLock.lock();
// if we have a value which is previously associated with the given
// key, we simply replace it with new value, and invalidate the
// previously associated value
final ExpiredObject<K, V> object =
map.putIfAbsent(key, new ExpiredObject<>(key, value, expireMs));
if (object != null) {
object.getTask().cancel();
}
} finally {
writeLock.unlock();
}
}
@Override
public void put(K key, V value) {
put(key, value, DEFAULT_TTL);
}
@Override
public V get(K key) {
return map.containsKey(key) ? map.get(key).getValue() : null;
}
@Override
public void clear() {
try {
writeLock.lock();
for (ExpiredObject<K, V> object : map.values()) {
object.getTask().cancel();
}
map.clear();
timer.purge();
} finally {
writeLock.unlock();
}
}
@Override
public boolean containsKey(K key) {
return map.containsKey(key);
}
@Override
public Set<K> keySet() {
return map.keySet();
}
@Override
public boolean isEmpty() {
return map.isEmpty();
}
@Override
public V remove(K key) {
final ExpiredObject<K, V> object;
try {
writeLock.lock();
object = map.remove(key);
if (object != null) {
object.getTask().cancel();
}
} finally {
writeLock.unlock();
}
return (object == null ? null : object.getValue());
}
@Override
public int size() {
return map.size();
}
}