Skip to content

Package: LruCacheManager

LruCacheManager

nameinstructionbranchcomplexitylinemethod
LruCacheManager()
M: 0 C: 4
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 2
100%
M: 0 C: 1
100%
LruCacheManager(Map)
M: 0 C: 35
100%
M: 0 C: 2
100%
M: 0 C: 2
100%
M: 0 C: 9
100%
M: 0 C: 1
100%
add(Object, Object, Descriptor)
M: 0 C: 28
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 7
100%
M: 0 C: 1
100%
close()
M: 0 C: 3
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 2
100%
M: 0 C: 1
100%
contains(Class, Object, Descriptor)
M: 0 C: 23
100%
M: 0 C: 6
100%
M: 0 C: 4
100%
M: 0 C: 5
100%
M: 0 C: 1
100%
evict(Class)
M: 0 C: 14
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 5
100%
M: 0 C: 1
100%
evict(Class, Object, URI)
M: 0 C: 23
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 6
100%
M: 0 C: 1
100%
evict(URI)
M: 0 C: 11
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 4
100%
M: 0 C: 1
100%
evictAll()
M: 0 C: 14
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 4
100%
M: 0 C: 1
100%
evictInferredObjects()
M: 0 C: 12
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 4
100%
M: 0 C: 1
100%
get(Class, Object, Descriptor)
M: 0 C: 23
100%
M: 2 C: 4
67%
M: 2 C: 2
50%
M: 0 C: 5
100%
M: 0 C: 1
100%
getCapacity()
M: 0 C: 3
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
getInferredClasses()
M: 2 C: 6
75%
M: 1 C: 1
50%
M: 1 C: 1
50%
M: 1 C: 2
67%
M: 0 C: 1
100%
resolveCapacitySetting(Map)
M: 6 C: 20
77%
M: 0 C: 2
100%
M: 0 C: 2
100%
M: 2 C: 7
78%
M: 0 C: 1
100%
setInferredClasses(Set)
M: 0 C: 4
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 2
100%
M: 0 C: 1
100%
static {...}
M: 0 C: 4
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%

Coverage

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