/*************************************************** * * cismet GmbH, Saarbruecken, Germany * * ... and it just works. * ****************************************************/ package Sirius.navigator.tools; import Sirius.navigator.connection.SessionManager; import Sirius.navigator.connection.proxy.ConnectionProxy; import Sirius.navigator.exception.ConnectionException; import Sirius.server.middleware.types.MetaObject; import org.apache.log4j.Logger; import java.lang.ref.SoftReference; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * DOCUMENT ME! * * @author therter * @version $Revision$, $Date$ */ public class MetaObjectCache { //~ Static fields/initializers --------------------------------------------- private static final transient Logger LOG = Logger.getLogger(MetaObjectCache.class); //~ Instance fields -------------------------------------------------------- // we're soft-referencing the whole array so that we can be sure that the whole array will be collected and not only // single items of the array. if there is a need for additional cache access methods we'll have to change the data // store anyway private final transient Map<Integer, SoftReference<MetaObject[]>> cache; private final transient Map<Integer, ReentrantReadWriteLock> locks; //~ Constructors ----------------------------------------------------------- /** * Creates a new MetaSearchCache object. */ private MetaObjectCache() { cache = new HashMap<Integer, SoftReference<MetaObject[]>>(); locks = new HashMap<Integer, ReentrantReadWriteLock>(); } //~ Methods ---------------------------------------------------------------- /** * DOCUMENT ME! * * @return DOCUMENT ME! */ public static MetaObjectCache getInstance() { return LazyInitialiser.INSTANCE; } /** * DOCUMENT ME! * * @param query DOCUMENT ME! * * @return DOCUMENT ME! * * @deprecated use {@link #getMetaObjectsByQuery(java.lang.String)} instead */ public MetaObject[] get(final String query) { try { getLock(query.intern().hashCode()).readLock().lock(); final SoftReference<MetaObject[]> objs = cache.get(query.intern().hashCode()); return (objs == null) ? null : objs.get(); } finally { getLock(query.intern().hashCode()).readLock().unlock(); } } /** * DOCUMENT ME! * * @param query DOCUMENT ME! * @param value DOCUMENT ME! * * @deprecated altering cache entries manually is highly discouraged */ public void put(final String query, final MetaObject[] value) { try { getLock(query.intern().hashCode()).writeLock().lock(); cache.put(query.intern().hashCode(), new SoftReference<MetaObject[]>(value)); } finally { getLock(query.intern().hashCode()).writeLock().unlock(); } } /** * Completely wipes the cache content. */ public synchronized void clearCache() { try { final Collection<ReentrantReadWriteLock> c = getAllLocks(); for (final ReentrantReadWriteLock tmp : c) { tmp.writeLock().lock(); } cache.clear(); } finally { final Collection<ReentrantReadWriteLock> c = getAllLocks(); for (final ReentrantReadWriteLock tmp : c) { tmp.writeLock().unlock(); } } } /** * Wipes the cache content for the given query and returns the cache's original content. * * @param query the query for which the cache shall be cleared * * @return DOCUMENT ME! */ public MetaObject[] clearCache(final String query) { try { getLock(query.intern().hashCode()).writeLock().lock(); final SoftReference<MetaObject[]> objs = cache.put(query.intern().hashCode(), null); return (objs == null) ? null : objs.get(); } finally { getLock(query.intern().hashCode()).writeLock().unlock(); } } /** * DOCUMENT ME! * * @param query DOCUMENT ME! * * @return DOCUMENT ME! * * @deprecated use {@link #getMetaObjectsByQuery(java.lang.String)} instead */ public MetaObject[] getMetaObjectByQuery(final String query) { try { return getMetaObjectsByQuery(query, false); } catch (final CacheException e) { // this is only to maintain compliance with the previous API behaviour LOG.warn("exception in cache, returning empty array", e); // NOI18N return new MetaObject[0]; } } /** * Get {@link MetaObject}s by query using the very same query string as one would request them from the server. This * operation simply calls {@link #getMetaObjectsByQuery(java.lang.String, boolean)} with the given query and <code> * false</code> for force reload. It throws an exception to indicate any errors so that it can maintain compliance * with a call to {@link ConnectionProxy#getMetaObjectByQuery(java.lang.String, int)} with regards to its return * value. * * @param query the query to get the <code>MetaObject</code>s for * * @return an array of <code>MetaObject</code>s as they would have been returned from * ConnectionProxy#getMetaObjectByQuery(java.lang.String, int) if used directly * * @throws CacheException if any error occurs, e.g. the server is not reachable if the cache is empty * * @see #getMetaObjectsByQuery(java.lang.String, boolean) */ @Deprecated public MetaObject[] getMetaObjectsByQuery(final String query) throws CacheException { return getMetaObjectsByQuery(query, null, false); } /** * Get {@link MetaObject}s by query using the very same query string as one would request them from the server. This * operation simply calls {@link #getMetaObjectsByQuery(java.lang.String, boolean)} with the given query and <code> * false</code> for force reload. It throws an exception to indicate any errors so that it can maintain compliance * with a call to {@link ConnectionProxy#getMetaObjectByQuery(java.lang.String, int)} with regards to its return * value. * * @param query the query to get the <code>MetaObject</code>s for * @param domain the domai to get the <code>MetaObject</code>s from * * @return an array of <code>MetaObject</code>s as they would have been returned from * ConnectionProxy#getMetaObjectByQuery(java.lang.String, int) if used directly * * @throws CacheException if any error occurs, e.g. the server is not reachable if the cache is empty * * @see #getMetaObjectsByQuery(java.lang.String, boolean) */ public MetaObject[] getMetaObjectsByQuery(final String query, final String domain) throws CacheException { return getMetaObjectsByQuery(query, domain, false); } /** * Get {@link MetaObject}s by query using the very same query string as one would request them from the server. This * operation supports forcing of a reload so that callers are assured to receive a current result as it would have * been returned from {@link ConnectionProxy#getMetaObjectByQuery(java.lang.String, int)}. It throws an exception to * indicate any errors so that it can maintain compliance with a call to * {@link ConnectionProxy#getMetaObjectByQuery(java.lang.String, int)} with regards to its return value. * * @param query the query to get the <code>MetaObject</code>s for * @param forceReload force a reload of the <code>MetaObject</code>s if they have already been cached * * @return an array of <code>MetaObject</code>s as they would have been returned from * {@link ConnectionProxy#getMetaObjectByQuery(java.lang.String, int)} if used directly * * @throws CacheException if any error occurs, e.g. the server is not reachable if the cache is empty or was * forced to reload * * @see ConnectionProxy#getMetaObjectByQuery(java.lang.String, int) */ @Deprecated public MetaObject[] getMetaObjectsByQuery(final String query, final boolean forceReload) throws CacheException { return getMetaObjectsByQuery(query, null, forceReload); } /** * Get {@link MetaObject}s by query using the very same query string as one would request them from the server. This * operation supports forcing of a reload so that callers are assured to receive a current result as it would have * been returned from {@link ConnectionProxy#getMetaObjectByQuery(java.lang.String, int)}. It throws an exception to * indicate any errors so that it can maintain compliance with a call to * {@link ConnectionProxy#getMetaObjectByQuery(java.lang.String, int)} with regards to its return value. * * @param query the query to get the <code>MetaObject</code>s for * @param domain the domain to get the <code>MetaObject</code>s from * @param forceReload force a reload of the <code>MetaObject</code>s if they have already been cached * * @return an array of <code>MetaObject</code>s as they would have been returned from * {@link ConnectionProxy#getMetaObjectByQuery(java.lang.String, int)} if used directly * * @throws CacheException if any error occurs, e.g. the server is not reachable if the cache is empty or was * forced to reload * * @see ConnectionProxy#getMetaObjectByQuery(java.lang.String, int) */ public MetaObject[] getMetaObjectsByQuery(final String query, final String domain, final boolean forceReload) throws CacheException { if (query == null) { return null; } final String iQuery = query.intern(); final Integer qHash = (iQuery + ((domain != null) ? ("@" + domain) : "")).hashCode(); MetaObject[] cachedObjects = null; Lock lock = null; try { lock = getLock(qHash).readLock(); lock.lock(); SoftReference<MetaObject[]> objs = cache.get(qHash); cachedObjects = (objs == null) ? null : objs.get(); if ((cachedObjects == null) || forceReload) { lock.unlock(); lock = getLock(qHash).writeLock(); lock.lock(); final boolean wasEmpty = cachedObjects == null; // somebody may have aquired the write lock in the meantime and we're late objs = cache.get(qHash); cachedObjects = (objs == null) ? null : objs.get(); // if this is the case we truly have to load it because there are no objects or there have already been // objects in the cache but a reload is forced. in case of the write lock was aquired in the mean time // and the cache has been filled by another thread and the forceReload is true, we don't want to reload // (because it has already been done by the other thread) if ((cachedObjects == null) || (!wasEmpty && forceReload)) { try { if (LOG.isDebugEnabled()) { LOG.debug("loading metaobjects: " // NOI18N + "[query=" + iQuery // NOI18N + "|qHash=" + qHash // NOI18N + "|cachedObjects=" + cachedObjects // NOI18N + "|wasEmpty=" + wasEmpty // NOI18N + "|forceReload=" + forceReload // NOI18N + "]"); // NOI18N } if (domain != null) { cachedObjects = SessionManager.getProxy() .getMetaObjectByQuery(SessionManager.getSession().getUser(), query, domain); } else { cachedObjects = SessionManager.getProxy().getMetaObjectByQuery(iQuery, 0); } cache.put(qHash, new SoftReference<MetaObject[]>(cachedObjects)); } catch (final ConnectionException ex) { final String message = "cannot fetch meta objects for query: " + iQuery; // NOI18N LOG.error(message, ex); throw new CacheException(iQuery, message, ex); } } } } finally { if (lock != null) { lock.unlock(); } } return cachedObjects; } /** * DOCUMENT ME! * * @param hash DOCUMENT ME! * * @return DOCUMENT ME! */ private synchronized ReentrantReadWriteLock getLock(final int hash) { ReentrantReadWriteLock lock = locks.get(hash); if (lock == null) { lock = new ReentrantReadWriteLock(); locks.put(hash, lock); } return lock; } /** * DOCUMENT ME! * * @return DOCUMENT ME! */ private synchronized Collection<ReentrantReadWriteLock> getAllLocks() { return locks.values(); } //~ Inner Classes ---------------------------------------------------------- /** * DOCUMENT ME! * * @version $Revision$, $Date$ */ private static final class LazyInitialiser { //~ Static fields/initializers ----------------------------------------- private static final MetaObjectCache INSTANCE = new MetaObjectCache(); //~ Constructors ------------------------------------------------------- /** * Creates a new LazyInitialiser object. */ private LazyInitialiser() { } } }