Skip to contentMethod: evict(Class)
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.*;
25: import java.util.Map.Entry;
26: import java.util.concurrent.Executors;
27: import java.util.concurrent.Future;
28: import java.util.concurrent.ScheduledExecutorService;
29: import java.util.concurrent.TimeUnit;
30: import java.util.concurrent.locks.Lock;
31: import java.util.concurrent.locks.ReentrantReadWriteLock;
32:
33: /**
34: * Manages the second level cache shared by all persistence contexts. </p>
35: * <p>
36: * This implementation of CacheManager uses cache-wide locking, i. e. the whole cache is locked when an entity is being
37: * put in it, no matter that only one context is affected by the change.
38: * <p>
39: * This cache is sweeped regularly by a dedicated thread, which removes all entries whose time-to-live (TTL) has
40: * expired.
41: *
42: * @author kidney
43: */
44: public class TtlCacheManager implements CacheManager {
45:
46: private static final Logger LOG = LoggerFactory.getLogger(TtlCacheManager.class);
47:
48: /**
49: * Default time to live in millis
50: */
51: private static final long DEFAULT_TTL = 60000L;
52: // Initial delay is the sweep rate multiplied by this multiplier
53: private static final int DELAY_MULTIPLIER = 2;
54: /**
55: * Default sweep rate in millis
56: */
57: private static final long DEFAULT_SWEEP_RATE = 30000L;
58:
59: private Set<Class<?>> inferredClasses;
60:
61: private TtlCache cache;
62:
63: // Each repository can have its own lock and they could be acquired by this
64: // instance itself, no need to pass this burden to callers
65: private final Lock readLock;
66: private final Lock writeLock;
67:
68: private final ScheduledExecutorService sweeperScheduler;
69: private Future<?> sweeperFuture;
70: private long initDelay;
71: private long sweepRate;
72: private long timeToLive;
73: private volatile boolean sweepRunning;
74:
75: public TtlCacheManager(Map<String, String> properties) {
76: this.cache = new TtlCache();
77: initSettings(properties);
78: final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
79: this.readLock = lock.readLock();
80: this.writeLock = lock.writeLock();
81: this.sweeperScheduler = Executors.newSingleThreadScheduledExecutor();
82: this.sweeperFuture = sweeperScheduler.scheduleAtFixedRate(new CacheSweeper(), initDelay, sweepRate,
83: TimeUnit.MILLISECONDS);
84: }
85:
86: private void initSettings(Map<String, String> properties) {
87: if (!properties.containsKey(JOPAPersistenceProperties.CACHE_TTL)) {
88: this.timeToLive = DEFAULT_TTL;
89: } else {
90: final String strCacheTtl = properties.get(JOPAPersistenceProperties.CACHE_TTL);
91: try {
92: // The property is in seconds, we need milliseconds
93: this.timeToLive = Long.parseLong(strCacheTtl) * 1000;
94: } catch (NumberFormatException e) {
95: LOG.warn("Unable to parse cache time to live setting value {}, using default value.", strCacheTtl);
96: this.timeToLive = DEFAULT_TTL;
97: }
98: }
99: if (!properties.containsKey(JOPAPersistenceProperties.CACHE_SWEEP_RATE)) {
100: this.sweepRate = DEFAULT_SWEEP_RATE;
101: } else {
102: final String strSweepRate = properties
103: .get(JOPAPersistenceProperties.CACHE_SWEEP_RATE);
104: try {
105: // The property is in seconds, we need milliseconds
106: this.sweepRate = Long.parseLong(strSweepRate) * 1000;
107: } catch (NumberFormatException e) {
108: LOG.warn("Unable to parse sweep rate setting value {}, using default value.", strSweepRate);
109: this.sweepRate = DEFAULT_SWEEP_RATE;
110: }
111: }
112: this.initDelay = DELAY_MULTIPLIER * sweepRate;
113: }
114:
115: @Override
116: public void close() {
117: if (sweeperFuture != null) {
118: LOG.debug("Stopping cache sweeper.");
119: sweeperFuture.cancel(true);
120: sweeperScheduler.shutdown();
121: }
122: this.sweeperFuture = null;
123: }
124:
125: @Override
126: public void add(Object primaryKey, Object entity, URI context) {
127: Objects.requireNonNull(primaryKey, ErrorUtils.constructNPXMessage("primaryKey"));
128: Objects.requireNonNull(entity, ErrorUtils.constructNPXMessage("entity"));
129:
130: acquireWriteLock();
131: try {
132: cache.put(primaryKey, entity, context);
133: } finally {
134: releaseWriteLock();
135: }
136: }
137:
138: /**
139: * Releases the live object cache.
140: */
141: private void releaseCache() {
142: acquireWriteLock();
143: try {
144: this.cache = new TtlCache();
145: } finally {
146: releaseWriteLock();
147: }
148: }
149:
150: @Override
151: public void clearInferredObjects() {
152: acquireWriteLock();
153: try {
154: getInferredClasses().forEach(cache::evict);
155: } finally {
156: releaseWriteLock();
157: }
158: }
159:
160: @Override
161: public <T> T get(Class<T> cls, Object primaryKey, URI context) {
162: if (cls == null || primaryKey == null) {
163: return null;
164: }
165: acquireReadLock();
166: try {
167: return cache.get(cls, primaryKey, context);
168: } finally {
169: releaseReadLock();
170: }
171: }
172:
173: /**
174: * Get the set of inferred classes.
175: * <p>
176: * Inferred classes (i. e. classes with inferred attributes) are tracked separately since they require special
177: * behavior.
178: *
179: * @return Set of inferred classes
180: */
181: public Set<Class<?>> getInferredClasses() {
182: if (inferredClasses == null) {
183: this.inferredClasses = new HashSet<>();
184: }
185: return inferredClasses;
186: }
187:
188: /**
189: * Set the inferred classes.
190: * <p>
191: * For more information about inferred classes see {@link #getInferredClasses()}.
192: *
193: * @param inferredClasses The set of inferred classes
194: */
195: @Override
196: public void setInferredClasses(Set<Class<?>> inferredClasses) {
197: this.inferredClasses = inferredClasses;
198: }
199:
200: @Override
201: public boolean contains(Class<?> cls, Object primaryKey) {
202: if (cls == null || primaryKey == null) {
203: return false;
204: }
205: acquireReadLock();
206: try {
207: return cache.contains(cls, primaryKey);
208: } finally {
209: releaseReadLock();
210: }
211: }
212:
213: @Override
214: public boolean contains(Class<?> cls, Object primaryKey, URI context) {
215: if (cls == null || primaryKey == null) {
216: return false;
217: }
218: acquireReadLock();
219: try {
220: return cache.contains(cls, primaryKey, context);
221: } finally {
222: releaseReadLock();
223: }
224: }
225:
226: @Override
227: public void evict(Class<?> cls) {
228: Objects.requireNonNull(cls, ErrorUtils.constructNPXMessage("cls"));
229:
230: acquireWriteLock();
231: try {
232: cache.evict(cls);
233: } finally {
234: releaseWriteLock();
235: }
236: }
237:
238: @Override
239: public void evict(Class<?> cls, Object primaryKey, URI context) {
240: Objects.requireNonNull(cls, ErrorUtils.constructNPXMessage("cls"));
241: Objects.requireNonNull(primaryKey, ErrorUtils.constructNPXMessage("primaryKey"));
242:
243: acquireWriteLock();
244: try {
245: cache.evict(cls, primaryKey, context);
246: } finally {
247: releaseWriteLock();
248: }
249:
250: }
251:
252: @Override
253: public void evict(URI context) {
254: acquireWriteLock();
255: try {
256: cache.evict(context);
257: } finally {
258: releaseWriteLock();
259: }
260: }
261:
262: @Override
263: public void evictAll() {
264: releaseCache();
265: }
266:
267: private void acquireReadLock() {
268: readLock.lock();
269: }
270:
271: private void releaseReadLock() {
272: readLock.unlock();
273: }
274:
275: private void acquireWriteLock() {
276: writeLock.lock();
277: }
278:
279: private void releaseWriteLock() {
280: writeLock.unlock();
281: }
282:
283: /**
284: * Sweeps the second level cache and removes entities with no more time to live.
285: *
286: * @author kidney
287: */
288: private final class CacheSweeper implements Runnable {
289:
290: public void run() {
291: LOG.trace("Running cache sweep.");
292: TtlCacheManager.this.acquireWriteLock();
293: try {
294: if (TtlCacheManager.this.sweepRunning) {
295: return;
296: }
297: TtlCacheManager.this.sweepRunning = true;
298: final long currentTime = System.currentTimeMillis();
299: final long timeToLive = TtlCacheManager.this.timeToLive;
300: final List<URI> toEvict = new ArrayList<>();
301: // Mark the objects for eviction (can't evict them now, it would
302: // cause ConcurrentModificationException)
303: for (Entry<URI, Long> e : cache.ttls.entrySet()) {
304: final long lm = e.getValue();
305: if (lm + timeToLive < currentTime) {
306: toEvict.add(e.getKey());
307: }
308: }
309: // Evict them
310: toEvict.forEach(TtlCacheManager.this::evict);
311: } finally {
312: TtlCacheManager.this.sweepRunning = false;
313: TtlCacheManager.this.releaseWriteLock();
314: }
315: }
316: }
317:
318: private static final class TtlCache extends EntityCache {
319:
320: private final Map<URI, Long> ttls = new HashMap<>();
321:
322: void put(Object primaryKey, Object entity, URI context) {
323: super.put(primaryKey, entity, context);
324: final URI ctx = context != null ? context : defaultContext;
325: updateTimeToLive(ctx);
326: }
327:
328: <T> T get(Class<T> cls, Object primaryKey, URI context) {
329: assert cls != null;
330: assert primaryKey != null;
331:
332: final URI ctx = context != null ? context : defaultContext;
333: T result = super.get(cls, primaryKey, ctx);
334: if (result != null) {
335: updateTimeToLive(ctx);
336: }
337: return result;
338: }
339:
340: private void updateTimeToLive(URI context) {
341: assert context != null;
342:
343: ttls.put(context, System.currentTimeMillis());
344: }
345:
346: void evict(URI context) {
347: final URI ctx = context != null ? context : defaultContext;
348: super.evict(ctx);
349: ttls.remove(context);
350: }
351:
352: void evict(Class<?> cls) {
353:• for (Entry<URI, Map<Class<?>, Map<Object, Object>>> e : repoCache.entrySet()) {
354: final Map<Class<?>, Map<Object, Object>> m = e.getValue();
355: m.remove(cls);
356:• if (m.isEmpty()) {
357: ttls.remove(e.getKey());
358: }
359: }
360: }
361: }
362: }