/**********************************************************************************
* $URL: https://source.sakaiproject.org/svn/kernel/trunk/kernel-impl/src/main/java/org/sakaiproject/memory/impl/BasicMemoryService.java $
* $Id: BasicMemoryService.java 129412 2013-09-06 17:38:55Z 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.memory.impl;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.UUID;
import net.sf.ehcache.CacheException;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Status;
import net.sf.ehcache.event.CacheManagerEventListener;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sakaiproject.authz.api.AuthzGroupService;
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.event.api.UsageSessionService;
import org.sakaiproject.memory.api.Cache;
import org.sakaiproject.memory.api.CacheRefresher;
import org.sakaiproject.memory.api.Cacher;
import org.sakaiproject.memory.api.GenericMultiRefCache;
import org.sakaiproject.memory.api.MemoryPermissionException;
import org.sakaiproject.memory.api.MemoryService;
import org.sakaiproject.memory.api.MultiRefCache;
import org.sakaiproject.memory.util.CacheInitializer;
/**
* <p>
* MemBasicMemoryServiceoryService 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
{
/** Our logger. */
private static Log M_log = LogFactory.getLog(BasicMemoryService.class);
/** 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";
/** 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
*********************************************************************************************************************************************************************************************************************************************************/
/**
* Configuration: cache verbose debug
*/
public void setCacheLogging(boolean value)
{
m_cacheLogging = value;
}
public boolean getCacheLogging()
{
return m_cacheLogging;
}
/**********************************************************************************************************************************************************************************************************************************************************
* 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
*********************************************************************************************************************************************************************************************************************************************************/
/**
* 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 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 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-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.
boolean override = false;
if (serverConfigurationService() != null) {
override = serverConfigurationService().getBoolean(
"memory.cache.statistics.force.disabled", false);
}
cache.setStatisticsEnabled(!override);
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 MultiRefCacheImpl(
this,
eventTrackingService(),
authzGroupService(),
instantiateCache(cacheName));
}
}