001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2014 ForgeRock AS.
015 */
016
017package org.forgerock.openig.filter.oauth2.cache;
018
019import java.util.concurrent.Callable;
020import java.util.concurrent.ConcurrentHashMap;
021import java.util.concurrent.ConcurrentMap;
022import java.util.concurrent.ExecutionException;
023import java.util.concurrent.Future;
024import java.util.concurrent.FutureTask;
025import java.util.concurrent.ScheduledExecutorService;
026import java.util.concurrent.TimeUnit;
027
028import org.forgerock.openig.util.Duration;
029
030/**
031 * ThreadSafeCache is a thread-safe write-through cache.
032 * <p>
033 * Instead of storing directly the value in the backing Map, it requires the consumer to provide a value factory (a
034 * Callable). A new FutureTask encapsulate the callable, is executed and is placed inside a ConcurrentHashMap if absent.
035 * <p>
036 * The final behavior is that, even if two concurrent Threads are borrowing an object from the cache,
037 * given that they provide an equivalent value factory, the first one will compute the value while the other will get
038 * the result from the Future (and will wait until the result is computed or a timeout occurs).
039 *
040 * @param <K> Type of the key
041 * @param <V> Type of the value
042 */
043public class ThreadSafeCache<K, V> {
044
045    private final ScheduledExecutorService executorService;
046    private final ConcurrentMap<K, Future<V>> cache = new ConcurrentHashMap<K, Future<V>>();
047    private volatile Duration timeout = new Duration(1L, TimeUnit.MINUTES);
048
049    /**
050     * Build a new {@link ThreadSafeCache} using the given scheduled executor.
051     *
052     * @param executorService
053     *         scheduled executor for registering expiration callbacks.
054     */
055    public ThreadSafeCache(final ScheduledExecutorService executorService) {
056        this.executorService = executorService;
057    }
058
059    /**
060     * Sets the cache entry expiration delay. Notice that this will impact only new cache entries.
061     *
062     * @param timeout new cache entry timeout
063     */
064    public void setTimeout(Duration timeout) {
065        this.timeout = timeout;
066    }
067
068    private Future<V> createIfAbsent(final K key, final Callable<V> callable) {
069        Future<V> future = cache.get(key);
070        if (future == null) {
071            final FutureTask<V> futureTask = new FutureTask<V>(callable);
072            future = cache.putIfAbsent(key, futureTask);
073            if (future == null) {
074                future = futureTask;
075
076                // Compute the value
077                futureTask.run();
078
079                // Register cache entry expiration time
080                executorService.schedule(new Expiration(key),
081                                         timeout.getValue(),
082                                         timeout.getUnit());
083            }
084        }
085        return future;
086    }
087
088    /**
089     * Borrow (and create before hand if absent) a cache entry. If another Thread has created (or the creation is
090     * undergoing) the value, this methods waits indefinitely for the value to be available.
091     *
092     * @param key
093     *         entry key
094     * @param callable
095     *         cached value factory
096     * @return the cached value
097     * @throws InterruptedException
098     *         if the current thread was interrupted while waiting
099     * @throws ExecutionException
100     *         if the cached value computation threw an exception
101     */
102    public V getValue(final K key, final Callable<V> callable) throws InterruptedException, ExecutionException {
103        try {
104            return createIfAbsent(key, callable).get();
105        } catch (InterruptedException e) {
106            cache.remove(key);
107            throw e;
108        } catch (ExecutionException e) {
109            cache.remove(key);
110            throw e;
111        } catch (RuntimeException e) {
112            cache.remove(key);
113            throw e;
114        }
115    }
116
117    /**
118     * Clean-up the cache entries.
119     */
120    public void clear() {
121        // Clear the cache
122        cache.clear();
123    }
124
125    /**
126     * Registered in the executor, this callable simply removes the cache entry after a specified amount of time.
127     */
128    private class Expiration implements Callable<Object> {
129        private final K key;
130
131        public Expiration(final K key) {
132            this.key = key;
133        }
134
135        @Override
136        public Object call() throws Exception {
137            return cache.remove(key);
138        }
139    }
140}