Java tutorial
/********************************************************************************** * $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)); } }