/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt 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.framework.server;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jboss.cache.Cache;
import org.jboss.cache.CacheException;
import org.jboss.cache.Fqn;
import org.jboss.cache.Node;
import org.jboss.cache.config.Configuration.CacheMode;
import org.jboss.cache.notifications.annotation.CacheListener;
import org.jboss.cache.notifications.annotation.NodeModified;
import org.jboss.cache.notifications.event.NodeModifiedEvent;
import org.jboss.cache.notifications.event.NodeModifiedEvent.ModificationType;
import org.jboss.ha.core.framework.server.ChannelSource;
import org.jboss.ha.framework.server.spi.ManagedDistributedState;
import org.jboss.logging.Logger;
import org.jgroups.Channel;
/**
* This class manages distributed state across the cluster.
*
* @author <a href="mailto:sacha.labourey@cogito-info.ch">Sacha Labourey</a>.
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>.
* @author Scott.Stark@jboss.org
* @version $Revision:77673 $
*/
@CacheListener
public class DistributedStateImpl
implements ManagedDistributedState, DistributedStateImplMBean, ChannelSource
{
// Constants -----------------------------------------------------
public static final Serializable ROOT = "__DISTRIBUTED_STATE__";
public static final Fqn<Serializable> ROOTFQN = Fqn.fromElements(ROOT);
public static final int ROOTFQNSIZE = ROOTFQN.size();
protected static final String SERVICE_NAME = "DistributedState";
// Attributes ----------------------------------------------------
protected Map<String, List<Object>> keyListeners = new HashMap<String, List<Object>>();
protected Logger log = Logger.getLogger(this.getClass());
protected String name = null;
protected Cache<Serializable, Serializable> cache;
protected boolean replAsync;
protected HAPartitionCacheHandlerImpl cacheHandler;
protected boolean acquiredCache = false;
// Public --------------------------------------------------------
public void createService() throws Exception
{
}
public void startService() throws Exception
{
if (this.cache == null)
{
if (cacheHandler == null)
{
throw new IllegalStateException("No clustered cache available");
}
@SuppressWarnings("unchecked")
Cache c = null;
synchronized (cacheHandler)
{
c = cacheHandler.getCache();
if (c == null)
{
cacheHandler.acquireCache();
c = cacheHandler.getCache();
acquiredCache = true;
}
}
cacheHandler.startCache();
@SuppressWarnings("unchecked")
Cache<Serializable, Serializable> unchecked = c;
internalSetClusteredCache(unchecked);
}
this.cache.addCacheListener(this);
}
public void stopService() throws Exception
{
this.cache.removeCacheListener(this);
if (acquiredCache)
{
cacheHandler.releaseCache();
cache = null;
acquiredCache = false;
}
}
public void destroyService() throws Exception
{
}
public String listContent() throws Exception
{
StringBuilder result = new StringBuilder();
Collection<String> cats = this.getAllCategories();
if (cats == null) return result.toString();
Iterator<String> catsIter = cats.iterator();
while (catsIter.hasNext())
{
String category = (String) catsIter.next();
Iterator<Serializable> keysIter = this.getAllKeys(category).iterator();
result.append("-----------------------------------------------\n");
result.append("Logger : ").append(category).append("\n\n");
result.append("KEY\t:\tVALUE\n");
while (keysIter.hasNext())
{
Serializable key = (Serializable) keysIter.next();
String value = this.get(category, key).toString();
result.append("'").append(key);
result.append("'\t:\t'");
result.append(value);
result.append("'\n");
}
result.append("\n");
}
return result.toString();
}
public String listXmlContent() throws Exception
{
StringBuilder result = new StringBuilder();
result.append("<DistributedState>\n");
Collection<String> cats = this.getAllCategories();
if (cats != null)
{
Iterator<String> catsIter = cats.iterator();
while (catsIter.hasNext())
{
String category = (String) catsIter.next();
Iterator<Serializable> keysIter = this.getAllKeys(category).iterator();
result.append("\t<Logger>\n");
result.append("\t\t<LoggerName>").append(category).append("</LoggerName>\n");
while (keysIter.hasNext())
{
Serializable key = (Serializable) keysIter.next();
String value = this.get(category, key).toString();
result.append("\t\t<Entry>\n");
result.append("\t\t\t<Key>").append(key).append("</Key>\n");
result.append("\t\t\t<Value>").append(value).append("</Value>\n");
result.append("\t\t</Entry>\n");
}
result.append("\t</Logger>\n");
}
}
result.append("</DistributedState>\n");
return result.toString();
}
public Cache<Serializable, Serializable> getClusteredCache()
{
return this.cache;
}
/**
* Sets the cache to use.
*
* @param cache the cache
*
* @throws IllegalStateException if the cache isn't configured for replication
*/
public void setClusteredCache(Cache<Serializable, Serializable> cache)
{
internalSetClusteredCache(cache);
acquiredCache = false;
}
public HAPartitionCacheHandlerImpl getCacheHandler()
{
return cacheHandler;
}
public void setCacheHandler(HAPartitionCacheHandlerImpl cacheHandler)
{
this.cacheHandler = cacheHandler;
}
// DistributedState implementation ----------------------------------------------
/*
* (non-Javadoc)
*
* @see org.jboss.ha.framework.interfaces.DistributedState#set(java.lang.String,
* java.io.Serializable, java.io.Serializable)
*/
public void set(String category, Serializable key, Serializable value) throws Exception
{
this.set(category, key, value, true);
}
/*
* (non-Javadoc)
*
* @see org.jboss.ha.framework.interfaces.DistributedState#set(java.lang.String,
* java.io.Serializable, java.io.Serializable, boolean) @param
* asynchronousCall is not supported yet. TreeCache cannot switch this
* on the fly. Will take value from TreeCache-config instead.
*/
public void set(String category, Serializable key, Serializable value, boolean asynchronousCall) throws Exception
{
if (this.replAsync != asynchronousCall)
{
if (asynchronousCall)
{
this.cache.getInvocationContext().getOptionOverrides().setForceAsynchronous(true);
}
else
{
this.cache.getInvocationContext().getOptionOverrides().setForceSynchronous(true);
}
}
this.cache.put(this.buildFqn(category), key, value);
}
/*
* (non-Javadoc)
*
* @see org.jboss.ha.framework.interfaces.DistributedState#remove(java.lang.String,
* java.io.Serializable) @return - returns null in case of
* CacheException
*/
public Serializable remove(String category, Serializable key) throws Exception
{
return this.remove(category, key, true);
}
/*
* (non-Javadoc)
*
* @see org.jboss.ha.framework.interfaces.DistributedState#remove(java.lang.String,
* java.io.Serializable, boolean)
*/
public Serializable remove(String category, Serializable key, boolean asynchronousCall) throws Exception
{
Serializable retVal = this.get(category, key);
if (retVal != null)
{
if (this.replAsync != asynchronousCall)
{
if (asynchronousCall)
{
this.cache.getInvocationContext().getOptionOverrides().setForceAsynchronous(true);
}
else
{
this.cache.getInvocationContext().getOptionOverrides().setForceSynchronous(true);
}
}
this.cache.remove(this.buildFqn(category), key);
}
return retVal;
}
/*
* (non-Javadoc)
*
* @see org.jboss.ha.framework.interfaces.DistributedState#get(java.lang.String,
* java.io.Serializable)
*/
public Serializable get(String category, Serializable key)
{
try
{
return (Serializable) this.cache.get(this.buildFqn(category), key);
}
catch (CacheException ce)
{
return null;
}
}
public Collection<String> getAllCategories()
{
try
{
@SuppressWarnings("unchecked")
Node base = this.cache.getRoot().getChild(ROOTFQN);
@SuppressWarnings("unchecked")
Collection<String> keys = (base == null ? null : base.getChildrenNames());
if (keys != null && keys.size() > 0)
{
keys = Collections.unmodifiableCollection(keys);
}
return keys;
}
catch (CacheException ce)
{
return null;
}
}
/*
* (non-Javadoc)
*
* @see org.jboss.ha.framework.interfaces.DistributedState#getAllKeys(java.lang.String)
* @return - returns null in case of CacheException
*/
public Collection<Serializable> getAllKeys(String category)
{
try
{
Node<Serializable, Serializable> node = this.getNode(category);
if (node == null) return null;
return node.getKeys();
}
catch (CacheException ce)
{
return null;
}
}
/*
* (non-Javadoc)
*
* @see org.jboss.ha.framework.interfaces.DistributedState#getAllValues(java.lang.String)
* @return - returns null in case of CacheException
*/
public Collection<Serializable> getAllValues(String category)
{
try
{
Node<Serializable, Serializable> categoryNode = this.getNode(category);
if (categoryNode == null) return null;
Set<Serializable> childNodes = categoryNode.getKeys();
if (childNodes == null) return null;
Map<Serializable, Serializable> entries = categoryNode.getData();
if (entries == null) return null;
Collection<Serializable> retVal = new HashSet<Serializable>(entries.values());
return Collections.unmodifiableCollection(retVal);
}
catch (CacheException ce)
{
return null;
}
}
@SuppressWarnings("deprecation")
public void registerDSListenerEx(String category, DSListenerEx subscriber)
{
this.registerListener(category, subscriber);
}
@SuppressWarnings("deprecation")
public void unregisterDSListenerEx(String category, DSListenerEx subscriber)
{
this.unregisterListener(category, subscriber);
}
@SuppressWarnings("deprecation")
public void registerDSListener(String category, DSListener subscriber)
{
this.registerListener(category, subscriber);
}
@SuppressWarnings("deprecation")
public void unregisterDSListener(String category, DSListener subscriber)
{
this.unregisterListener(category, subscriber);
}
// ChannelSource -------------------------------------------------
public Channel getChannel()
{
Channel result = null;
if (cache != null)
{
result = cache.getConfiguration().getRuntimeConfig().getChannel();
}
return result;
}
// Package protected ---------------------------------------------
// Protected -----------------------------------------------------
protected void registerListener(String category, Object subscriber)
{
synchronized (this.keyListeners)
{
List<Object> listeners = this.keyListeners.get(category);
if (listeners == null)
{
listeners = new ArrayList<Object>();
this.keyListeners.put(category, listeners);
}
listeners.add(subscriber);
}
}
protected void unregisterListener(String category, Object subscriber)
{
synchronized (this.keyListeners)
{
List<Object> listeners = this.keyListeners.get(category);
if (listeners == null) return;
listeners.remove(subscriber);
if (listeners.size() == 0)
{
this.keyListeners.remove(category);
}
}
}
@SuppressWarnings("deprecation")
protected void notifyKeyListeners(String category, Serializable key, Serializable value, boolean locallyModified)
{
synchronized (this.keyListeners)
{
List<Object> listeners = this.keyListeners.get(category);
if (listeners == null) return;
String strKey = key.toString();
for (Object listener: listeners)
{
if (listener instanceof DSListener)
{
DSListener dslistener = (DSListener) listener;
dslistener.valueHasChanged(category, strKey, value, locallyModified);
}
else
{
DSListenerEx dslistener = (DSListenerEx) listener;
dslistener.valueHasChanged(category, key, value, locallyModified);
}
}
}
}
@SuppressWarnings("deprecation")
protected void notifyKeyListenersOfRemove(String category, Serializable key, Serializable oldContent,
boolean locallyModified)
{
synchronized (this.keyListeners)
{
List<Object> listeners = this.keyListeners.get(category);
if (listeners == null) return;
String strKey = key.toString();
for (Object listener: listeners)
{
if (listener instanceof DSListener)
{
DSListener dslistener = (DSListener) listener;
dslistener.keyHasBeenRemoved(category, strKey, oldContent, locallyModified);
}
else
{
DSListenerEx dslistener = (DSListenerEx) listener;
dslistener.keyHasBeenRemoved(category, key, oldContent, locallyModified);
}
}
}
}
protected void cleanupKeyListeners()
{
// NOT IMPLEMENTED YET
}
/** ExtendedTreeCacheListener methods */
// Private -------------------------------------------------------
protected Fqn<Serializable> buildFqn(String category)
{
return Fqn.fromRelativeElements(ROOTFQN, category);
}
protected Fqn<Serializable> buildFqn(String category, Serializable key)
{
return Fqn.fromElements(ROOT, category, key);
}
protected Fqn<Serializable> buildFqn(String category, Serializable key, Serializable value)
{
return Fqn.fromElements(ROOT, category, key, value);
}
protected Node<Serializable, Serializable> getNode(String category) throws CacheException
{
return this.cache.getRoot().getChild(this.buildFqn(category));
}
// @CacheListener -------------------------------------------------
@NodeModified
public void nodeModified(NodeModifiedEvent event)
{
if (event.isPre()) return;
// we're only interested in put and remove data operations
ModificationType modType = event.getModificationType();
if (!modType.equals(ModificationType.PUT_DATA) && !modType.equals(ModificationType.REMOVE_DATA)) return;
// ignore changes for other roots in a shared cache
@SuppressWarnings("unchecked")
Fqn<Serializable> fqn = event.getFqn();
if (!fqn.isChildOf(ROOTFQN)) return;
Serializable key = null;
Serializable value = null;
// there should be exactly one key/value pair in the map
@SuppressWarnings("unchecked")
Map<Serializable, Serializable> data = event.getData();
if (data != null && !data.isEmpty())
{
key = (Serializable) data.keySet().iterator().next();
value = (Serializable) data.get(key);
}
if (modType.equals(ModificationType.PUT_DATA))
{
DistributedStateImpl.this.notifyKeyListeners((String) fqn.get(ROOTFQNSIZE), key, value, event.isOriginLocal());
}
else
{
DistributedStateImpl.this.notifyKeyListenersOfRemove((String) fqn.get(ROOTFQNSIZE), key, value, event.isOriginLocal());
}
}
// Private ------------------------------------------------------------
private void internalSetClusteredCache(Cache<Serializable, Serializable> cache)
{
this.cache = cache;
if (this.cache != null)
{
CacheMode cm = cache.getConfiguration().getCacheMode();
if (CacheMode.REPL_ASYNC == cm)
{
this.replAsync = true;
}
else if (CacheMode.REPL_SYNC == cm)
{
this.replAsync = false;
}
else
{
throw new IllegalStateException("Cache must be configured for replication, not " + cm);
}
}
}
}