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

Java tutorial

Introduction

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

Source

/**********************************************************************************
 * $URL$
 * $Id$
 ***********************************************************************************
 *
 * 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.memory.impl;

import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Status;
import net.sf.ehcache.config.Configuration;
import net.sf.ehcache.event.CacheManagerEventListener;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sakaiproject.authz.api.SecurityService;
import org.sakaiproject.component.api.ServerConfigurationService;
import org.sakaiproject.component.cover.ComponentManager;
import org.sakaiproject.event.api.Event;
import org.sakaiproject.event.api.EventTrackingService;
import org.sakaiproject.memory.api.Cache;
import org.sakaiproject.memory.api.CacheRefresher;
import org.sakaiproject.memory.api.GenericMultiRefCache;
import org.sakaiproject.memory.api.MemoryService;
import org.sakaiproject.memory.util.CacheInitializer;

import java.lang.reflect.Field;
import java.util.*;

/**
 * <p>
 * BasicMemoryService is an implementation for the MemoryService which reports memory usage and runs a periodic garbage collection to keep memory available.
 * </p>
 */
public abstract class BasicMemoryService implements MemoryService, Observer {

    /** Event for the memory reset. */
    protected static final String EVENT_RESET = "memory.reset";
    /**
     * Event to expire members
     */
    protected static final String EVENT_EXPIRE = "memory.expire";
    /** Our logger. */
    private static Log M_log = LogFactory.getLog(BasicMemoryService.class);
    /** The underlying cache manager; injected */
    protected CacheManager cacheManager;

    /** If true, output verbose caching info. */
    protected boolean m_cacheLogging = false;

    /**********************************************************************************************************************************************************************************************************************************************************
     * Dependencies and their setter methods
     *********************************************************************************************************************************************************************************************************************************************************/

    /**
     * @return the EventTrackingService collaborator.
     */
    protected abstract EventTrackingService eventTrackingService();

    /**
     * @return the SecurityService collaborator.
     */
    protected abstract SecurityService securityService();

    /**
     * @return the UsageSessionService collaborator.
     */
    //protected abstract UsageSessionService usageSessionService();

    /**
     * @return the AuthzGroupService collaborator.
     */
    //protected abstract AuthzGroupService authzGroupService();

    /**
     * @return the ServerConfigurationService collaborator
     */
    protected abstract ServerConfigurationService serverConfigurationService();

    /**********************************************************************************************************************************************************************************************************************************************************
     * Configuration
     *********************************************************************************************************************************************************************************************************************************************************/

    public boolean getCacheLogging() {
        return m_cacheLogging;
    }

    /**
     * Configuration: cache verbose debug
     */
    public void setCacheLogging(boolean value) {
        m_cacheLogging = value;
    }

    /**********************************************************************************************************************************************************************************************************************************************************
     * Init and Destroy
     *********************************************************************************************************************************************************************************************************************************************************/

    /**
     * Final initialization, once all dependencies are set.
     */
    public void init() {
        try {
            // get notified of events to watch for a reset
            eventTrackingService().addObserver(this);

            M_log.info("init()");

            if (cacheManager == null)
                throw new IllegalStateException("CacheManager was not injected properly!");

            cacheManager.getCacheManagerEventListenerRegistry().registerListener(new CacheManagerEventListener() {

                private Status status = Status.STATUS_UNINITIALISED;

                public void dispose() throws CacheException {
                    status = Status.STATUS_SHUTDOWN;
                }

                public Status getStatus() {
                    return status;
                }

                public void init() throws CacheException {
                    status = Status.STATUS_ALIVE;
                }

                public void notifyCacheAdded(String name) {
                    Ehcache cache = cacheManager.getEhcache(name);
                    M_log.info("Added Cache name [" + name + "] as Cache [" + cache.getName() + "]");

                }

                public void notifyCacheRemoved(String name) {
                    M_log.info("Cache Removed " + name);

                }

            });
        } catch (Exception t) {
            M_log.warn("init(): ", t);
        }

    } // init

    /**
     * Returns to uninitialized state.
     */
    public void destroy() {
        // if we are not in a global shutdown, remove my event notification registration
        if (!ComponentManager.hasBeenClosed()) {
            eventTrackingService().deleteObserver(this);
        }

        cacheManager.clearAll();

        M_log.info("destroy()");
    }

    /**********************************************************************************************************************************************************************************************************************************************************
     * MemoryService implementation
     *********************************************************************************************************************************************************************************************************************************************************/

    @Override
    public ClassLoader getClassLoader() {
        return BasicMemoryService.class.getClassLoader();
    }

    @Override
    public Properties getProperties() {
        Configuration ec = cacheManager.getConfiguration();
        Properties p = new Properties();
        p.put("name", ec.getName());
        p.put("source", ec.getConfigurationSource().toString());
        p.put("timeoutSeconds", ec.getDefaultTransactionTimeoutInSeconds());
        p.put("maxBytesDisk", ec.getMaxBytesLocalDisk());
        p.put("maxBytesHeap", ec.getMaxBytesLocalHeap());
        p.put("maxDepth", ec.getSizeOfPolicyConfiguration().getMaxDepth());
        p.put("defaultCacheMaxEntries", ec.getDefaultCacheConfiguration().getMaxEntriesLocalHeap());
        p.put("defaultCacheTimeToIdleSecs", ec.getDefaultCacheConfiguration().getTimeToIdleSeconds());
        p.put("defaultCacheTimeToLiveSecs", ec.getDefaultCacheConfiguration().getTimeToLiveSeconds());
        p.put("defaultCacheEternal", ec.getDefaultCacheConfiguration().isEternal());
        return p;
    }

    @Override
    public <C extends org.sakaiproject.memory.api.Configuration> Cache createCache(String cacheName,
            C configuration) {
        M_log.warn("BasicMemoryService does not support cache creation configuration");
        return newCache(cacheName);
    }

    @Override
    public Cache getCache(String cacheName) {
        return newCache(cacheName);
    }

    @Override
    public Iterable<String> getCacheNames() {
        if (this.cacheManager != null) {
            String[] names = cacheManager.getCacheNames();
            return Arrays.asList(names);
        } else {
            return new ArrayList<String>(0);
        }
    }

    @Override
    public void destroyCache(String cacheName) {
        if (this.cacheManager != null) {
            this.cacheManager.removeCache(cacheName);
        }
    }

    @Override
    public <T> T unwrap(Class<T> clazz) {
        //noinspection unchecked
        return (T) cacheManager;
    }

    /**
    * Return the amount of available memory.
    * 
    * @return the amount of available memory.
    */
    public long getAvailableMemory() {
        return Runtime.getRuntime().freeMemory();

    } // getAvailableMemory

    /**
     * Cause less memory to be used by clearing any optional caches.
     */
    public void resetCachers() //throws MemoryPermissionException
    {
        // check that this is a "super" user with the security service
        if (!securityService().isSuperUser()) {
            // TODO: session id or session user id?
            throw new SecurityException("must be admin");//MemoryPermissionException(usageSessionService().getSessionId(), EVENT_RESET, "");
        }

        // post the event so this and any other app servers in the cluster will reset
        eventTrackingService().post(eventTrackingService().newEvent(EVENT_RESET, "", true));

    } // resetMemory

    public void evictExpiredMembers() {//throws MemoryPermissionException {
        // check that this is a "super" user with the security service
        if (!securityService().isSuperUser()) {
            // TODO: session id or session user id?
            throw new SecurityException("must be admin");//MemoryPermissionException(usageSessionService().getSessionId(), EVENT_EXPIRE, "");
        }

        // post the event so this and any other app servers in the cluster will reset
        eventTrackingService().post(eventTrackingService().newEvent(EVENT_EXPIRE, "", true));

    }

    /**
     * Compute a status report on all memory users
     */
    public String getStatus() {
        final StringBuilder buf = new StringBuilder();
        buf.append("** Memory report\n");
        buf.append("freeMemory: " + Runtime.getRuntime().freeMemory());
        buf.append(" totalMemory: ");
        buf.append(Runtime.getRuntime().totalMemory());
        buf.append(" maxMemory: ");
        buf.append(Runtime.getRuntime().maxMemory());
        buf.append("\n\n");

        List<Ehcache> allCaches = getAllCaches(true);

        // summary
        for (Ehcache cache : allCaches) {
            final long hits = cache.getStatistics().getCacheHits();
            final long misses = cache.getStatistics().getCacheMisses();
            final long total = hits + misses;
            final long hitRatio = ((total > 0) ? ((100l * hits) / total) : 0);
            // Even when we're not collecting statistics ehcache knows how
            // many objects are in the cache
            buf.append(cache.getName() + ": " + " count:" + cache.getStatistics().getObjectCount());
            if (cache.isStatisticsEnabled()) {
                buf.append(" hits:" + hits + " misses:" + misses + " hit%:" + hitRatio);
            } else {
                buf.append(" NO statistics (not enabled for cache)");
            }
            buf.append("\n");
        }

        // extended report
        buf.append("\n** Extended Cache Report\n");
        for (Object ehcache : allCaches) {
            buf.append(ehcache.toString());
            buf.append("\n");
        }

        // Iterator<Cacher> it = m_cachers.iterator();
        //      while (it.hasNext())
        //      {
        //         Cacher cacher = (Cacher) it.next();
        //         buf.append(cacher.getSize() + " in " + cacher.getDescription() + "\n");
        //      }

        final String rv = buf.toString();
        M_log.info(rv);

        return rv;
    }

    /**
     * Return all caches from the CacheManager
     * @param sorted Should the caches be sorted by name?
     * @return
     */
    private List<Ehcache> getAllCaches(boolean sorted) {
        M_log.debug("getAllCaches()");

        final String[] cacheNames = cacheManager.getCacheNames();
        if (sorted)
            Arrays.sort(cacheNames);
        final List<Ehcache> caches = new ArrayList<Ehcache>(cacheNames.length);
        for (String cacheName : cacheNames) {
            caches.add(cacheManager.getEhcache(cacheName));
        }
        return caches;
    }

    /**
     * Do a reset of all cachers
     */
    protected void doReset() {
        M_log.debug("doReset()");

        final List<Ehcache> allCaches = getAllCaches(false);
        for (Ehcache ehcache : allCaches) {
            ehcache.removeAll(); //TODO should we doNotNotifyCacheReplicators? Ian?
            ehcache.clearStatistics();
        }

        M_log.info("doReset():  Low Memory Recovery to: " + Runtime.getRuntime().freeMemory());

    } // doReset

    private void doExpire() {

        M_log.info(
                "doExpire():  About to evict expired elements free memory: " + Runtime.getRuntime().freeMemory());

        final List<Ehcache> allCaches = getAllCaches(false);
        for (Ehcache ehcache : allCaches) {
            ehcache.evictExpiredElements();

        }

        M_log.info("doExpire(): free memory now " + Runtime.getRuntime().freeMemory());

    }
    /**
     * Register as a cache user
     * @deprecated
     *//*
        synchronized public void registerCacher(Cacher cacher)
        {
        // not needed with ehcache
            
        } // registerCacher */

    /**
     * Unregister as a cache user
     * @deprecated
     *//*
        synchronized public void unregisterCacher(Cacher cacher)
        {
        // not needed with ehcache
            
        } // unregisterCacher */

    /**
     * {@inheritDoc}
     * @deprecated
     */
    public Cache newCache(CacheRefresher refresher, String pattern) {
        return new MemCache(this, eventTrackingService(), refresher, pattern, instantiateCache("MemCache"));
    }

    /**
     * {@inheritDoc}
     * @deprecated
     *//*
        public Cache newHardCache(CacheRefresher refresher, String pattern)
        {
        return new HardCache(this, eventTrackingService(), refresher, pattern,
            instantiateCache("HardCache"));
        } */

    /**
     * {@inheritDoc}
     * @deprecated
     *//*
        public Cache newHardCache(long sleep, String pattern)
        {
        return new HardCache(this, eventTrackingService(), sleep, pattern,
            instantiateCache("HardCache"));
        } */

    /**
     * {@inheritDoc}
     * @deprecated
     */
    public Cache newCache(CacheRefresher refresher, long sleep) {
        return new MemCache(this, eventTrackingService(), refresher, sleep, instantiateCache("MemCache"));
    }

    /**
     * {@inheritDoc}
     * @deprecated
     */
    public Cache newHardCache(CacheRefresher refresher, long sleep) {
        return new MemCache(this, eventTrackingService(), refresher, sleep, instantiateCache("HardCache"));
    }

    /**
     * {@inheritDoc}
     * @deprecated
     */
    public Cache newCache() {
        return new MemCache(this, eventTrackingService(), instantiateCache("MemCache"));
    }

    /**
     * {@inheritDoc}
     * @deprecated
     *//*
        public Cache newHardCache()
        {
        return new HardCache(this, eventTrackingService(),
            instantiateCache("HardCache"));
        } */

    /**
     * {@inheritDoc}
     * @deprecated
     *//*
        public MultiRefCache newMultiRefCache(long sleep)
        {
        return new MultiRefCacheImpl(
            this,
            eventTrackingService(),
            authzGroupService(),
            instantiateCache("MultiRefCache"));
        } */

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

    /**
     * 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) {
        // arg is Event
        if (!(arg instanceof Event))
            return;
        Event event = (Event) arg;

        // look for the memory reset event
        String function = event.getEvent();
        if (EVENT_RESET.equals(function)) {
            // do the reset
            doReset();
        } else if (EVENT_EXPIRE.equals(function)) {
            doExpire();
        }
    }

    /**
     * 
     * @param cacheName
     * @param legacyMode
     *            If true always create a new Cache. If false, cache must be
     *            defined in bean factory.
     * @return
     */
    private Ehcache instantiateCache(String cacheName) {
        if (M_log.isDebugEnabled())
            M_log.debug("createNewCache(String " + cacheName + ")");

        String name = cacheName;
        if (name == null || "".equals(name)) {
            name = "DefaultCache" + UUID.randomUUID().toString();
        }

        // Cache creation should all go to the cache manager and be
        // configured via the cache manager setup.

        if (cacheManager.cacheExists(name)) {
            return cacheManager.getEhcache(name);
        }

        Ehcache cache = null;

        try {
            Ehcache defaultCache = getDefaultCache();
            if (defaultCache != null) {
                cache = (Ehcache) defaultCache.clone();
                cache.setName(cacheName);

                // Not look for any custom configuration.
                // Check for old configuration properties.
                if (serverConfigurationService().getString(name) == null) {
                    M_log.warn("Old cache configuration " + name + " must be changed to memory." + name);
                }
                String config = serverConfigurationService().getString("memory." + name);
                if (config != null && config.length() > 0) {
                    M_log.debug("Found configuration for cache: " + name + " of: " + config);
                    new CacheInitializer().configure(config).initialize(cache.getCacheConfiguration());
                }

                cacheManager.addCache(cache);
            }
        } catch (Exception ex) {
            M_log.warn("Unable to access or close default cache", ex);
        }

        if (cache == null) {
            cacheManager.addCache(name);
            cache = cacheManager.getEhcache(name);
        }

        // KNL-1292: do not set if the cache is not yet init'ed
        if (cache != null && cache.getStatus().equals(Status.STATUS_ALIVE)) {
            //KNL-532 - Upgraded Ehcache 2.5.1 (2.1.0+) defaults to no stats collection.
            //We may choose to allow configuration per-cache for performance tuning.
            //For now, we default everything to on, while this property allows a system-wide override.
            cache.setStatisticsEnabled(
                    !(serverConfigurationService().getBoolean("memory.cache.statistics.force.disabled", false)));
        }

        return cache;

        /*
            
            
        if(legacyMode)
        {
           if (cacheManager.cacheExists(name)) {
        M_log.warn("Cache already exists and is bound to CacheManager; creating new cache from defaults: "
              + name);
        // favor creation of new caches for backwards compatibility
        // in the future, it seems like you would want to return the same
        // cache if it already exists
        name = name + UUID.randomUUID().toString();
           }
        }
            
        Ehcache cache = null;
            
        // try to locate a named cache in the bean factory
        try {
           cache = (Ehcache) ComponentManager.get(name);
        } catch (Exception e) {
           cache = null;
           M_log.error("Error occurred when trying to load cache from bean factory!", e);
        }
            
            
        if(cache != null) // found the cache
        {
           M_log.info("Loaded Named Cache " + cache);
            
           return cache;
        }
        else // did not find the cache
        {
           if(legacyMode)
           {
        cacheManager.addCache(name); // create a new cache
        cache = cacheManager.getEhcache(name);
        M_log.info("Loaded Default Cache " + cache);            
           }
           else
           {
        M_log.error("Could not find named cache in the bean factory!:"
                    + name);
           }
            
           return cache;         
        }
        */
    }

    private Ehcache getDefaultCache()
            throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
        Field defaultCacheField = CacheManager.class.getDeclaredField("defaultCache");
        defaultCacheField.setAccessible(true);
        return (Ehcache) defaultCacheField.get(cacheManager);
    }

    public void setCacheManager(net.sf.ehcache.CacheManager cacheManager) {
        this.cacheManager = cacheManager;
    }

    public Cache newCache(String cacheName, CacheRefresher refresher, String pattern) {
        return new MemCache(this, eventTrackingService(), refresher, pattern, instantiateCache(cacheName));
    }

    public Cache newCache(String cacheName, String pattern) {
        return new MemCache(this, eventTrackingService(), pattern, instantiateCache(cacheName));
    }

    public Cache newCache(String cacheName, CacheRefresher refresher) {
        return new MemCache(this, eventTrackingService(), refresher, instantiateCache(cacheName));
    }

    public Cache newCache(String cacheName) {
        return new MemCache(this, eventTrackingService(), instantiateCache(cacheName));
    }

    /*
       public MultiRefCache newMultiRefCache(String cacheName) {
          return new MultiRefCacheImpl(
        this,
        eventTrackingService(),
        authzGroupService(),
        instantiateCache(cacheName));
       }
    */
    public GenericMultiRefCache newGenericMultiRefCache(String cacheName) {
        return new GenericMultiRefCacheImpl(this, eventTrackingService(), instantiateCache(cacheName));
    }

}