/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.ha.cachemanager;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.management.JMException;
import javax.management.MBeanRegistration;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.Name;
import javax.naming.NameNotFoundException;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import org.jboss.cache.Cache;
import org.jboss.cache.CacheStatus;
import org.jboss.cache.config.Configuration;
import org.jboss.cache.config.ConfigurationRegistry;
import org.jboss.cache.jmx.CacheJmxWrapper;
import org.jboss.cache.notifications.annotation.CacheListener;
import org.jboss.cache.notifications.annotation.CacheStarted;
import org.jboss.cache.notifications.annotation.CacheStopped;
import org.jboss.cache.notifications.event.CacheStartedEvent;
import org.jboss.cache.notifications.event.CacheStoppedEvent;
import org.jboss.cache.pojo.PojoCache;
import org.jboss.cache.pojo.PojoCacheFactory;
import org.jboss.cache.pojo.jmx.PojoCacheJmxWrapper;
import org.jboss.ha.framework.server.CacheManagerLocator;
import org.jboss.ha.framework.server.PojoCacheManager;
import org.jboss.ha.framework.server.PojoCacheManagerLocator;
import org.jboss.logging.Logger;
import org.jboss.util.naming.NonSerializableFactory;
import org.jgroups.ChannelFactory;
/**
* JBoss AS specific {@link CacheManager}. Extends the core JBoss Cache
* cache manager by also handling, PojoCache, by registering created caches
* in JMX, and by registering itself in JNDI.
*
* @author <a href="brian.stansberry@jboss.com">Brian Stansberry</a>
* @version $Revision: 1.1 $
*/
public class CacheManager
extends org.jboss.cache.CacheManagerImpl
implements org.jboss.cache.CacheManager, PojoCacheManager, MBeanRegistration, CacheManagerMBean
{
private static final Logger log = Logger.getLogger(CacheManager.class);
public static final String DEFAULT_CORE_CACHE_JMX_ATTRIBUTES = "service=Cache,config=";
public static final String DEFAULT_POJO_CACHE_JMX_ATTRIBUTES = "service=Cache,cacheType=PojoCache,config=";
private MBeanServer mbeanServer;
private String jmxDomain;
private String coreCacheJmxAttributes = DEFAULT_CORE_CACHE_JMX_ATTRIBUTES;
private String pojoCacheJmxAttributes = DEFAULT_POJO_CACHE_JMX_ATTRIBUTES;
private Map<String, PojoCache> pojoCaches = new HashMap<String, PojoCache>();
private Map<String, Integer> pojoCacheCheckouts = new HashMap<String, Integer>();
private Map<String, String> configAliases = new HashMap<String, String>();
private boolean registerCachesInJmx = true;
private Map<String, Boolean> startupCaches = new HashMap<String, Boolean>();
private String jndiName;
private boolean started;
/**
* Create a new CacheManagerImpl.
*
*/
public CacheManager()
{
super();
}
/**
* Create a new CacheManagerImpl.
*
* @param configRegistry
* @param factory
*/
public CacheManager(ConfigurationRegistry configRegistry, ChannelFactory factory)
{
super(configRegistry, factory);
}
/**
* Create a new CacheManagerImpl.
*
* @param configFileName
* @param factory
*/
public CacheManager(String configFileName, ChannelFactory factory)
{
super(configFileName, factory);
}
// -------------------------------------------------------- PojoCacheManager
@SuppressWarnings("unchecked")
public Set<String> getConfigurationNames()
{
synchronized (pojoCaches)
{
Set<String> configNames = super.getConfigurationNames();
configNames.addAll(getPojoCacheNames());
configNames.addAll(configAliases.keySet());
return configNames;
}
}
public Set<String> getPojoCacheNames()
{
synchronized (pojoCaches)
{
return new HashSet<String>(pojoCaches.keySet());
}
}
public PojoCache getPojoCache(String configName, boolean create) throws Exception
{
// Check if there's an alias involved
configName = resolveAlias(configName);
PojoCache cache = null;
synchronized (pojoCaches)
{
if (getCacheNames().contains(configName))
throw new IllegalStateException("Cannot create PojoCache: plain cache already created for config " + configName);
cache = pojoCaches.get(configName);
if (cache == null && create)
{
Configuration config = getConfigurationRegistry().getConfiguration(configName);
if (getChannelFactory() != null && config.getMultiplexerStack() != null)
{
config.getRuntimeConfig().setMuxChannelFactory(getChannelFactory());
}
cache = createPojoCache(config);
registerPojoCache(cache, configName);
}
else if (cache != null)
{
incrementPojoCacheCheckout(configName);
}
}
// Wrap the pojocache to control classloading and disable stop/destroy
return cache == null ? null : new PojoCacheManagerManagedPojoCache(cache);
}
/**
* Extension point for subclasses, where we actually use a
* {@link PojoCacheFactory} to create a PojoCache.
*
* @param config the Configuration for the cache
* @return the PojoCache
*/
@SuppressWarnings("unchecked")
protected PojoCache createPojoCache(Configuration config)
{
return PojoCacheFactory.createCache(config, false);
}
public void registerPojoCache(PojoCache cache, String configName)
{
synchronized (pojoCaches)
{
if (pojoCaches.containsKey(configName) || getCacheNames().contains(configName))
throw new IllegalStateException(configName + " already registered");
pojoCaches.put(configName, cache);
incrementPojoCacheCheckout(configName);
if (registerCachesInJmx && mbeanServer != null)
{
String oName = getObjectName(getPojoCacheJmxAttributes(), configName);
PojoCacheJmxWrapper wrapper = new PojoCacheJmxWrapper(cache);
try
{
mbeanServer.registerMBean(wrapper, new ObjectName(oName));
}
catch (JMException e)
{
throw new RuntimeException("Cannot register cache under name " + oName, e);
}
// Synchronize the start/stop of the plain and pojo cache
cache.getCache().addCacheListener(new StartStopListener(wrapper));
}
}
}
// ------------------------------------------------------------- Overrides
@Override
public Cache<Object, Object> getCache(String configName, boolean create) throws Exception
{
// Check if there's an alias involved
configName = resolveAlias(configName);
synchronized (pojoCaches)
{
if (create && pojoCaches.containsKey(configName))
{
log.debug("Plain cache requested for config " + configName +
" but a PojoCache is already registered; returning " +
" the PojoCache's underlying plain cache");
PojoCache pc = getPojoCache(configName, false);
if (pc != null)
return wrapCache(pc.getCache());
}
return wrapCache(super.getCache(configName, create));
}
}
@Override
public void registerCache(Cache<Object, Object> cache, String configName)
{
synchronized (pojoCaches)
{
if (pojoCaches.containsKey(configName))
throw new IllegalStateException(configName + " already registered");
super.registerCache(cache, configName);
if (registerCachesInJmx && mbeanServer != null)
{
String oName = getObjectName(getCoreCacheJmxAttributes(), configName);
CacheJmxWrapper wrapper = new CacheJmxWrapper(cache);
try
{
mbeanServer.registerMBean(wrapper, new ObjectName(oName));
}
catch (JMException e)
{
throw new RuntimeException("Cannot register cache under name " + oName, e);
}
// Synchronize the start/stop of the plain and pojo cache
cache.addCacheListener(new StartStopListener(wrapper));
}
}
}
@Override
public void releaseCache(String configName)
{
// Check if there's an alias involved
configName = resolveAlias(configName);
synchronized (pojoCaches)
{
if (pojoCaches.containsKey(configName))
{
if (decrementPojoCacheCheckout(configName) == 0)
{
PojoCache cache = pojoCaches.remove(configName);
destroyPojoCache(configName, cache);
}
}
else
{
super.releaseCache(configName);
if (registerCachesInJmx && mbeanServer != null && !getCacheNames().contains(configName))
{
String oNameStr = getObjectName(getCoreCacheJmxAttributes(), configName);
try
{
ObjectName oName = new ObjectName(oNameStr);
if (mbeanServer.isRegistered(oName))
{
mbeanServer.unregisterMBean(oName);
}
}
catch (JMException e)
{
log.error("Problem unregistering CacheJmxWrapper " + oNameStr, e);
}
}
}
}
}
@Override
public void start() throws Exception
{
if (!started)
{
super.start();
startEagerStartCaches();
CacheManagerLocator locator = CacheManagerLocator.getCacheManagerLocator();
if (locator.getDirectlyRegisteredManager() == null)
locator.registerCacheManager(this);
PojoCacheManagerLocator pclocator = PojoCacheManagerLocator.getCacheManagerLocator();
if (pclocator.getDirectlyRegisteredManager() == null)
pclocator.registerCacheManager(this);
// Bind ourself in the public JNDI space if configured to do so
if (jndiName != null)
{
Context ctx = new InitialContext();
this.bind(jndiName, this, CacheManager.class, ctx);
log.debug("Bound in JNDI under " + jndiName);
}
started = true;
}
}
@Override
public void stop()
{
if (started)
{
releaseEagerStartCaches();
synchronized (pojoCaches)
{
for (Iterator<Map.Entry<String, PojoCache>> it = pojoCaches.entrySet().iterator(); it.hasNext();)
{
Map.Entry<String, PojoCache> entry = it.next();
destroyPojoCache(entry.getKey(), entry.getValue());
it.remove();
}
pojoCaches.clear();
pojoCacheCheckouts.clear();
}
super.stop();
if (jndiName != null)
{
InitialContext ctx = null;
try
{
// the following statement fails when the server is being shut down (07/19/2007)
ctx = new InitialContext();
ctx.unbind(jndiName);
}
catch (Exception e) {
log.error("partition unbind operation failed", e);
}
finally
{
if (ctx != null)
{
try
{
ctx.close();
}
catch (NamingException e)
{
log.error("Caught exception closing naming context", e);
}
}
}
try
{
NonSerializableFactory.unbind (jndiName);
}
catch (NameNotFoundException e)
{
log.error("Caught exception unbinding from NonSerializableFactory", e);
}
}
CacheManagerLocator locator = CacheManagerLocator.getCacheManagerLocator();
if (locator.getDirectlyRegisteredManager() == this)
locator.deregisterCacheManager();
PojoCacheManagerLocator pclocator = PojoCacheManagerLocator.getCacheManagerLocator();
if (pclocator.getDirectlyRegisteredManager() == this)
pclocator.deregisterCacheManager();
started = false;
}
}
// ------------------------------------------------------------- Properties
public String getJmxDomain()
{
return jmxDomain;
}
public void setJmxDomain(String jmxDomain)
{
this.jmxDomain = jmxDomain;
}
public String getCoreCacheJmxAttributes()
{
return coreCacheJmxAttributes;
}
public void setCoreCacheJmxAttributes(String coreCacheJmxAttributes)
{
this.coreCacheJmxAttributes = coreCacheJmxAttributes;
}
public String getPojoCacheJmxAttributes()
{
return pojoCacheJmxAttributes;
}
public void setPojoCacheJmxAttributes(String pojoCacheJmxAttributes)
{
this.pojoCacheJmxAttributes = pojoCacheJmxAttributes;
}
public boolean getRegisterCachesInJmx()
{
return registerCachesInJmx;
}
public void setRegisterCachesInJmx(boolean register)
{
this.registerCachesInJmx = register;
}
public String getJndiName()
{
return jndiName;
}
public void setJndiName(String jndiName)
{
this.jndiName = jndiName;
}
public Map<String, String> getConfigAliases()
{
synchronized (configAliases)
{
return new HashMap<String, String>(configAliases);
}
}
public void setConfigAliases(Map<String, String> aliases)
{
synchronized (configAliases)
{
configAliases.clear();
if (aliases != null)
configAliases.putAll(aliases);
}
}
public void setEagerStartCaches(Set<String> configNames)
{
if (configNames != null)
{
for (String name : configNames)
{
startupCaches.put(name, Boolean.FALSE);
}
}
}
public void setEagerStartPojoCaches(Set<String> configNames)
{
if (configNames != null)
{
for (String name : configNames)
{
startupCaches.put(name, Boolean.TRUE);
}
}
}
// ------------------------------------------------------ MBeanRegistration
public ObjectName preRegister(MBeanServer server, ObjectName name) throws Exception
{
this.mbeanServer = server;
if (jmxDomain == null)
{
jmxDomain = name.getDomain();
}
return name;
}
public void postDeregister()
{
// no-op
}
public void preDeregister() throws Exception
{
// TODO Auto-generated method stub
}
public void postRegister(Boolean registrationDone)
{
// no-op
}
// ---------------------------------------------------------------- Private
/**
* Wrap the pojocache to control classloading and disable stop/destroy
*/
private Cache wrapCache(Cache toWrap)
{
return toWrap == null ? null : new CacheManagerManagedCache(toWrap);
}
private int incrementPojoCacheCheckout(String configName)
{
synchronized (pojoCacheCheckouts)
{
Integer count = pojoCacheCheckouts.get(configName);
if (count == null)
count = new Integer(0);
Integer newVal = new Integer(count.intValue() + 1);
pojoCacheCheckouts.put(configName, newVal);
return newVal.intValue();
}
}
private int decrementPojoCacheCheckout(String configName)
{
synchronized (pojoCacheCheckouts)
{
Integer count = pojoCacheCheckouts.get(configName);
if (count == null || count.intValue() < 1)
throw new IllegalStateException("invalid count of " + count + " for " + configName);
Integer newVal = new Integer(count.intValue() - 1);
pojoCacheCheckouts.put(configName, newVal);
return newVal.intValue();
}
}
private void destroyPojoCache(String configName, PojoCache pojoCache)
{
Cache<Object, Object> cache = pojoCache.getCache();
if (cache.getCacheStatus() == CacheStatus.STARTED)
{
pojoCache.stop();
}
if (cache.getCacheStatus() != CacheStatus.DESTROYED && cache.getCacheStatus() != CacheStatus.INSTANTIATED)
{
pojoCache.destroy();
}
if (registerCachesInJmx && mbeanServer != null)
{
String oNameStr = getObjectName(getPojoCacheJmxAttributes(), configName);
try
{
ObjectName oName = new ObjectName(oNameStr);
if (mbeanServer.isRegistered(oName))
{
mbeanServer.unregisterMBean(oName);
}
}
catch (JMException e)
{
log.error("Problem unregistering PojoCacheJmxWrapper " + oNameStr, e);
}
}
}
private String getObjectName(String attributesBase, String configName)
{
String base = getJmxDomain() == null ? "" : getJmxDomain();
return base + ":" + attributesBase + configName;
}
@CacheListener
public static class StartStopListener
{
private final CacheJmxWrapper plainWrapper;
private final PojoCacheJmxWrapper pojoWrapper;
private StartStopListener(CacheJmxWrapper wrapper)
{
assert wrapper != null : "wrapper is null";
this.plainWrapper = wrapper;
this.pojoWrapper = null;
}
private StartStopListener(PojoCacheJmxWrapper wrapper)
{
assert wrapper != null : "wrapper is null";
this.pojoWrapper = wrapper;
this.plainWrapper = null;
}
@CacheStarted
public void cacheStarted(CacheStartedEvent event)
{
if (plainWrapper != null)
plainWrapper.start();
else
pojoWrapper.start();
}
@CacheStopped
public void cacheStopped(CacheStoppedEvent event)
{
if (plainWrapper != null)
plainWrapper.stop();
else
pojoWrapper.stop();
}
}
/**
* Helper method that binds the partition in the JNDI tree.
* @param jndiName Name under which the object must be bound
* @param who Object to bind in JNDI
* @param classType Class type under which should appear the bound object
* @param ctx Naming context under which we bind the object
* @throws Exception Thrown if a naming exception occurs during binding
*/
private void bind(String jndiName, Object who, Class classType, Context ctx) throws Exception
{
// Ah ! This service isn't serializable, so we use a helper class
//
NonSerializableFactory.bind(jndiName, who);
Name n = ctx.getNameParser("").parse(jndiName);
while (n.size () > 1)
{
String ctxName = n.get (0);
try
{
ctx = (Context)ctx.lookup (ctxName);
}
catch (NameNotFoundException e)
{
log.debug ("creating Subcontext " + ctxName);
ctx = ctx.createSubcontext (ctxName);
}
n = n.getSuffix (1);
}
// The helper class NonSerializableFactory uses address type nns, we go on to
// use the helper class to bind the service object in JNDI
//
StringRefAddr addr = new StringRefAddr("nns", jndiName);
Reference ref = new Reference(classType.getName (), addr, NonSerializableFactory.class.getName (), null);
ctx.rebind (n.get (0), ref);
}
private String resolveAlias(String configName)
{
String alias = configAliases.get(configName);
return alias == null ? configName : alias;
}
private void startEagerStartCaches() throws Exception
{
for (Map.Entry<String, Boolean> entry : startupCaches.entrySet())
{
Cache cache = null;
if (entry.getValue().booleanValue())
{
PojoCache pc = getPojoCache(entry.getKey(), true);
cache = pc.getCache();
}
else
{
cache = getCache(entry.getKey(), true);
}
if (cache.getCacheStatus() != CacheStatus.STARTED)
{
if (cache.getCacheStatus() != CacheStatus.CREATED)
{
cache.create();
}
cache.start();
}
}
}
private void releaseEagerStartCaches()
{
for (String name : startupCaches.keySet())
{
releaseCache(name);
}
}
}