/* Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved. Contact: SYSTAP, LLC DBA Blazegraph 2501 Calvert ST NW #106 Washington, DC 20008 licenses@blazegraph.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * Created on Jun 30, 2008 */ package com.bigdata.relation.locator; import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; import java.util.Iterator; import java.util.Map; import java.util.Properties; import java.util.UUID; import java.util.WeakHashMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Lock; import org.apache.log4j.Logger; import com.bigdata.btree.IIndex; import com.bigdata.cache.ConcurrentWeakValueCache; import com.bigdata.cache.ConcurrentWeakValueCacheWithTimeout; import com.bigdata.cache.LRUCache; import com.bigdata.concurrent.NamedLock; import com.bigdata.journal.AbstractTask; import com.bigdata.journal.ICommitRecord; import com.bigdata.journal.IIndexManager; import com.bigdata.journal.IIndexStore; import com.bigdata.journal.IJournal; import com.bigdata.journal.ITx; import com.bigdata.journal.Journal; import com.bigdata.journal.TemporaryStore; import com.bigdata.journal.TimestampUtility; import com.bigdata.rawstore.IRawStore; import com.bigdata.relation.AbstractResource; import com.bigdata.relation.IRelation; import com.bigdata.relation.RelationSchema; import com.bigdata.service.IBigdataFederation; import com.bigdata.sparse.GlobalRowStoreHelper; import com.bigdata.sparse.SparseRowStore; import com.bigdata.util.NT; /** * Generic implementation relies on a ctor for the resource with the following * method signature: * * <pre> * public NAME ( IIndexManager indexManager, String namespace, Long timestamp, Properties properties ) * </pre> * * <p> * A relation is located using the {@link IIndexStore#getGlobalRowStore(long)} and * materialized by supplying an {@link IIndexManager} that will be able to * resolve the indices for the relation's view. Several different contexts are * handled: * <dl> * * <dt>{@link IBigdataFederation}</dt> * * <dd>The {@link IRelation} will be resolved using the * {@link IBigdataFederation#getGlobalRowStore(long)} and the * {@link IBigdataFederation} as its {@link IIndexManager}. The makes access to * a remote and potentially distributed {@link IIndex} transparent to the * {@link IRelation}. However, it is NOT possible to resolve local resources on * other JVMs - only scale-out indices registered against the * {@link IBigdataFederation}.</dd> * * <dt>{@link Journal}</dt> * * <dd>The {@link IRelation} will be resolved using the * {@link Journal#getGlobalRowStore(long)} and will use the local index objects * directly.</dd> * * <dt>{@link AbstractTask}</dt> * * <dd>If the index is local and monolithic then you can declare the index to * the {@link AbstractTask} and <em>override</em> the locator to use * {@link AbstractTask#getJournal()} as its index manager. This will give you * access to the local index object from within the concurrency control * mechanism</dd> * * <dt>{@link TemporaryStore}</dt> * * <dd>When used by itself, this is just like a {@link Journal}. However, * {@link TemporaryStore}s are also used to provide local resources for more * efficient data storage for a variety of purposes. When used in this manner, * you must explicitly notify the locator of the existence of the * {@link TemporaryStore} in order to be able to resolve {@link IRelation}s on * the {@link TemporaryStore}. It is prudent to use a prefix for such local * resources that guarentees uniqueness, e.g., an {@link UUID}</dd> * * </dl> * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> * * @param <T> * The generic type of the [R]elation. */ public class DefaultResourceLocator<T extends ILocatableResource<T>> // implements IResourceLocator<T> { protected static final transient Logger log = Logger .getLogger(DefaultResourceLocator.class); protected static final boolean INFO = log.isInfoEnabled(); protected final transient IIndexManager indexManager; private final IResourceLocator<T> delegate; /** * Cache for recently located resources. */ final private transient ConcurrentWeakValueCache<NT, T> resourceCache; /** * Cache for recently materialized properties from the GRS. * <p> * Note: Due to the manner in which the property sets are materialized from * the {@link GlobalRowStoreHelper}, items in this cache can not cause * references to indices or other persistence capable / high cost resources * to remain strongly reachable. */ final /*private*/ transient ConcurrentWeakValueCache<NT, Map<String,Object>> propertyCache; /** * Special property used to record the {@link IRawStore#getUUID() UUID} of * the backing {@link IIndexManager} on which the property set for some * namespace was discovered. */ private final String STORE_UUID = DefaultResourceLocator.class.getName()+".storeUUID"; /** * Provides locks on a per-namespace basis for higher concurrency. */ private final transient NamedLock<String> namedLock = new NamedLock<String>(); /** * The default #of recently located resources whose hard references will be * retained by the {@link LRUCache}. */ protected static transient final int DEFAULT_CACHE_CAPACITY = 10; /** * The default timeout for stale entries in milliseconds. */ protected static transient final long DEFAULT_CACHE_TIMEOUT = (10 * 1000); /** * Ctor uses {@link #DEFAULT_CACHE_CAPACITY} and * {@link #DEFAULT_CACHE_TIMEOUT}. * * @param indexManager * @param delegate * Optional {@link IResourceLocator} to which unanswered requests * are then delegated. */ public DefaultResourceLocator(final IIndexManager indexManager, final IResourceLocator<T> delegate) { this(indexManager, delegate, DEFAULT_CACHE_CAPACITY, DEFAULT_CACHE_TIMEOUT); } /** * * @param indexManager * @param delegate * Optional {@link IResourceLocator} to which unanswered requests * are then delegated. * @param cacheCapacity * The capacity of the internal weak value cache. * @param cacheTimeout * The timeout in milliseconds for stale entries in that cache. */ public DefaultResourceLocator(final IIndexManager indexManager, final IResourceLocator<T> delegate, final int cacheCapacity, final long cacheTimeout) { if (indexManager == null) throw new IllegalArgumentException(); this.indexManager = indexManager; this.delegate = delegate;// MAY be null. if (cacheCapacity <= 0) throw new IllegalArgumentException(); if (cacheTimeout < 0) throw new IllegalArgumentException(); this.resourceCache = new ConcurrentWeakValueCacheWithTimeout<NT, T>( cacheCapacity, TimeUnit.MILLISECONDS.toNanos(cacheTimeout)); /* * Configure the property sets cache. * * Note: This cache capacity is set to a multiple of the specififed * capacity. While the property sets in this cache can not cause indices * or relations to be retained, a cache hit significantly decreases the * cost of materializing a relation or index view. Therefore a larger * cache capacity can have a big payoff with relatively little heap * overhead. For the same reason, we also boost the cacheTimeout. */ { final int propertyCacheCapacity = Math.max(cacheCapacity, cacheCapacity * 10); final long propertyCacheTimeout = TimeUnit.MILLISECONDS .toNanos(cacheTimeout) * 10; this.propertyCache = new ConcurrentWeakValueCacheWithTimeout<NT, Map<String, Object>>( propertyCacheCapacity, propertyCacheTimeout); } } // @todo hotspot 2% total query time. @Override public T locate(final String namespace, final long timestamp) { if (namespace == null) throw new IllegalArgumentException(); if (INFO) { log.info("namespace=" + namespace + ", timestamp=" + timestamp); } T resource = null; final NT nt; /* * Note: The drawback with resolving the resource against the * [commitTime] is that the views will be the same object instance and * will have the timestamp associated with the [commitTime] rather than * the caller's timestamp. This breaks the assumption that * resource#getTimestamp() returns the transaction identifier for a * read-only transaction. In order to fix that, we resort to sharing the * Properties object instead of the resource view. */ // if (TimestampUtility.isReadOnly(timestamp) // && indexManager instanceof Journal) { // // /* // * If we are looking on a local Journal (standalone database) then // * we resolve the caller's [timestamp] to the commit point against // * which the resource will be located and handle caching of the // * resource using that commit point. This is done in order to share // * a read-only view of a resource with any request which would be // * serviced by the same commit point. Any such views are read-only // * and immutable. // */ // // final Journal journal = (Journal) indexManager; // // // find the commit record on which we need to read. // final long commitTime = journal.getCommitRecord( // TimestampUtility.asHistoricalRead(timestamp)) // .getTimestamp(); // // nt = new NT(namespace, commitTime); // // } else { nt = new NT(namespace, timestamp); // // } // test cache: hotspot 93% of method time. resource = resourceCache.get(nt); if (resource != null) { if (log.isDebugEnabled()) log.debug("cache hit: " + resource); // cache hit. return resource; } /* * Since there was a cache miss, acquire a lock for the named relation * so that the locate + cache.put sequence will be atomic. * * TODO Better javadoc. This is a mixed blessing. Obviously, any request * for the same timestamp or the same commit time should hit this lock * so we serialize such efforts. However, lacking the commitTime for a * request, this serializes all lookups against the same namespace. If * we have concurrent queries arriving against different commit times * then this lock could be too broad. Ideally, the lock would be on * (namespace,commitTime). Fix this when we address * http://sourceforge.net/apps/trac/bigdata/ticket/266 (Refactor native * long tx id to thin object). */ final Lock lock = namedLock.acquireLock(namespace); try { // test cache now that we have the lock. resource = resourceCache.get(nt); if (resource != null) { if (log.isDebugEnabled()) log.debug("cache hit: " + resource); return resource; } else { if (INFO) log.info("cache miss: namespace=" + namespace + ", timestamp=" + timestamp); resource = cacheMiss(nt); } if (resource == null) { if (INFO) log.info("Not found: " + nt); } else { if (INFO) log.info("Caching: " + nt + " as " + resource); // Add to the cache. resourceCache.put(nt, resource); } return resource; } finally { lock.unlock(); } } /** * Handle a cache miss. * * @param nt * The (namespace, timestamp) tuple. * * @return The resolved resource -or- <code>null</code> if the resource * could not be resolved for that namespace and timestamp. */ private T cacheMiss(final NT nt) { T resource = null; /* * First, test this locator, including any [seeAlso] IIndexManager * objects. */ final AtomicReference<IIndexManager> foundOn = new AtomicReference<IIndexManager>(); final Properties properties = locateResource(nt.getName(), nt.getTimestamp(), foundOn); if (properties == null) { // Not found by this locator. if(delegate != null) { /* * A delegate was specified, so see if the delegate can resolve * this request. */ if (INFO) { log.info("Not found - passing to delegate: " + nt); } // pass request to delegate. resource = delegate.locate(nt.getName(), nt.getTimestamp()); if (resource != null) { if (INFO) { log.info("delegate answered: " + resource); } return resource; } } // not found. return null; } if (log.isDebugEnabled()) { log.debug(properties.toString()); } // can throw a ClassCastException. final String className = properties.getProperty(RelationSchema.CLASS); if (className == null) { /* * Note: This can indicate a deleted resource (all properties have * been cleared from the global row store). */ // throw new IllegalStateException( // "Required property not found: namespace=" + nt.getName() // + ", property=" + RelationSchema.CLASS); return null; } final Class<? extends T> cls; try { cls = (Class<? extends T>) Class.forName(className); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } if (log.isDebugEnabled()) { log.debug("Implementation class=" + cls.getName()); } // create a new instance of the relation. resource = newInstance(cls, foundOn.get(), nt, properties); return resource; } /** * Note: Caller is synchronized for this <i>namespace</i>. * * @param namespace * The namespace for the resource. * @param timestamp * @param foundOn * Used to pass back the {@link IIndexManager} on which the * resource was found as a side-effect. * * @return The properties for that resource. */ protected Properties locateResource(final String namespace, final long timestamp, final AtomicReference<IIndexManager> foundOn) { synchronized (seeAlso) { // FIXME Probably a read/write lock since [seeAlso] normally empty. for (IIndexManager indexManager : seeAlso.keySet()) { /* * read properties from the global row store for the default * index manager. */ Properties properties = null; try { properties = locateResourceOn(indexManager, namespace, timestamp); } catch (IllegalStateException t) { if(indexManager instanceof TemporaryStore) { /* * Note: Asynchronous close is common for temporary * stores since they can be asynchronously closed * and removed from the [seeAlso] weak value cache. */ if (INFO) log.info("Closed? " + indexManager); } else { /* * Other stores should more typically remain open, but * they could be closed asynchronously for valid * reasons. */ log.warn("Closed? " + indexManager); } continue; } catch (Throwable t) { log.error(t, t); continue; } if (properties != null) { if(indexManager instanceof IRawStore) { final UUID storeUUID = (UUID) properties.get(STORE_UUID); if (storeUUID != null && !storeUUID.equals(((IRawStore) indexManager).getUUID())) { // Property set for namespace was found on a // different store. continue; } } if (INFO) { log.info("Found: namespace=" + namespace + " on " + indexManager + ", properties=" + properties); } // tell the caller _where_ we found the resource. foundOn.set(indexManager); return properties; } } } /* * read properties from the global row store for the default index * manager. */ final Properties properties = locateResourceOn(indexManager, namespace, timestamp); if (properties != null) { if (INFO) { log.info("Found: namespace=" + namespace + " on " + indexManager); } // tell the caller _where_ we found the resource. foundOn.set(indexManager); return properties; } // will be null. return properties; } /** * Return the {@link Properties} that will be used to configure the * {@link IRelation} instance. The {@link RelationSchema#CLASS} property * MUST be defined and specified the runtime class that will be * instantiated. * <p> * Note: A <code>null</code> return is an indication that the resource was * NOT FOUND on this {@link IIndexManager} and the caller SHOULD try another * {@link IIndexManager}. * * @param indexManager * @param namespace * The resource identifier - this is the primary key. * @param timestamp * The timestamp of the resource view. * * @return The {@link Properties} iff there is a logical row for the given * namespace. */ protected Properties locateResourceOn(final IIndexManager indexManager, final String namespace, final long timestamp) { if (INFO) { log.info("indexManager=" + indexManager + ", namespace=" + namespace + ", timestamp=" + timestamp); } /* * Look at the global row store view corresponding to the specified * timestamp. * * Note: caching here is important in order to reduce the heap pressure * associated with large numbers of concurrent historical reads against * the same commit point when those reads are performed within read-only * transactions and, hence, each read is performed with a DISTINCT * timestamp. Since the timestamps are distinct, the resource [cache] * will have cache misses. This code provides for a [propertyCache] * which ensures that we share the materialized properties from the GRS * across resource views backed by the same commit point (and also avoid * unnecessary GRS reads). */ Long commitTime2 = null; final Map<String, Object> map; final boolean propertyCacheHit; if (TimestampUtility.isReadOnly(timestamp) && !TimestampUtility.isReadCommitted(timestamp)) { /* * A stable read-only view. * * Note: This code path is NOT for read-committed views since such * views are not stable. * * Note: We will cache the property set on this code path. This code * path is NOT used for read-committed or mutable views since the * cache would prevent updated information from becoming visible. */ final long readTime; if (indexManager instanceof IJournal) { /* * For a Journal, find the commitTime backing that read-only * view. */ final IJournal journal = (IJournal) indexManager; /* * Note: Using the local transaction manager to resolve a * read-only transaction identifer to the ITx object and then * retrieving the readsOnCommitTime from that ITx is a * non-blocking code path. It is preferrable to looking up the * commitRecord in the CommitRecordIndex, which can be * contended. This non-blocking code path will handle any * read-only tx, but it will not be able to locate a timestamp * not protected by a tx. */ final ITx tx = journal.getLocalTransactionManager().getTx( timestamp); if (tx != null) { // Fast path. commitTime2 = readTime = tx.getReadsOnCommitTime(); } else { // find the commit record on which we need to read. final ICommitRecord commitRecord = journal .getCommitRecord(TimestampUtility .asHistoricalRead(timestamp)); if (commitRecord == null) { /* * No data for that timestamp. */ return null; } /* * Find the timestamp associated with that commit record. * * Note: We also save commitTime2 to stuff into the properties, * thereby revealing the commitTime backing the resource view * whenever possible. */ commitTime2 = readTime = commitRecord.getTimestamp(); } } else { /* * Federation, TemporaryStore, etc. * * Note: The TemporaryStore lacks a history mechanism. * * Note: The federation will cache based on the caller's * [timestamp]. This works well if a global read lock has been * asserted and query is directed against that read-only tx. It * works poorly if a new tx is obtained for each query. Use a * shared read lock for better performance on the federation! * * TODO The IBigdataFederation does not expose the backing * commit time, which is why using a shared read lock is * important for good performance. * * @see https://sourceforge.net/apps/trac/bigdata/ticket/266 * (replace native long with thin tx interface) */ readTime = timestamp; } /* * The key for the property cache is based on the 'readTime'. When * possible, this will be the commitTime on which the [timestamp] is * reading. That provides some reuse of the cache entry across * different [timestamp]s for read-only views. * * TODO The TPS stores the evolution of the property set. We could * create a more sophisticated cache which can answer for any * historical view for which there is data in the TPS. Under this * model, the TPS would be cached under the [namespace] without * regard to the [timestamp]. We would then use TPS#asMap(timestamp) * to reconstruct the propertySet for the caller's timestamp. We * would only need to read-through to the GRS when the caller's * [timestamp] was more recent than the last update on the TPS for * the namespace. This approach might be an attractive alternative * in combination with some sort of invalidation mechanism for the * rate writes on the GRS for a relation namespace (relation data * writes vastly outpace GRS writes for the metadata declaring that * relation so a guaranteed invalidation mechanism might very well * pay off). * * @see https://sourceforge.net/apps/trac/bigdata/ticket/266 * (replace native long with thin tx interface) */ final NT nt = new NT(namespace, readTime); // Check the cache before materializing the properties from GRS. final Map<String, Object> cachedMap = propertyCache.get(nt); if (cachedMap != null) { /* * Property cache hit. */ // Save reference to the property set. map = cachedMap; propertyCacheHit = true; } else { /* * Property cache miss. */ propertyCacheHit = false; // Use the GRS view as of that commit point. final SparseRowStore rowStore = indexManager .getGlobalRowStore(readTime); // Read the properties from the GRS. try { map = rowStore == null ? null : rowStore.read( RelationSchema.INSTANCE, namespace); } catch (RuntimeException ex) { // Provide more information in trace. // Provide more information in trace. // See http://jira.blazegraph.com/browse/BLZG-1268 throw new RuntimeException(ex.getMessage() + "::namespace=" + namespace + ", timestamp=" + TimestampUtility.toString(timestamp) + ", readTime=" + TimestampUtility.toString(readTime), ex); } if (map != null) { // Stuff the properties into the cache. if (indexManager instanceof IRawStore) { map.put(STORE_UUID, ((IRawStore) indexManager).getUUID()); } propertyCache.put(nt, map); } } } else { final SparseRowStore rowStore; /* * Look up the GRS for a non-read-historical view. * * Note: We do NOT cache the property set on this code path. * * Note: This used to use the UNISOLATED view for all such requests. * I have modified the code (9/7/2012) to use the caller's view. * This allows a caller using a READ_COMMITTED view to obtain a * read-only view of the GRS. That is required to support GRS reads * on followers in an HA quorum (followers are read-only and do not * have access to the unisolated versions of indices). */ if (timestamp == ITx.UNISOLATED) { /* * The unisolated view. */ rowStore = indexManager.getGlobalRowStore(); } else if (timestamp == ITx.READ_COMMITTED) { /* * View for the last commit time. */ rowStore = indexManager.getGlobalRowStore(indexManager .getLastCommitTime()); } else if (TimestampUtility.isReadWriteTx(timestamp)) { if (indexManager instanceof IJournal) { final IJournal journal = (IJournal) indexManager; final ITx tx = journal.getLocalTransactionManager().getTx( timestamp); if (tx == null) { // No such tx? throw new IllegalStateException("No such tx: " + timestamp); } // Use the view that the tx is reading on. rowStore = indexManager.getGlobalRowStore(tx .getReadsOnCommitTime()); } else { /* * TODO This should use the readsOnCommitTime on the cluster * as well. * * @see https://sourceforge.net/apps/trac/bigdata/ticket/266 * (thin txId) */ rowStore = indexManager.getGlobalRowStore(/* unisolated */); } } else { throw new AssertionError("timestamp=" + TimestampUtility.toString(timestamp)); } // Read the properties from the GRS. map = rowStore == null ? null : rowStore.read( RelationSchema.INSTANCE, namespace); propertyCacheHit = false; } if (map == null) { if (log.isDebugEnabled()) { log.debug("Not found: indexManager=" + indexManager + ", namespace=" + namespace + ", timestamp=" + timestamp); } return null; } // Note: Prevent cross view mutation. final Properties properties = new Properties(); properties.putAll(map); if (commitTime2 != null) { /* * Make the commit time against which we are reading accessible to * the locatable resource. */ properties.put(RelationSchema.COMMIT_TIME, commitTime2); } if (log.isTraceEnabled()) { log.trace("Read properties: indexManager=" + indexManager + ", namespace=" + namespace + ", timestamp=" + timestamp + ", propertyCacheHit=" + propertyCacheHit + ", properties=" + properties); } return properties; } /** * Create a new view of the relation. * * @param indexManager * The {@link IIndexManager} that will be used to resolve the * named indices for the relation. * @param nt * The namespace and timestamp for the view of the relation. * @param properties * Configuration properties for the relation. * * @return A new instance of the identified resource. */ protected T newInstance(final Class<? extends T> cls, final IIndexManager indexManager, final NT nt, final Properties properties) { if (cls == null) throw new IllegalArgumentException(); if (indexManager == null) throw new IllegalArgumentException(); if (nt == null) throw new IllegalArgumentException(); if (properties == null) throw new IllegalArgumentException(); final Constructor<? extends T> ctor; try { ctor = cls.getConstructor(new Class[] {// IIndexManager.class,// String.class,// relation namespace Long.class, // timestamp of the view Properties.class // configuration properties. }); } catch (Exception e) { throw new RuntimeException("No appropriate ctor?: cls=" + cls.getName() + " : " + e, e); } final T r; try { r = ctor.newInstance(new Object[] {// indexManager,// nt.getName(), // nt.getTimestamp(), // properties // }); r.init(); if(INFO) { log.info("new instance: "+r); } return r; } catch (Exception ex) { throw new RuntimeException("Could not instantiate relation: " + ex, ex); } } /** * Places the instance into the cache iff there is no existing instance in * the cache for the same resource and timestamp. * <p> * Note: This is done automatically by {@link AbstractResource}. * * @param instance * The instance. */ public T putInstance(final T instance) { if (instance == null) throw new IllegalArgumentException(); final String namespace = instance.getNamespace(); final long timestamp = instance.getTimestamp(); if (INFO) { log.info("namespace=" + namespace+", timestamp="+timestamp); } // lock is only for the named relation. final Lock lock = namedLock.acquireLock(namespace); try { final NT nt = new NT(namespace, timestamp); final T tmp = resourceCache.get(nt); if (tmp != null) { if(INFO) { log.info("Existing instance already in cache: "+tmp); } return tmp; } resourceCache.put(nt, instance); if (INFO) { log.info("Instance added to cache: nt=" + nt + ", instance=" + instance + ", indexManager=" + (instance instanceof AbstractResource ? ((AbstractResource<?>) instance).getIndexManager() : "n/a")); } return instance; } finally { lock.unlock(); } } @Override public void discard(final ILocatableResource<T> instance, final boolean destroyed) { if (instance == null) throw new IllegalArgumentException(); final String namespace = instance.getNamespace(); final long timestamp = instance.getTimestamp(); if (INFO) { log.info("namespace=" + namespace + ", timestamp=" + timestamp + ", destroyed=" + destroyed); } // lock is only for the named relation. final Lock lock = namedLock.acquireLock(namespace); try { final NT nt = new NT(namespace, timestamp); /* * Clear the resource cache, but we do not need to clear the * property cache since it only retains immutable historical state. */ final boolean found = resourceCache.remove(nt) != null; if (INFO) { log.info("instance=" + instance + ", found=" + found); } if (destroyed) { // Also discard the unisolated view. resourceCache.remove(new NT(namespace, ITx.UNISOLATED)); // Also discard the read-committed view. resourceCache.remove(new NT(namespace, ITx.READ_COMMITTED)); } } finally { lock.unlock(); } } @Override public void clearUnisolatedCache() { // Discard any unisolated resource views. // resourceCache.clear(); // propertyCache.clear(); { final Iterator<Map.Entry<NT, WeakReference<T>>> itr = resourceCache.entryIterator(); while (itr.hasNext()) { final Map.Entry<NT, WeakReference<T>> e = itr.next(); final NT nt = e.getKey(); if (TimestampUtility.isUnisolated(nt.getTimestamp())) { itr.remove(); // resourceCache.remove(nt); } } }{ final Iterator<Map.Entry<NT, WeakReference<Map<String,Object>>>> itr = propertyCache.entryIterator(); while (itr.hasNext()) { final Map.Entry<NT, WeakReference<Map<String,Object>>> e = itr.next(); final NT nt = e.getKey(); if (TimestampUtility.isUnisolated(nt.getTimestamp())) { itr.remove(); // propertyCache.remove(nt); } } } if (delegate != null) { // And do this on the delegate as well. delegate.clearUnisolatedCache(); } if (log.isInfoEnabled()) log.info("Cleared resource cache"); } /** * Causes the {@link IIndexManager} to be tested when attempting to resolve * a resource identifiers. The {@link IIndexManager} will be automatically * cleared from the set of {@link IIndexManager}s to be tested if its * reference is cleared by the JVM. If it becomes closed asynchronously then * a warning will be logged until its reference is cleared. * <p> * Note: The default {@link IIndexManager} specified to the ctor normally * ensures that the global namespace is consistent (it is not possible to * have two indices or {@link ILocatableResource}s with the same name). * When you add additional {@link IIndexManager}s, the opportunity for an * <em>inconsistent</em> unified namespace is introduced. You can protect * yourself by ensuring that resources located on {@link TemporaryStore}s * and the like are always created with a unique prefix, e.g., the * {@link UUID} of the {@link TemporaryStore} itself or a {@link UUID} for * each container that is allocated on the {@link TemporaryStore}. * * @param indexManager * Typically a {@link TemporaryStore} or {@link Journal} * containing local resources. * * @see #locateResource(String) */ public void add(final IIndexManager indexManager) { if (indexManager == null) throw new IllegalArgumentException(); synchronized (seeAlso) { /* * Note: weak reference keys, nothing stored under the key. */ seeAlso.put(indexManager, null); if (INFO) { log.info("size=" + seeAlso.size() + ", added indexManager=" + indexManager); } } } private final WeakHashMap<IIndexManager, Void> seeAlso = new WeakHashMap<IIndexManager, Void>(); }