001/* 002 * Copyright 2002-2020 the original author or authors. 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 * https://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 */ 016 017package org.springframework.cache.caffeine; 018 019import java.util.concurrent.Callable; 020import java.util.function.Function; 021 022import com.github.benmanes.caffeine.cache.LoadingCache; 023 024import org.springframework.cache.support.AbstractValueAdaptingCache; 025import org.springframework.lang.Nullable; 026import org.springframework.util.Assert; 027 028/** 029 * Spring {@link org.springframework.cache.Cache} adapter implementation 030 * on top of a Caffeine {@link com.github.benmanes.caffeine.cache.Cache} instance. 031 * 032 * <p>Requires Caffeine 2.1 or higher. 033 * 034 * @author Ben Manes 035 * @author Juergen Hoeller 036 * @author Stephane Nicoll 037 * @since 4.3 038 * @see CaffeineCacheManager 039 */ 040public class CaffeineCache extends AbstractValueAdaptingCache { 041 042 private final String name; 043 044 private final com.github.benmanes.caffeine.cache.Cache<Object, Object> cache; 045 046 047 /** 048 * Create a {@link CaffeineCache} instance with the specified name and the 049 * given internal {@link com.github.benmanes.caffeine.cache.Cache} to use. 050 * @param name the name of the cache 051 * @param cache the backing Caffeine Cache instance 052 */ 053 public CaffeineCache(String name, com.github.benmanes.caffeine.cache.Cache<Object, Object> cache) { 054 this(name, cache, true); 055 } 056 057 /** 058 * Create a {@link CaffeineCache} instance with the specified name and the 059 * given internal {@link com.github.benmanes.caffeine.cache.Cache} to use. 060 * @param name the name of the cache 061 * @param cache the backing Caffeine Cache instance 062 * @param allowNullValues whether to accept and convert {@code null} 063 * values for this cache 064 */ 065 public CaffeineCache(String name, com.github.benmanes.caffeine.cache.Cache<Object, Object> cache, 066 boolean allowNullValues) { 067 068 super(allowNullValues); 069 Assert.notNull(name, "Name must not be null"); 070 Assert.notNull(cache, "Cache must not be null"); 071 this.name = name; 072 this.cache = cache; 073 } 074 075 076 @Override 077 public final String getName() { 078 return this.name; 079 } 080 081 @Override 082 public final com.github.benmanes.caffeine.cache.Cache<Object, Object> getNativeCache() { 083 return this.cache; 084 } 085 086 @Override 087 @Nullable 088 public ValueWrapper get(Object key) { 089 if (this.cache instanceof LoadingCache) { 090 Object value = ((LoadingCache<Object, Object>) this.cache).get(key); 091 return toValueWrapper(value); 092 } 093 return super.get(key); 094 } 095 096 @SuppressWarnings("unchecked") 097 @Override 098 @Nullable 099 public <T> T get(Object key, final Callable<T> valueLoader) { 100 return (T) fromStoreValue(this.cache.get(key, new LoadFunction(valueLoader))); 101 } 102 103 @Override 104 @Nullable 105 protected Object lookup(Object key) { 106 return this.cache.getIfPresent(key); 107 } 108 109 @Override 110 public void put(Object key, @Nullable Object value) { 111 this.cache.put(key, toStoreValue(value)); 112 } 113 114 @Override 115 @Nullable 116 public ValueWrapper putIfAbsent(Object key, @Nullable final Object value) { 117 PutIfAbsentFunction callable = new PutIfAbsentFunction(value); 118 Object result = this.cache.get(key, callable); 119 return (callable.called ? null : toValueWrapper(result)); 120 } 121 122 @Override 123 public void evict(Object key) { 124 this.cache.invalidate(key); 125 } 126 127 @Override 128 public boolean evictIfPresent(Object key) { 129 return (this.cache.asMap().remove(key) != null); 130 } 131 132 @Override 133 public void clear() { 134 this.cache.invalidateAll(); 135 } 136 137 @Override 138 public boolean invalidate() { 139 boolean notEmpty = !this.cache.asMap().isEmpty(); 140 this.cache.invalidateAll(); 141 return notEmpty; 142 } 143 144 145 private class PutIfAbsentFunction implements Function<Object, Object> { 146 147 @Nullable 148 private final Object value; 149 150 private boolean called; 151 152 public PutIfAbsentFunction(@Nullable Object value) { 153 this.value = value; 154 } 155 156 @Override 157 public Object apply(Object key) { 158 this.called = true; 159 return toStoreValue(this.value); 160 } 161 } 162 163 164 private class LoadFunction implements Function<Object, Object> { 165 166 private final Callable<?> valueLoader; 167 168 public LoadFunction(Callable<?> valueLoader) { 169 this.valueLoader = valueLoader; 170 } 171 172 @Override 173 public Object apply(Object o) { 174 try { 175 return toStoreValue(this.valueLoader.call()); 176 } 177 catch (Exception ex) { 178 throw new ValueRetrievalException(o, this.valueLoader, ex); 179 } 180 } 181 } 182 183}