/**
*
* Copyright 2004 Protique Ltd
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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.codehaus.activecluster.impl;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.activecluster.Cluster;
import org.codehaus.activecluster.ClusterEvent;
import org.codehaus.activecluster.ClusterListener;
import org.codehaus.activecluster.Node;
import org.codehaus.activecluster.election.ElectionStrategy;
import org.codehaus.activecluster.election.impl.BullyElectionStrategy;
import javax.jms.Destination;
import javax.jms.JMSException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Timer;
import java.util.TimerTask;
/**
* Represents a node list
*
* @version $Revision: 1.10 $
*/
public class StateServiceImpl implements StateService {
private final static Log log = LogFactory.getLog(StateServiceImpl.class);
private Cluster cluster;
private Object clusterLock;
private Map nodes = new HashMap();
private long inactiveTime;
private List listeners = Collections.synchronizedList(new ArrayList());
private Destination localDestination;
private Runnable localNodePing;
private Timer timer;
private NodeImpl coordinator;
private ElectionStrategy electionStrategy;
public StateServiceImpl(Cluster cluster, Object clusterLock, Runnable localNodePing, Timer timer, long inactiveTime) {
this.cluster = cluster;
this.clusterLock = clusterLock;
this.localDestination = cluster.getLocalNode().getDestination();
this.localNodePing = localNodePing;
this.timer = timer;
this.inactiveTime = inactiveTime;
long delay = inactiveTime / 3;
timer.scheduleAtFixedRate(createTimerTask(), delay, delay);
(this.coordinator = (NodeImpl)cluster.getLocalNode()).setCoordinator(true);
this.electionStrategy = new BullyElectionStrategy();
}
/**
* @return the current election strategy
*/
public ElectionStrategy getElectionStrategy(){
return electionStrategy;
}
/**
* set the election strategy
* @param electionStrategy
*/
public void setElectionStrategy(ElectionStrategy electionStrategy){
this.electionStrategy = electionStrategy;
}
public long getInactiveTime() {
return inactiveTime;
}
public void setInactiveTime(long inactiveTime) {
this.inactiveTime = inactiveTime;
}
public synchronized Map getNodes() {
HashMap answer = new HashMap(nodes.size());
for (Iterator iter = nodes.entrySet().iterator(); iter.hasNext();) {
Map.Entry entry = (Map.Entry) iter.next();
Destination key = (Destination) entry.getKey();
NodeEntry nodeEntry = (NodeEntry) entry.getValue();
answer.put(key, nodeEntry.node);
}
return answer;
}
public synchronized void keepAlive(Node node) {
Destination key = node.getDestination();
if (!localDestination.equals(key)) {
NodeEntry entry = (NodeEntry) nodes.get(key);
if (entry == null) {
entry = new NodeEntry();
entry.node = node;
nodes.put(key, entry);
nodeAdded(node);
synchronized (clusterLock) {
clusterLock.notifyAll();
}
}
else {
// has the data changed
if (stateHasChanged(entry.node, node)) {
entry.node = node;
nodeUpdated(node);
}
}
// lets update the timer at which the node will be considered
// to be dead
entry.lastKeepAlive = getTimeMillis();
}
}
public synchronized void shutdown(Node node) {
Destination key = node.getDestination();
nodes.remove(key);
ClusterEvent event = new ClusterEvent(cluster, node, ClusterEvent.ADD_NODE);
// lets take a copy to make contention easier
Object[] array = listeners.toArray();
for (int i = 0, size = array.length; i < size; i++) {
ClusterListener listener = (ClusterListener) array[i];
listener.onNodeRemoved(event);
}
}
public synchronized void checkForTimeouts() {
localNodePing.run();
long time = getTimeMillis();
for (Iterator iter = nodes.entrySet().iterator(); iter.hasNext();) {
Map.Entry entry = (Entry) iter.next();
NodeEntry nodeEntry = (NodeEntry) entry.getValue();
if (nodeEntry.lastKeepAlive + inactiveTime < time) {
iter.remove();
nodeFailed(nodeEntry.node);
}
}
}
public TimerTask createTimerTask() {
return new TimerTask() {
public void run() {
checkForTimeouts();
}
};
}
public void addClusterListener(ClusterListener listener) {
listeners.add(listener);
}
public void removeClusterListener(ClusterListener listener) {
listeners.remove(listener);
}
protected void nodeAdded(Node node) {
ClusterEvent event = new ClusterEvent(cluster, node, ClusterEvent.ADD_NODE);
// lets take a copy to make contention easier
Object[] array = listeners.toArray();
for (int i = 0, size = array.length; i < size; i++) {
ClusterListener listener = (ClusterListener) array[i];
listener.onNodeAdd(event);
}
doElection();
}
protected void nodeUpdated(Node node) {
ClusterEvent event = new ClusterEvent(cluster, node, ClusterEvent.UPDATE_NODE);
// lets take a copy to make contention easier
Object[] array = listeners.toArray();
for (int i = 0, size = array.length; i < size; i++) {
ClusterListener listener = (ClusterListener) array[i];
listener.onNodeUpdate(event);
}
}
protected void nodeFailed(Node node) {
ClusterEvent event = new ClusterEvent(cluster, node, ClusterEvent.REMOVE_NODE);
// lets take a copy to make contention easier
Object[] array = listeners.toArray();
for (int i = 0, size = array.length; i < size; i++) {
ClusterListener listener = (ClusterListener) array[i];
listener.onNodeFailed(event);
}
doElection();
}
protected void coordinatorChanged(Node node) {
ClusterEvent event = new ClusterEvent(cluster, node, ClusterEvent.ELECTED_COORDINATOR);
// lets take a copy to make contention easier
Object[] array = listeners.toArray();
for (int i = 0, size = array.length; i < size; i++) {
ClusterListener listener = (ClusterListener) array[i];
listener.onCoordinatorChanged(event);
}
}
protected void doElection() {
if (electionStrategy != null) {
try {
NodeImpl newElected = (NodeImpl) electionStrategy.doElection(cluster);
if (newElected != null && !newElected.equals(coordinator)) {
coordinator.setCoordinator(false);
coordinator = newElected;
coordinator.setCoordinator(true);
coordinatorChanged(coordinator);
}
}catch(JMSException jmsEx){
log.error("do election failed",jmsEx);
}
}
}
/**
* For performance we may wish to use a less granualar timing mechanism
* only updating the time every x millis since we're only using
* the time as a judge of when a node has not pinged for at least a few
* hundred millis etc.
*
* @return
*/
protected long getTimeMillis() {
return System.currentTimeMillis();
}
protected static class NodeEntry {
public Node node;
public long lastKeepAlive;
}
/**
* @return true if the node has changed state from the old in memory copy to the
* newly arrived copy
*/
protected boolean stateHasChanged(Node oldNode, Node newNode) {
Map oldState = oldNode.getState();
Map newState = newNode.getState();
if (oldState == newState) {
return false;
}
return oldState == null || newState == null || !oldState.equals(newState);
}
}