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}