All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.pojomvcc.impl.RootObjectCacheImpl Maven / Gradle / Ivy

The newest version!
package com.github.pojomvcc.impl;

import com.github.pojomvcc.*;

import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * A {@code RootObjectCache} is the root of the {@code ObjectCache} cache heirarchies.
 * 

* A {@code RootObjectCache} is not modifyable directly, a {@code RevisionObjectCache} must be created * (using the {@link RootObjectCacheImpl#checkout()} method) and the changes made to that. * * @author Aidan Morgan */ public class RootObjectCacheImpl implements RootObjectCache { /** * The current revision number of this store. */ private final AtomicLong CURRENT_REVISION = new AtomicLong(0); /** * The {@code java.util.concurrent.locks.ReadWriteLock} that ensures safe access to this cache from * multiple threads. */ private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); /** * A {@code List} of currently active {@code com.github.pojomvcc.RevisionObjectCache}s. */ private List> openRevisionCaches; /** * A {@code java.util.Map} that stores a {@code K} with the {@code com.github.pojomvcc.impl.ObjectRevisions} * that track the history of the objects. */ private Map> revisions; /** * A {@code java.util.Map} of revision to a {@code java.util.List} of {@link K} that are the keys * in the {@code com.github.pojomvcc.RootObjectCache} for that revision. */ private Map> keysPerRevisionMap; /** * The {@link CacheElementFactory} that is used for creating clones and merging {@link V}s. */ private CacheElementFactory factory; /** * The {@link com.github.pojomvcc.CacheExpiry} that determines how long historical information about * {@link V}s should be retained. It also can provide a mechanism for * handling expired {@link com.github.pojomvcc.RevisionKeyList}s so that that can be retrieved later on. */ private CacheExpiry cacheExpiryPolicy = CacheExpiry.DEFAULT(); /** * Constructor. * * @param factory the {@link CacheElementFactory} that is used for creating clones and * merging {@link V}s. */ public RootObjectCacheImpl(CacheElementFactory factory) { this.openRevisionCaches = new ArrayList>(); this.revisions = new HashMap>(); this.keysPerRevisionMap = new HashMap>(); this.keysPerRevisionMap.put(0L, new RevisionKeyList(0L)); this.factory = factory; } /** * @inheritDoc */ public List getKeysForRevision(long revision) { try { readWriteLock.readLock().lock(); if (revision > CURRENT_REVISION.get()) { throw new ObjectCacheException("Attempting to access revision " + revision + " which is > current head " + CURRENT_REVISION.get()); } if (!keysPerRevisionMap.containsKey(revision)) { throw new ObjectCacheException("Cannot get keys for revision " + revision + ". Current revision is " + CURRENT_REVISION.get() + "."); } return Collections.unmodifiableList(keysPerRevisionMap.get(revision).getKeys()); } finally { readWriteLock.readLock().unlock(); } } /** * @inheritDoc */ public List getKeys() { try { readWriteLock.readLock().lock(); return getKeysForRevision(CURRENT_REVISION.get()); } finally { readWriteLock.readLock().unlock(); } } /** * @inheritDoc */ public V getElementWithRevision(long revision, K key) { try { readWriteLock.readLock().lock(); if (revision > CURRENT_REVISION.get()) { throw new ObjectCacheException("Attempting to access revision " + revision + " which is > current head " + CURRENT_REVISION.get()); } CacheElementRevisions revs = revisions.get(key); if (revs != null) { return revs.get(revision); } return null; } finally { readWriteLock.readLock().unlock(); } } /** * @inheritDoc */ public CacheElementFactory getElementFactory() { return factory; } /** * @inheritDoc */ public long getRevision() { try { readWriteLock.readLock().lock(); return CURRENT_REVISION.get(); } finally { readWriteLock.readLock().unlock(); } } /** * @inheritDoc */ public V getElement(K key) { return getElementWithRevision(CURRENT_REVISION.get(), key); } /** * @inheritDoc */ public RevisionObjectCache checkout() { try { readWriteLock.readLock().lock(); RevisionObjectCache impl = new RevisionObjectCacheImpl(this, CURRENT_REVISION.get()); openRevisionCaches.add(impl); return impl; } finally { readWriteLock.readLock().unlock(); } } /** * @inheritDoc */ public ReadOnlyRevisionObjectCache export() { try { readWriteLock.readLock().lock(); return new RevisionObjectCacheImpl(this, CURRENT_REVISION.get()); } finally { readWriteLock.readLock().unlock(); } } /** * @inheritDoc */ public int size() { return keysPerRevisionMap.get(CURRENT_REVISION.get()).size(); } /** * @inheritDoc */ public void commit(RevisionObjectCache cache) { try { readWriteLock.writeLock().lock(); long revision = CURRENT_REVISION.incrementAndGet(); // this is the set of keys associated with a revision. List keysForRevision = keysPerRevisionMap.get(revision - 1).getKeys(); List cache_keys = new ArrayList(keysForRevision.size() + cache.getAddedElements().size()); cache_keys.addAll(keysForRevision); for (K key : cache.getAddedElements()) { assert !revisions.containsKey(key); CacheElementRevisions revs = new CacheElementRevisions(this, key); V added = cache.getElement(key); if (added == null) { throw new ObjectCacheException("Added CacheElement is null."); } V addedClone = factory.createClone(added); revs.addElement(revision, key, addedClone); // new item, so update the cache keys. cache_keys.add(key); // new item so need to create an CacheElementRevisions object for it. revisions.put(key, revs); } for (K key : cache.getModifiedElements()) { V element = cache.getElement(key); CacheElementRevisions revs = revisions.get(key); assert revs != null; V merged = factory.createClone(factory.merge(revs.getLeading(), element)); revs.addModification(revision, key, merged); } for (K key : cache.getRemovedElements()) { CacheElementRevisions revs = revisions.get(key); assert revs != null; revs.removeElement(revision, key); cache_keys.remove(key); revisions.put(key, revs); } keysPerRevisionMap.put(revision, new RevisionKeyList(revision, cache_keys)); } finally { readWriteLock.writeLock().unlock(); } } /** * Handles the expiry of {@link com.github.pojomvcc.RevisionKeyList}s from this {@link com.github.pojomvcc.RootObjectCache}. * Will use the {@link com.github.pojomvcc.CacheExpiry} registered to check which revisions should be evicted * from memory. */ private void expire() { try { readWriteLock.writeLock().lock(); if (cacheExpiryPolicy != null) { if (!cacheExpiryPolicy.getPolicy().shouldRun(this)) { return; } List> keysToKill = new ArrayList>(); for (RevisionKeyList rkl : keysPerRevisionMap.values()) { // make sure we don't somehow drop the current revision if (rkl.getRevision() != CURRENT_REVISION.get()) { if (cacheExpiryPolicy.getPolicy().shouldExpire(this, rkl)) { keysToKill.add(rkl); } } } // we now have a set of RevisionKeyList(s) that should be evicted from the cache. // go through and evict them from memory. for (RevisionKeyList rkl : keysToKill) { for (K ck : rkl.getKeys()) { CacheElementRevisions revs = revisions.get(ck); // the CacheElementRevision won't necessarily remove the revision when we call this // there are cases in which the revision can't be removed. // @see CacheElementRevisions#remove(long) for more information revs.removeRevision(rkl.getRevision()); } // remove all traces of the revision from the store keysPerRevisionMap.remove(rkl.getRevision()); // optionally provide some mechanism for handling the expired revisions, probably by writing // them to disk, or a database, or something. cacheExpiryPolicy.getHandler().expired(rkl); } } } finally { readWriteLock.writeLock().unlock(); } } /** * @inheritDoc */ public void close(RevisionObjectCache cache) { try { readWriteLock.writeLock().lock(); this.openRevisionCaches.remove(cache); // a dependent cache has been closed, so lets check for any values that can be removed from the // cache. expire(); } finally { readWriteLock.writeLock().unlock(); } } /** * @inheritDoc */ public List> getActiveRevisions() { try { readWriteLock.readLock().lock(); return Collections.unmodifiableList(openRevisionCaches); } finally { readWriteLock.readLock().unlock(); } } /** * @inheritDoc */ public void setCacheExpiry(CacheExpiry pol) { this.cacheExpiryPolicy = pol; } /** * @inheritDoc */ public CacheExpiry getCacheExpiry() { return cacheExpiryPolicy; } /** * @inheritDoc */ public boolean containsKey(long revision, K keyForIndex) { try { readWriteLock.readLock().lock(); return revisions.get(keyForIndex).containsRevision(revision); } finally { readWriteLock.readLock().unlock(); } } /** * @inheritDoc */ public boolean containsKey(K key) { try { readWriteLock.readLock().lock(); return revisions.containsKey(key); } finally { readWriteLock.readLock().unlock(); } } /** * Returns a {@code java.util.Map} which is a simple mechanism for getting the current revision. *

* The returned {@code java.util.Map} is read-only and cannot be modified in any way. */ public Map asMap() { try { readWriteLock.writeLock().lock(); return new RevisionObjectCacheMap(this); } finally { readWriteLock.writeLock().unlock(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy