001/* 002 * Copyright 2020 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.core.util; 017 018import java.time.Duration; 019import java.util.concurrent.ExecutionException; 020import java.util.function.Supplier; 021 022import com.google.common.base.Ticker; 023import com.google.common.cache.CacheBuilder; 024import com.google.common.cache.CacheLoader; 025import com.google.common.cache.LoadingCache; 026 027/** 028 * A cached value. It can be constructed with {@link #withInitial} with a {@link Supplier} to pass in order to compute the value to cache on the first call of {@link #get()}. 029 * <br>It can also be constructed with {@link #withExpiryDuration} with the {@link Supplier} and an expiry {@link Duration} to automatically force the reload of the value. 030 * <br>The supplier is ensured to be called once (until {@link #uncache} is called, or after the expiry duration has elapsed since the last computing), even with multiple callers at the same time. 031 * <br>The returned value by the supplier <b>must not be null</b> 032 * @param <T> The type of the cached value 033 */ 034public class CachedValue<T> 035{ 036 private static final Object __ALL_CACHES_ONLY_KEY = new Object(); 037 038 private LoadingCache<Object, T> _loadingCache; 039 040 CachedValue(Supplier<T> supplier, Duration expiryDuration, Ticker ticker) 041 { 042 LoadingCache<Object, T> loadingCache = _cacheBuilder(expiryDuration, ticker) 043 .build(_cacheLoader(supplier)); 044 _loadingCache = loadingCache; 045 } 046 047 /** 048 * Creates a cached value. The value will be computed by invoking 049 * the given supplier when the {@link #get()} method will be 050 * called the first time. 051 * @param <T> The type of the cached value 052 * @param supplier The supplier which will be invoked only once in order to compute the value to cache. The value <b>must not be null</b> 053 * @return The {@link CachedValue} 054 */ 055 public static <T> CachedValue<T> withInitial(Supplier<T> supplier) 056 { 057 return new CachedValue<>(supplier, null, null); 058 } 059 060 /** 061 * Creates a cached value. The value will be computed by invoking 062 * the given supplier when the {@link #get()} method will be 063 * called the very first time, and every first time after the given {@link Duration} is expired. 064 * @param <T> The type of the cached value 065 * @param supplier The supplier which will be invoked only once in order to compute the value to cache. The value <b>must not be null</b> 066 * @param expiryDuration The expiry duration 067 * @return The {@link CachedValue} 068 */ 069 public static <T> CachedValue<T> withExpiryDuration(Supplier<T> supplier, Duration expiryDuration) 070 { 071 return new CachedValue<>(supplier, expiryDuration, null); 072 } 073 074 CacheBuilder<Object, Object> _cacheBuilder(Duration expiryDuration, Ticker ticker) 075 { 076 CacheBuilder<Object, Object> cacheBuilder = CacheBuilder.newBuilder() 077 .initialCapacity(1) 078 // key will always be __ALL_CACHES_ONLY_KEY 079 .weakKeys(); 080 081 if (expiryDuration != null) 082 { 083 cacheBuilder.expireAfterWrite(expiryDuration); 084 } 085 if (ticker != null) 086 { 087 cacheBuilder.ticker(ticker); 088 } 089 090 return cacheBuilder; 091 } 092 093 private static <T> CacheLoader<Object, T> _cacheLoader(Supplier<T> supplier) 094 { 095 return new CacheLoader<>() 096 { 097 @Override 098 public T load(Object key) throws Exception 099 { 100 // key should always be __ALL_CACHES_ONLY_KEY, and is useless 101 T value = supplier.get(); 102 if (value == null) 103 { 104 throw new IllegalArgumentException("The given supplier cannot return a null value."); 105 } 106 return value; 107 } 108 }; 109 } 110 111 /** 112 * Returns the value. If it is the first time it is called, then it is computed 113 * by invoking the supplier function. Otherwise, the cached value is returned. 114 * @return the value 115 */ 116 public T get() 117 { 118 try 119 { 120 return _loadingCache.get(__ALL_CACHES_ONLY_KEY); 121 } 122 catch (ExecutionException e) 123 { 124 throw new IllegalStateException("An unexpected error occured when loading the value from the supplier function.", e); 125 } 126 } 127 128 /** 129 * Uncache the value. Mostly to force to re-compute the value. 130 */ 131 public void uncache() 132 { 133 _loadingCache.invalidate(__ALL_CACHES_ONLY_KEY); 134 } 135}