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