Skip to content

Method: LruCacheManager.LruEntityCache(int)

1: /**
2: * Copyright (C) 2016 Czech Technical University in Prague
3: *
4: * This program is free software: you can redistribute it and/or modify it under
5: * the terms of the GNU General Public License as published by the Free Software
6: * Foundation, either version 3 of the License, or (at your option) any
7: * later version.
8: *
9: * This program is distributed in the hope that it will be useful, but WITHOUT
10: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11: * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12: * details. You should have received a copy of the GNU General Public License
13: * along with this program. If not, see <http://www.gnu.org/licenses/>.
14: */
15: package cz.cvut.kbss.jopa.sessions.cache;
16:
17: import cz.cvut.kbss.jopa.model.JOPAPersistenceProperties;
18: import cz.cvut.kbss.jopa.sessions.CacheManager;
19: import cz.cvut.kbss.jopa.utils.ErrorUtils;
20: import org.slf4j.Logger;
21: import org.slf4j.LoggerFactory;
22:
23: import java.net.URI;
24: import java.util.Collections;
25: import java.util.Map;
26: import java.util.Objects;
27: import java.util.Set;
28: import java.util.concurrent.locks.Lock;
29: import java.util.concurrent.locks.ReadWriteLock;
30: import java.util.concurrent.locks.ReentrantReadWriteLock;
31: import java.util.function.Consumer;
32:
33: /**
34: * This is a fixed-size second level cache implementation with LRU eviction policy.
35: * <p>
36: * When the capacity is reached, the least recently used entry is removed from the cache.
37: *
38: * @author kidney
39: */
40: public class LruCacheManager implements CacheManager {
41:
42: private static final Logger LOG = LoggerFactory.getLogger(LruCacheManager.class);
43:
44: /**
45: * Default cache size limit in number of entries.
46: */
47: public static final int DEFAULT_CAPACITY = 512;
48:
49: private final int capacity;
50:
51: private final Lock readLock;
52: private final Lock writeLock;
53:
54: private LruEntityCache entityCache;
55:
56: private Set<Class<?>> inferredClasses;
57:
58: public LruCacheManager() {
59: this(Collections.emptyMap());
60: }
61:
62: public LruCacheManager(Map<String, String> properties) {
63: Objects.requireNonNull(properties);
64: this.capacity = properties.containsKey(JOPAPersistenceProperties.LRU_CACHE_CAPACITY) ?
65: resolveCapacitySetting(properties) : DEFAULT_CAPACITY;
66: final ReadWriteLock rwLock = new ReentrantReadWriteLock();
67: this.readLock = rwLock.readLock();
68: this.writeLock = rwLock.writeLock();
69: this.entityCache = new LruEntityCache(capacity);
70: }
71:
72: private int resolveCapacitySetting(Map<String, String> properties) {
73: int capacitySetting = DEFAULT_CAPACITY;
74: try {
75: capacitySetting =
76: Integer.parseInt(properties.get(JOPAPersistenceProperties.LRU_CACHE_CAPACITY));
77: if (capacitySetting <= 0) {
78: LOG.warn("Invalid LRU cache capacity value {}. Using default value.", capacitySetting);
79: capacitySetting = DEFAULT_CAPACITY;
80: }
81: } catch (NumberFormatException e) {
82: LOG.error("Unable to parse LRU cache capacity setting. Using default capacity {}.", DEFAULT_CAPACITY);
83: }
84: return capacitySetting;
85: }
86:
87: int getCapacity() {
88: return capacity;
89: }
90:
91: @Override
92: public void add(Object primaryKey, Object entity, URI context) {
93: Objects.requireNonNull(primaryKey, ErrorUtils.constructNPXMessage("primaryKey"));
94: Objects.requireNonNull(entity, ErrorUtils.constructNPXMessage("entity"));
95:
96: writeLock.lock();
97: try {
98: entityCache.put(primaryKey, entity, context);
99: } finally {
100: writeLock.unlock();
101: }
102: }
103:
104: @Override
105: public <T> T get(Class<T> cls, Object primaryKey, URI context) {
106: if (cls == null || primaryKey == null) {
107: return null;
108: }
109: readLock.lock();
110: try {
111: return entityCache.get(cls, primaryKey, context);
112: } finally {
113: readLock.unlock();
114: }
115: }
116:
117: @Override
118: public void clearInferredObjects() {
119: writeLock.lock();
120: try {
121: getInferredClasses().forEach(this::evict);
122: } finally {
123: writeLock.unlock();
124: }
125: }
126:
127: private Set<Class<?>> getInferredClasses() {
128: if (inferredClasses == null) {
129: return Collections.emptySet();
130: }
131: return inferredClasses;
132: }
133:
134: @Override
135: public void setInferredClasses(Set<Class<?>> inferredClasses) {
136: this.inferredClasses = inferredClasses;
137: }
138:
139: @Override
140: public void close() {
141: // No-op
142: }
143:
144: @Override
145: public boolean contains(Class<?> cls, Object primaryKey) {
146: if (cls == null || primaryKey == null) {
147: return false;
148: }
149: readLock.lock();
150: try {
151: return entityCache.contains(cls, primaryKey);
152: } finally {
153: readLock.unlock();
154: }
155: }
156:
157: @Override
158: public boolean contains(Class<?> cls, Object primaryKey, URI context) {
159: if (cls == null || primaryKey == null) {
160: return false;
161: }
162: readLock.lock();
163: try {
164: return entityCache.contains(cls, primaryKey, context);
165: } finally {
166: readLock.unlock();
167: }
168: }
169:
170: @Override
171: public void evict(Class<?> cls, Object primaryKey, URI context) {
172: Objects.requireNonNull(cls, ErrorUtils.constructNPXMessage("cls"));
173: Objects.requireNonNull(primaryKey, ErrorUtils.constructNPXMessage("primaryKey"));
174:
175: writeLock.lock();
176: try {
177: entityCache.evict(cls, primaryKey, context);
178: } finally {
179: writeLock.unlock();
180: }
181: }
182:
183: @Override
184: public void evict(Class<?> cls) {
185: Objects.requireNonNull(cls);
186:
187: writeLock.lock();
188: try {
189: entityCache.evict(cls);
190: } finally {
191: writeLock.unlock();
192: }
193: }
194:
195: @Override
196: public void evict(URI context) {
197: writeLock.lock();
198: try {
199: entityCache.evict(context);
200: } finally {
201: writeLock.unlock();
202: }
203: }
204:
205: @Override
206: public void evictAll() {
207: writeLock.lock();
208: try {
209: this.entityCache = new LruEntityCache(capacity);
210: } finally {
211: writeLock.unlock();
212: }
213: }
214:
215: static final class LruEntityCache extends EntityCache implements Consumer<LruCache.CacheNode> {
216:
217: private static final Object NULL_VALUE = null;
218:
219: private final LruCache cache;
220:
221: LruEntityCache(int capacity) {
222: this.cache = new LruCache(capacity, this);
223: }
224:
225: @Override
226: public void accept(LruCache.CacheNode cacheNode) {
227: super.evict(cacheNode.getCls(), cacheNode.getIdentifier(), cacheNode.getContext());
228: }
229:
230: @Override
231: void put(Object primaryKey, Object entity, URI context) {
232: final URI ctx = context != null ? context : defaultContext;
233: super.put(primaryKey, entity, ctx);
234: cache.put(new LruCache.CacheNode(ctx, entity.getClass(), primaryKey), NULL_VALUE);
235: }
236:
237: @Override
238: <T> T get(Class<T> cls, Object primaryKey, URI context) {
239: final URI ctx = context != null ? context : defaultContext;
240: T result = super.get(cls, primaryKey, ctx);
241: if (result != null) {
242: cache.get(new LruCache.CacheNode(ctx, cls, primaryKey));
243: }
244: return result;
245: }
246:
247: @Override
248: void evict(Class<?> cls, Object primaryKey, URI context) {
249: final URI ctx = context != null ? context : defaultContext;
250: super.evict(cls, primaryKey, ctx);
251: cache.remove(new LruCache.CacheNode(ctx, cls, primaryKey));
252: }
253:
254: @Override
255: void evict(URI context) {
256: if (context == null) {
257: context = defaultContext;
258: }
259: if (!repoCache.containsKey(context)) {
260: return;
261: }
262: final Map<Class<?>, Map<Object, Object>> ctxContent = repoCache.get(context);
263: for (Map.Entry<Class<?>, Map<Object, Object>> e : ctxContent.entrySet()) {
264: for (Object id : e.getValue().keySet()) {
265: cache.remove(new LruCache.CacheNode(context, e.getKey(), id));
266: }
267: }
268: ctxContent.clear();
269: }
270:
271: @Override
272: void evict(Class<?> cls) {
273: for (Map.Entry<URI, Map<Class<?>, Map<Object, Object>>> e : repoCache.entrySet()) {
274: final Map<Class<?>, Map<Object, Object>> m = e.getValue();
275: final Map<Object, Object> cached = m.remove(cls);
276: if (cached != null) {
277: for (Object id : cached.keySet()) {
278: cache.remove(new LruCache.CacheNode(e.getKey(), cls, id));
279: }
280: }
281: }
282: }
283: }
284: }