package org.mobicents.cluster;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.transaction.TransactionManager;
import org.apache.log4j.Logger;
import org.jboss.cache.Cache;
import org.jboss.cache.Fqn;
import org.jboss.cache.config.Configuration.CacheMode;
import org.jboss.cache.notifications.annotation.CacheListener;
import org.jboss.cache.notifications.annotation.ViewChanged;
import org.jboss.cache.notifications.event.ViewChangedEvent;
import org.jgroups.Address;
import org.mobicents.cache.MobicentsCache;
import org.mobicents.cluster.cache.ClusteredCacheData;
import org.mobicents.cluster.cache.ClusteredCacheDataIndexingHandler;
import org.mobicents.cluster.cache.DefaultClusteredCacheDataIndexingHandler;
import org.mobicents.cluster.election.SingletonElector;
/**
* Listener that is to be used for cluster wide replication(meaning no buddy
* replication, no data gravitation). It will index activity on nodes marking
* current node as owner(this is semi-gravitation behavior (we don't delete, we
* just mark)).
*
* Indexing is only at node level, i.e., there is not
* reverse indexing, so it has to iterate through whole resource group data FQNs to check which
* nodes should be taken over.
*
* @author <a href="mailto:baranowb@gmail.com">Bartosz Baranowski </a>
* @author martins
*/
@CacheListener(sync = false)
public class DefaultMobicentsCluster implements MobicentsCluster {
private static final Logger logger = Logger.getLogger(DefaultMobicentsCluster.class);
private final SortedSet<ClientLocalListener> localListeners;
private final MobicentsCache mobicentsCache;
private final TransactionManager txMgr;
private final SingletonElector elector;
private final DefaultClusteredCacheDataIndexingHandler clusteredCacheDataIndexingHandler;
private List<Address> currentView;
public DefaultMobicentsCluster(MobicentsCache watchedCache, TransactionManager txMgr, SingletonElector elector) {
this.localListeners = Collections.synchronizedSortedSet(new TreeSet<ClientLocalListener>(new ClientLocalListenerComparator()));
this.mobicentsCache = watchedCache;
final Cache cache = watchedCache.getJBossCache();
if (!cache.getConfiguration().getCacheMode().equals(CacheMode.LOCAL)) {
// get current cluster members
currentView = new ArrayList<Address>(cache.getConfiguration().getRuntimeConfig().getChannel().getView().getMembers());
// start listening to events
cache.addCacheListener(this);
}
this.txMgr = txMgr;
this.elector = elector;
this.clusteredCacheDataIndexingHandler = new DefaultClusteredCacheDataIndexingHandler();
}
/* (non-Javadoc)
* @see org.mobicents.cluster.MobicentsCluster#getLocalAddress()
*/
public Address getLocalAddress() {
return mobicentsCache.getJBossCache().getLocalAddress();
}
/* (non-Javadoc)
* @see org.mobicents.cluster.MobicentsCluster#getClusterMembers()
*/
public List<Address> getClusterMembers() {
if (currentView != null) {
return Collections.unmodifiableList(currentView);
}
else {
Address localAddress = getLocalAddress();
if (localAddress == null) {
return Collections.emptyList();
}
else {
List<Address> list = new ArrayList<Address>();
list.add(localAddress);
return Collections.unmodifiableList(list);
}
}
}
/* (non-Javadoc)
* @see org.mobicents.cluster.MobicentsCluster#isHeadMember()
*/
public boolean isHeadMember() {
final Address localAddress = getLocalAddress();
if (localAddress != null) {
final List<Address> clusterMembers = getClusterMembers();
return !clusterMembers.isEmpty() && clusterMembers.get(0).equals(localAddress);
}
else {
return true;
}
}
/**
* Method handle a change on the cluster members set
* @param event
*/
@ViewChanged
public synchronized void onViewChangeEvent(ViewChangedEvent event) {
if (logger.isDebugEnabled()) {
logger.debug("onViewChangeEvent : pre[" + event.isPre() + "] : event local address[" + event.getCache().getLocalAddress() + "]");
}
final List<Address> oldView = currentView;
currentView = new ArrayList<Address>(event.getNewView().getMembers());
final Address localAddress = getLocalAddress();
// recover stuff from lost members
Runnable runnable = new Runnable() {
public void run() {
for (Address oldMember : oldView) {
if (!currentView.contains(oldMember)) {
// this member is gone
if (logger.isDebugEnabled()) {
logger.debug("onViewChangeEvent : processing lost member " + oldMember);
}
if (elector.elect(currentView).equals(localAddress)) {
// local member was elected to take over the work being done by the lost member
performTakeOver(oldMember,localAddress);
}
}
}
}
};
Thread t = new Thread(runnable);
t.start();
}
/**
*
*/
private void performTakeOver(Address lostMember, Address localAddress) {
if (logger.isDebugEnabled()) {
logger.debug("onViewChangeEvent : failing over lost member " + lostMember);
}
final Cache jbossCache = mobicentsCache.getJBossCache();
for (ClientLocalListener localListener : this.localListeners) {
final Fqn rootFqnOfChanges = localListener.getBaseFqn();
boolean createdTx = false;
boolean doRollback = true;
try {
if (txMgr != null && txMgr.getTransaction() == null) {
txMgr.begin();
createdTx = true;
}
localListener.failOverClusterMember(lostMember);
for (Object childName : jbossCache.getChildrenNames(rootFqnOfChanges)) {
// Here in values we store data and... inet node., we must match
// passed one.
final ClusteredCacheData clusteredCacheData = new ClusteredCacheData(Fqn.fromRelativeElements(rootFqnOfChanges, childName),this);
if (clusteredCacheData.exists()) {
Address address = clusteredCacheData.getClusterNodeAddress();
if (address != null && address.equals(lostMember)) {
// call back the listener
localListener.wonOwnership(clusteredCacheData);
// change ownership
clusteredCacheData.setClusterNodeAddress(localAddress);
}
}
}
doRollback = false;
} catch (Exception e) {
logger.error(e.getMessage(),e);
} finally {
if (createdTx) {
try {
if (!doRollback) {
txMgr.commit();
}
else {
txMgr.rollback();
}
} catch (Exception e) {
logger.error(e.getMessage(),e);
}
}
}
}
}
// NOTE USED FOR NOW
/*
@NodeCreated
public void onNodeCreateddEvent(NodeCreatedEvent event) {
if (log.isDebugEnabled()) {
log.debug("onNodeCreateddEvent : " + event.getFqn() + " : local[" + event.isOriginLocal() + "] pre[" + event.isPre() + "] : event local address[" + event.getCache().getLocalAddress()
+ "]");
}
}
@NodeModified
public void onNodeModifiedEvent(NodeModifiedEvent event) {
if (log.isDebugEnabled()) {
log.debug("onNodeModifiedEvent : pre[" + event.isPre() + "] : event local address[" + event.getCache().getLocalAddress() + "]");
}
}
@NodeRemoved
public void onNodeRemovedEvent(NodeRemovedEvent event) {
if (log.isDebugEnabled()) {
log.debug("onNodeRemovedEvent : " + event.getFqn() + " : local[" + event.isOriginLocal() + "] pre[" + event.isPre() + "] ");
}
}
@NodeMoved
public void onNodeMovedEvent(NodeMovedEvent event) {
if (log.isDebugEnabled()) {
log.debug("onNodeMovedEvent : " + event.getFqn() + " : local[" + event.isOriginLocal() + "] pre[" + event.isPre() + "] ");
}
}
@NodeVisited
public void onNodeVisitedEvent(NodeVisitedEvent event) {
if (log.isDebugEnabled()) {
log.debug("onNodeVisitedEvent : " + event.getFqn() + " : local[" + event.isOriginLocal() + "] pre[" + event.isPre() + "] ");
}
}
@NodeLoaded
public void onNodeLoadedEvent(NodeLoadedEvent event) {
if (log.isDebugEnabled()) {
log.debug("onNodeLoadedEvent : " + event.getFqn() + " : local[" + event.isOriginLocal() + "] pre[" + event.isPre() + "] ");
}
}
@NodeEvicted
public void onNodeEvictedEvent(NodeEvictedEvent event) {
if (log.isDebugEnabled()) {
log.debug("onNodeEvictedEvent : " + event.getFqn() + " : local[" + event.isOriginLocal() + "] pre[" + event.isPre() + "] ");
}
}
@NodeInvalidated
public void onNodeInvalidatedEvent(NodeInvalidatedEvent event) {
if (log.isDebugEnabled()) {
log.debug("onNodeInvalidatedEvent : " + event.getFqn() + " : local[" + event.isOriginLocal() + "] pre[" + event.isPre() + "] ");
}
}
@NodeActivated
public void onNodeActivatedEvent(NodeActivatedEvent event) {
if (log.isDebugEnabled()) {
log.debug("onNodeActivatedEvent : " + event.getFqn() + " : local[" + event.isOriginLocal() + "] pre[" + event.isPre() + "] ");
}
}
@NodePassivated
public void onNodePassivatedEvent(NodePassivatedEvent event) {
if (log.isDebugEnabled()) {
log.debug("onNodePassivatedEvent : " + event.getFqn() + " : local[" + event.isOriginLocal() + "] pre[" + event.isPre() + "] ");
}
}
@BuddyGroupChanged
public void onBuddyGroupChangedEvent(BuddyGroupChangedEvent event) {
if (log.isDebugEnabled()) {
log.debug("onBuddyGroupChangedEvent : pre[" + event.isPre() + "] ");
}
}
@CacheStarted
public void onCacheStartedEvent(CacheStartedEvent event) {
if (log.isDebugEnabled()) {
log.debug("onCacheStartedEvent : pre[" + event.isPre() + "] ");
}
}
@CacheStopped
public void onCacheStoppedEvent(CacheStoppedEvent event) {
if (log.isDebugEnabled()) {
log.debug("onCacheStoppedEvent : pre[" + event.isPre() + "] ");
}
}
*/
// LOCAL LISTENERS MANAGEMENT
/*
* (non-Javadoc)
* @see org.mobicents.cluster.MobicentsCluster#addLocalListener(org.mobicents.cluster.ClientLocalListener)
*/
public boolean addLocalListener(ClientLocalListener localListener) {
if (logger.isDebugEnabled()) {
logger.debug("Adding local listener " + localListener);
}
return localListeners.add(localListener);
}
/*
* (non-Javadoc)
* @see org.mobicents.cluster.MobicentsCluster#removeLocalListener(org.mobicents.cluster.ClientLocalListener)
*/
public boolean removeLocalListener(ClientLocalListener localListener) {
if (logger.isDebugEnabled()) {
logger.debug("Removing local listener " + localListener);
}
return localListeners.remove(localListener);
}
/*
* (non-Javadoc)
* @see org.mobicents.cluster.MobicentsCluster#getMobicentsCache()
*/
public MobicentsCache getMobicentsCache() {
return mobicentsCache;
}
/* (non-Javadoc)
* @see org.mobicents.cluster.MobicentsCluster#getClusteredCacheDataIndexingHandler()
*/
public ClusteredCacheDataIndexingHandler getClusteredCacheDataIndexingHandler() {
return clusteredCacheDataIndexingHandler;
}
}