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