org.sakaiproject.event.impl.NotificationCache.java Source code

Java tutorial

Introduction

Here is the source code for org.sakaiproject.event.impl.NotificationCache.java

Source

/**********************************************************************************
 * $URL: https://source.sakaiproject.org/svn/kernel/trunk/kernel-impl/src/main/java/org/sakaiproject/event/impl/NotificationCache.java $
 * $Id: NotificationCache.java 122028 2013-04-01 19:49:35Z azeckoski@unicon.net $
 ***********************************************************************************
 *
 * Copyright (c) 2003, 2004, 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.event.impl;

import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import java.util.Vector;

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.event.api.Notification;
import org.sakaiproject.memory.api.Cache;
import org.sakaiproject.memory.api.CacheRefresher;
import org.sakaiproject.memory.api.Cacher;
import org.sakaiproject.memory.api.MemoryService;

/**
 * <p>
 * A Cache of objects with keys with a limited lifespan.
 * </p>
 * <p>
 * When the object expires, the cache calls upon a CacheRefresher to update the key's value. The update is done in a separate thread.
 * </p>
 */
public class NotificationCache implements Cacher, Observer {
    /** Our logger. */
    private static Log M_log = LogFactory.getLog(NotificationCache.class);

    /** Map holding cached entries (by reference). */
    //   protected Map m_map = null;
    private Cache cache = null;

    /** Map of notification function to Set of notifications - same objects as in m_map. */
    protected Map m_functionMap = null;

    /** The object that will deal with expired entries. */
    protected CacheRefresher m_refresher = null;

    /** The string that all resources in this cache will start with. */
    protected String m_resourcePattern = null;

    /** If true, we are disabled. */
    protected boolean m_disabled = false;

    /** If true, we have all the entries that there are in the cache. */
    protected boolean m_complete = false;

    /** If true, we are going to hold any events we see in the m_heldEvents list for later processing. */
    protected boolean m_holdEventProcessing = false;
    private Object m_holdEventProcessingLock = new Object();

    /** The events we are holding for later processing. */
    protected List m_heldEvents = new Vector();

    protected EventTrackingService eventTrackingService;

    protected MemoryService memoryService;

    /**
     * Construct the Cache. Attempts to keep complete on Event notification by calling the refresher.
     * 
     * @param refresher
     *        The object that will handle refreshing of event notified modified or added entries.
     * @param pattern
     *        The "startsWith()" string for all resources that may be in this cache.
     */
    public NotificationCache(CacheRefresher refresher, String pattern, EventTrackingService eventTrackingService,
            MemoryService memoryService) {
        this.memoryService = memoryService;
        cache = memoryService.newCache("org.sakaiproject.event.api.NotificationService.cache", refresher, pattern);
        // TODO check logic with proxied class

        m_functionMap = new HashMap();

        m_refresher = refresher;
        m_resourcePattern = pattern;

        // register to get events - first, before others
        this.eventTrackingService = eventTrackingService;
        eventTrackingService.addPriorityObserver(this);

    } // NotificationCache

    /**
     * Clear all entries.
     */
    public synchronized void clear() {
        cache.clear();
        m_functionMap.clear();
        m_complete = false;

    } // clear

    /**
     * Test for an entry in the cache.
     * 
     * @param key
     *        The cache key.
     * @return true if the key maps to an entry, false if not.
     */
    public boolean containsKey(Object key) {
        return cache.containsKey(key);

    } // containsKey

    /**
     * Disable the cache.
     */
    public void disable() {
        m_disabled = true;
        eventTrackingService.deleteObserver(this);
        clear();

    } // disable

    /**
     * Is the cache disabled?
     * 
     * @return true if the cache is disabled, false if it is enabled.
     */
    public boolean disabled() {
        return m_disabled;

    } // disabled

    /**
     * Enable the cache.
     */
    public void enable() {
        m_disabled = false;
        eventTrackingService.addPriorityObserver(this);

    } // enable

    /**
     * Clean up.
     */
    protected void finalize() {
        // unregister as a cacher
        memoryService.unregisterCacher(this);

        // unregister to get events
        eventTrackingService.deleteObserver(this);

    } // finalize

    /**
     * Get the entry associated with the key, or null if not there
     * 
     * @param key
     *        The cache key.
     * @return The entry associated with the key, or null if the a null is cached, or the key is not found (Note: use containsKey() to remove this ambiguity).
     */
    public Notification get(Object key) {
        if (disabled())
            return null;

        return (Notification) cache.get(key);

    } // get

    /**
     * Get all the non-null entries.
     * 
     * @return all the non-null entries, or an empty list if none.
     */
    public List getAll() {
        List rv = new Vector();

        if (disabled())
            return rv;
        List allObjectsInCache = cache.getAll();
        for (Object object : allObjectsInCache) {
            if (object != null && object instanceof Notification)
                rv.add(object);
        }

        return rv;

    } // getAll

    /**
     * Get all the Notification entries that are watching this Event function.
     * 
     * @param function
     *        The function to use to select Notifications.
     * @return all the Notification entries that are watching this Event function.
     */
    public List getAll(String function) {
        return (List) m_functionMap.get(function);

    } // getAll

    /**
     * Return a description of the cacher.
     * 
     * @return The cacher's description.
     */
    public String getDescription() {
        StringBuilder buf = new StringBuilder();
        buf.append("NotificationCache:");
        if (m_disabled) {
            buf.append(" disabled:");
        }
        if (m_complete) {
            buf.append(" complete:");
        }
        if (m_resourcePattern != null) {
            buf.append(" pattern: " + m_resourcePattern);
        }
        if (m_refresher != null) {
            buf.append(" refresher: " + m_refresher.toString());
        }

        return buf.toString();
    }

    /**
     * Get all the keys, each modified to remove the resourcePattern prefix. Note: only works with String keys.
     * 
     * @return The List of keys converted from references to ids (String).
     */
    public List getIds() {
        //FIXME Need to create a separate cache for Notifications...
        return Collections.EMPTY_LIST;
        //      List rv = new Vector();
        //
        //      List keys = new Vector();
        //      keys.addAll(m_map.keySet());
        //
        //      Iterator it = keys.iterator();
        //      while (it.hasNext())
        //      {
        //         String key = (String) it.next();
        //         int i = key.indexOf(m_resourcePattern);
        //         if (i != -1) key = key.substring(i + m_resourcePattern.length());
        //         rv.add(key);
        //      }
        //
        //      return rv;

    } // getKeys

    /**
     * Get all the keys
     * 
     * @return The List of key values (Object).
     */
    public List getKeys() {
        return cache.getKeys();

    } // getKeys

    /**
     * Return the size of the cacher - indicating how much memory in use.
     * 
     * @return The size of the cacher.
     */
    public long getSize() {
        return cache.getSize();
    }

    /**
     * Are we complete?
     * 
     * @return true if we have all the possible entries cached, false if not.
     */
    public boolean isComplete() {
        if (disabled())
            return false;

        return m_complete;

    } // isComplete

    /**
     * Set the cache to hold events for later processing to assure an atomic "complete" load.
     */
    public synchronized void holdEvents() {
        m_holdEventProcessing = true;

    } // holdEvents

    /**
     * Restore normal event processing in the cache, and process any held events now.
     */
    public synchronized void processEvents() {
        m_holdEventProcessing = false;

        synchronized (m_heldEvents) {
            for (int i = 0; i < m_heldEvents.size(); i++) {
                Event event = (Event) m_heldEvents.get(i);
                continueUpdate(event);
            }

            m_heldEvents.clear();

        }

    } // holdEvents

    /**
     * Cache an object.
     * 
     * @param key
     *        The key with which to find the object.
     * @param payload
     *        The object to cache.
     * @param duration
     *        The time to cache the object (seconds).
     */
    public synchronized void put(Notification payload) {
        if (disabled() || payload == null)
            return;

        cache.put(payload.getReference(), payload);

        // put in m_functionMap, too, for each function of the notification
        List funcs = payload.getFunctions();
        for (Iterator iFuncs = funcs.iterator(); iFuncs.hasNext();) {
            String func = (String) iFuncs.next();

            List notifications = (List) m_functionMap.get(func);
            if (notifications == null) {
                notifications = new Vector();
                m_functionMap.put(func, notifications);
            }

            if (!notifications.contains(payload))
                notifications.add(payload);
        }

    } // cache

    /**
     * Remove this entry from the cache.
     * 
     * @param key
     *        The cache key.
     */
    public synchronized void remove(Object key) {
        if (disabled())
            return;

        Notification payload = (Notification) cache.get(key);
        cache.remove(key);

        if (payload == null)
            return;

        // remove it from the function map for each function
        List funcs = payload.getFunctions();
        for (Iterator iFuncs = funcs.iterator(); iFuncs.hasNext();) {
            String func = (String) iFuncs.next();

            List notifications = (List) m_functionMap.get(func);
            if (notifications != null) {
                notifications.remove(payload);
                if (notifications.isEmpty()) {
                    m_functionMap.remove(func);
                }
            }
        }

    } // remove

    /**
     * Clear out as much as possible anything cached; re-sync any cache that is needed to be kept.
     */
    public void resetCache() {
        clear();

    } // resetCache

    /**
     * Set the cache to be complete, containing all possible entries.
     */
    public void setComplete() {
        if (disabled())
            return;

        m_complete = true;

    } // isComplete

    /**
     * This method is called whenever the observed object is changed. An application calls an <tt>Observable</tt> object's <code>notifyObservers</code> method to have all the object's observers notified of the change. default implementation is to
     * cause the courier service to deliver to the interface controlled by my controller. Extensions can override.
     * 
     * @param o
     *        the observable object.
     * @param arg
     *        an argument passed to the <code>notifyObservers</code> method.
     */
    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;

        String key = event.getResource();

        // if this resource is not in my pattern of resources, we can ignore it
        if (key != null && !key.startsWith(m_resourcePattern))
            return;

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

        continueUpdate(event);

    } // update

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

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

        // do we have this in our cache?
        Object oldValue = get(key);
        if (cache.containsKey(key)) {
            // invalidate our copy
            remove(key);
        }

        // if we are being complete, we need to get this cached.
        if (m_complete) {
            // we can only get it cached if we have a refresher
            if (m_refresher != null) {
                // ask the refresher for the value
                Notification value = (Notification) m_refresher.refresh(key, oldValue, event);
                if (value != null) {
                    put(value);
                }
            } else {
                // we can no longer claim to be complete
                m_complete = false;
            }
        }

    } // continueUpdate
}