org.sakaiproject.memory.impl.GenericMultiRefCacheImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.sakaiproject.memory.impl.GenericMultiRefCacheImpl.java

Source

/**********************************************************************************
 * $URL: https://source.sakaiproject.org/svn/kernel/trunk/kernel-impl/src/main/java/org/sakaiproject/memory/impl/MultiRefCacheImpl.java $
 * $Id: MultiRefCacheImpl.java 68286 2009-10-27 07:49:08Z david.horwitz@uct.ac.za $
 ***********************************************************************************
 *
 * Copyright (c) 2005, 2006, 2007, 2008 Sakai Foundation
 *
 * Licensed under the Educational Community License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.opensource.org/licenses/ECL-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 **********************************************************************************/

package org.sakaiproject.memory.impl;

import net.sf.ehcache.CacheException;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import net.sf.ehcache.event.CacheEventListener;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sakaiproject.event.api.Event;
import org.sakaiproject.event.api.EventTrackingService;
import org.sakaiproject.memory.api.GenericMultiRefCache;

import java.io.Serializable;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * <p>
 * MultiRefCacheImpl implements the MultiRefCache.
 * </p>
 * <p>
 * The references that each cache entry are sensitive to are kept in a separate map for easy access.<br />
 * Manipulation of this map is synchronized. This map is not used for cache access, just when items are added and removed.<br />
 * The cache map itself becomes synchronized when it's manipulated (not when reads occur), so this added sync. for the refs fits the existing pattern.
 * </p>
 * @deprecated as of Sakai 2.9, this should no longer be used and should be removed in Sakai 11
 */
public class GenericMultiRefCacheImpl extends MemCache implements GenericMultiRefCache, CacheEventListener {
    /** Our logger. */
    private static Log M_log = LogFactory.getLog(GenericMultiRefCacheImpl.class);

    /** Map of reference string -> Collection of cache keys. */
    protected final ConcurrentMap<String, ConcurrentMap<Object, Object>> m_refsStore = new ConcurrentHashMap<String, ConcurrentMap<Object, Object>>();

    /**
     * Construct the Cache - checks for expiration periodically.
     */
    public GenericMultiRefCacheImpl(BasicMemoryService memoryService, EventTrackingService eventTrackingService,
            Ehcache cache) {
        super(memoryService, eventTrackingService, "", cache);
        cache.getCacheEventNotificationService().registerListener(this);

    }

    public void put(String key, Object payload, String ref, Collection dependRefs) {
        if (M_log.isDebugEnabled()) {
            M_log.debug("put(Object " + key + ", Object " + payload + ", Reference " + ref + ", Dependent Refs "
                    + dependRefs + ")");
        }
        if (disabled())
            return;
        // Durations don't work any more (hence 0 duration).
        super.put(key, new MultiRefCacheEntry(payload, 0, ref, dependRefs));

        // Why don't we do do this in the notify handler?
        if (ref != null) {
            addRefCachedKey(ref, key);
        }
        if (dependRefs != null) {
            for (Iterator<Object> i = dependRefs.iterator(); i.hasNext();) {
                String dependRef = (String) i.next();
                addRefCachedKey(dependRef, key);
            }
        }
        if (mrcDebug)
            logCacheState("put(" + key + ")");
    }

    /**
     * @inheritDoc
     */
    public void put(String key, Object payload, int duration) {
        put(key, payload, null, null);
    }

    /**
     * @inheritDoc
     */
    public void put(String key, Object payload) {
        put(key, payload, null, null);
    }

    /**
     * Make sure there's an entry in refs for this ref that includes this key.
     *
     * @param ref
     *        The entity reference string.
     * @param key
     *        The cache entry key dependent on this entity ref.
     */
    protected void addRefCachedKey(String ref, Object key) {
        // Isn't this a threading issue. Two thread hit this and both put their own data into m_refsStore.
        ConcurrentMap<Object, Object> cachedKeys = new ConcurrentHashMap<Object, Object>();
        ConcurrentMap<Object, Object> oldCachedKeys = m_refsStore.putIfAbsent(ref, cachedKeys);
        if (oldCachedKeys != null) {
            cachedKeys = oldCachedKeys;
        }
        cachedKeys.put(key, key);
    }

    /**
     * @inheritDoc
     */
    public void clear() {
        super.clear();
        m_refsStore.clear();
    }

    private void cleanEntityReferences(Object key, Object value) {
        if (M_log.isDebugEnabled())
            M_log.debug("cleanEntityReferences(Object " + key + ", Object " + value + ")");
        if (value == null)
            return;

        final MultiRefCacheEntry cachedEntry = (MultiRefCacheEntry) value;

        // remove this key from any of the entity references in m_refs that are dependent on this entry
        for (Iterator iRefs = cachedEntry.getRefs().iterator(); iRefs.hasNext();) {
            String ref = (String) iRefs.next();
            ConcurrentMap<Object, Object> keys = m_refsStore.get(ref);
            if (keys != null && keys.remove(key) != null) {
                // remove the ref entry if it no longer has any cached keys in
                // its collection
                // TODO This isn't thread safe.
                if (keys.isEmpty()) {
                    m_refsStore.remove(ref, keys);
                }
            }
        }
        if (mrcDebug)
            logCacheState("cleanEntityReferences(" + key + ")");
    }

    /**
     * @inheritDoc
     */
    public String getDescription() {
        return "GenericMultiRefCache: " + super.getDescription();
    }

    /**********************************************************************************************************************************************************************************************************************************************************
     * Cacher implementation
     *********************************************************************************************************************************************************************************************************************************************************/

    /**
     * @inheritDoc
     */
    public void update(Observable o, Object arg) {
        if (disabled())
            return;

        // arg is Event
        if (!(arg instanceof Event))
            return;
        Event event = (Event) arg;

        // if this is just a read, not a modify event, we can ignore it
        if (!event.getModify())
            return;

        // if we are holding event processing
        if (m_holdEventProcessing) {
            m_heldEvents.add(event);
            return;
        }

        continueUpdate(event);
    }

    /**********************************************************************************************************************************************************************************************************************************************************
     * Observer implementation
     *********************************************************************************************************************************************************************************************************************************************************/

    /**
     * Complete the update, given an event that we know we need to act upon.
     *
     * @param event
     *        The event to process.
     */
    protected void continueUpdate(Event event) {
        String ref = event.getResource();

        if (M_log.isDebugEnabled())
            M_log.debug("continueUpdate() [" + m_resourcePattern + "] resource: " + ref + " event: "
                    + event.getEvent());

        // get the copy of the Collection of cache keys for this reference (the actual collection will be reduced as the removes occur)
        ConcurrentMap<Object, Object> cachedKeys = m_refsStore.get(ref);
        if (cachedKeys != null) {
            Set<Object> keySet = cachedKeys.keySet();
            for (Iterator<Object> iKeys = keySet.iterator(); iKeys.hasNext();) {
                Object key = iKeys.next();
                remove(String.valueOf(key));

                if (M_log.isDebugEnabled()) {
                    M_log.debug("Removed from cache: " + key);
                }
            }
        }
    }

    /**
     * @inheritDoc
     */
    public boolean isComplete() {
        // we do not support being complete
        return false;
    }

    /**
     * @inheritDoc
     */
    public boolean isComplete(String path) {
        // we do not support being complete
        return false;
    }

    /**
     * @inheritDoc
     * @see org.sakaiproject.memory.impl.MemCache#get(java.lang.Object)
     */
    @Override
    public Object get(String key) {
        MultiRefCacheEntry mrce = (MultiRefCacheEntry) super.get(key);
        return (mrce != null ? mrce.getPayload(key) : null);
    }

    public void dispose() {
        M_log.debug("dispose()");
        // may not be necessary...
        m_refsStore.clear();
    }

    //////////////////////////////////////////////////////////////////////
    //  CacheEventListener methods. Cleanup HashMap of m_refs on eviction.
    //////////////////////////////////////////////////////////////////////

    public void notifyElementEvicted(Ehcache cache, Element element) {
        cleanEntityReferences(element.getObjectKey(), element.getObjectValue());
    }

    public void notifyElementExpired(Ehcache cache, Element element) {
        cleanEntityReferences(element.getObjectKey(), element.getObjectValue());
    }

    public void notifyElementPut(Ehcache cache, Element element) throws CacheException {
        // do nothing...

    }

    public void notifyElementRemoved(Ehcache cache, Element element) throws CacheException {
        cleanEntityReferences(element.getObjectKey(), element.getObjectValue());
    }

    public void notifyElementUpdated(Ehcache cache, Element element) throws CacheException {
        // do nothing...

    }

    public void notifyRemoveAll(Ehcache cache) {
        m_refsStore.clear();
    }

    /**
     * @see CacheEventListener#clone()
     */
    @Override
    public Object clone() throws CloneNotSupportedException {
        M_log.debug("clone()");

        // Creates a clone of this listener. This method will only be called by ehcache before a cache is initialized.
        // This may not be possible for listeners after they have been initialized. Implementations should throw CloneNotSupportedException if they do not support clone.
        throw new CloneNotSupportedException(
                "CacheEventListener implementations should throw CloneNotSupportedException if they do not support clone");
    }

    @Override
    public Properties getProperties(boolean includeExpensiveDetails) {
        Properties p = super.getProperties(includeExpensiveDetails);
        p.put("class", this.getClass().getSimpleName());
        p.put("refsCount", m_refsStore.size());
        return p;
    }

    // Added for KNL-1162

    protected class MultiRefCacheEntry extends CacheEntry implements Serializable {

        /**
         * The serial version UID
         */
        private static final long serialVersionUID = -4888170965591332845L;
        /** These are the entity reference strings that this entry is sensitive to. */
        protected List<Object> m_refs = new CopyOnWriteArrayList<Object>();

        /**
         * Construct to cache the payload for the duration.
         *
         * @param payload
         *        The thing to cache.
         * @param duration
         *        The time (seconds) to keep this cached.
         * @param ref
         *        One entity reference that, if changed, will invalidate this entry.
         * @param dependRefs
         *        References that, if the changed, will invalidate this entry.
         */
        public MultiRefCacheEntry(Object payload, int duration, String ref, Collection<Object> dependRefs) {
            super(payload, duration);
            if (ref != null)
                m_refs.add(ref);
            if (dependRefs != null)
                m_refs.addAll(dependRefs);
        }

        /**
         * @inheritDoc
         */
        public List<Object> getRefs() {
            return m_refs;
        }

        @Override
        public String toString() {
            return "MRCE[" + this.get() + ",refs(" + (this.m_refs != null ? this.m_refs.size() : "--") + ")="
                    + this.m_refs + "]";
        }
    }

    // KNL-1230 added to assist with debugging caching issues
    boolean mrcDebug = false; // always set to false in committed code
    boolean mrcDebugDetailed = false;

    void logCacheState(String operator) {
        if (mrcDebug) {
            String name = this.cache.getName();
            StringBuilder refsSB = new StringBuilder();
            refsSB.append("   * keys(").append(m_refsStore.keySet().size()).append("):")
                    .append(m_refsStore.keySet()).append("\n");
            int countRefs = 0;
            for (Map.Entry<String, ConcurrentMap<Object, Object>> entry : m_refsStore.entrySet()) {
                if (entry == null)
                    continue;
                int count = 0;
                if (entry.getValue() != null) {
                    count = entry.getValue().size();
                }
                countRefs += count;
                if (mrcDebugDetailed) {
                    refsSB.append("   ").append(entry.getKey()).append(" => (").append(count).append(")")
                            .append(entry.getValue()).append("\n");
                }
            }
            StringBuilder entriesSB = new StringBuilder();
            List keys = this.cache.getKeys();
            entriesSB.append("   * keys(").append(keys.size()).append("):").append(new ArrayList<Object>(keys))
                    .append("\n");
            Collection<Element> entries = this.cache.getAll(keys).values();
            int countMaps = 0;
            for (Element element : entries) {
                if (element == null)
                    continue;
                int count = 0;
                if (element.getObjectValue() != null && element.getObjectValue() instanceof MultiRefCacheEntry) {
                    count = ((MultiRefCacheEntry) element.getObjectValue()).getRefs().size();
                }
                countMaps += count;
                if (mrcDebugDetailed) {
                    entriesSB.append("   ").append(element.getObjectKey()).append(" => (").append(count).append(")")
                            .append(element.getObjectValue()).append("\n");
                }
            }
            M_log.info("MRC:" + name + ":: " + operator + " ::\n  refsStore(Map[key => value]," + m_refsStore.size()
                    + " + " + countRefs + " = " + (m_refsStore.size() + countRefs) + "):\n" + refsSB
                    + "\n  entries(Ehcache[key => payload]," + keys.size() + " + " + countMaps + " = "
                    + (keys.size() + countMaps) + "):\n" + entriesSB);
        }
    }
}