/*
* Galaxy
* Copyright (c) 2012-2014, Parallel Universe Software Co. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v1.0 as published by
* the Eclipse Foundation
*
* or (per the licensee's choosing)
*
* under the terms of the GNU Lesser General Public License version 3.0
* as published by the Free Software Foundation.
*/
package co.paralleluniverse.galaxy.core;
import co.paralleluniverse.common.collection.ConcurrentMultimapWithCopyOnWriteArrayList;
import co.paralleluniverse.common.spring.Service;
import co.paralleluniverse.galaxy.Cluster;
import co.paralleluniverse.galaxy.cluster.DistributedBranchHelper;
import co.paralleluniverse.galaxy.cluster.DistributedTree;
import co.paralleluniverse.galaxy.cluster.DistributedTreeAdapter;
import co.paralleluniverse.galaxy.cluster.LifecycleListener;
import co.paralleluniverse.galaxy.cluster.NodeChangeListener;
import co.paralleluniverse.galaxy.cluster.NodeInfo;
import co.paralleluniverse.galaxy.cluster.NodePropertyListener;
import co.paralleluniverse.galaxy.cluster.ReaderWriter;
import co.paralleluniverse.galaxy.cluster.SlaveConfigurationListener;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class basically works with the distributed tree provided by its concrete subclasses to write this node's info to the
* cluster and to provide queries and events regarding cluster status.
*
* @author pron
*/
public abstract class AbstractCluster extends Service implements Cluster {
private static final Logger LOG = LoggerFactory.getLogger(AbstractCluster.class);
protected static final String ROOT = "/co.paralleluniverse.galaxy";
protected static final String NODES = ROOT + "/nodes";
protected static final String LEADERS = ROOT + "/leaders";
//
private boolean hasServer = true;
//
private final Set<String> requiredPeerNodeProperties = new HashSet<String>();
private final Set<String> requiredServerProperties = new HashSet<String>();
private final Map<String, ReaderWriter> readerWriters = new ConcurrentHashMap<String, ReaderWriter>();
//
private final Map<String, NodeInfoImpl> nodes = new ConcurrentHashMap<String, NodeInfoImpl>();
private final Set<String> leaders = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
private DistributedTree controlTree;
private DistributedTree protectedTree;
private DistributedBranchHelper branch;
//
protected final short myId;
protected final NodeInfoImpl myNodeInfo;
private volatile boolean online;
private volatile boolean joined;
private volatile boolean master;
private volatile NodeInfoImpl myMaster;
private final List<NodeInfoImpl> mySlaves = new CopyOnWriteArrayList<NodeInfoImpl>();
private final Map<Short, NodeInfoImpl> masters = new ConcurrentHashMap<Short, NodeInfoImpl>();
private volatile NodeInfoImpl server;
private final List<NodeInfoImpl> slaveServers = new CopyOnWriteArrayList<NodeInfoImpl>();
private final Set<Short> activeNodes = Collections.newSetFromMap(new ConcurrentHashMap<Short, Boolean>());
//
private final List<NodeChangeListener> nodeChangeListeners = new CopyOnWriteArrayList<NodeChangeListener>();
private final List<LifecycleListener> lifecycleListeners = new CopyOnWriteArrayList<LifecycleListener>();
private final List<SlaveConfigurationListener> slaveConfigurationListeners = new CopyOnWriteArrayList<SlaveConfigurationListener>();
private final ConcurrentMultimapWithCopyOnWriteArrayList<String, NodePropertyListener> masterNodePropertyListeners = new ConcurrentMultimapWithCopyOnWriteArrayList<String, NodePropertyListener>();
private final ConcurrentMultimapWithCopyOnWriteArrayList<String, NodePropertyListener> slaveNodePropertyListeners = new ConcurrentMultimapWithCopyOnWriteArrayList<String, NodePropertyListener>();
public AbstractCluster(String name, short nodeId) {
super(name);
if (nodeId < 0) {
throw new IllegalArgumentException("nodeId " + nodeId + " is <= 0!");
}
this.myId = nodeId;
this.online = false;
this.master = false;
this.requiredPeerNodeProperties.add("id");
this.requiredServerProperties.add("id");
this.myNodeInfo = new NodeInfoImpl();
myNodeInfo.setNodeId(nodeId);
}
protected void setName(String name) {
assertDuringInitialization();
myNodeInfo.setName(name);
}
public void setHasServer(boolean hasServer) {
assertDuringInitialization();
this.hasServer = hasServer;
}
@Override
public boolean hasServer() {
return hasServer;
}
@Override
public synchronized void addNodeProperty(String property, boolean requiredForPeer, boolean requiredForServer, ReaderWriter<?> readerWriter) {
if (requiredForPeer) {
assertDuringInitialization();
requiredPeerNodeProperties.add(property);
}
if (requiredForServer) {
assertDuringInitialization();
requiredServerProperties.add(property);
}
if (!requiredForPeer && !requiredForServer) {
myNodeInfo.addProperty(property);
}
readerWriters.put(property, readerWriter);
}
@Override
public synchronized void setNodeProperty(String property, Object value) {
if (requiredPeerNodeProperties.contains(property) || requiredServerProperties.contains(property))
assertDuringInitialization();
myNodeInfo.set(property, value);
}
protected final void setControlTree(final DistributedTree controlTree) {
assertDuringInitialization();
this.controlTree = controlTree;
this.protectedTree = new DistributedTreeAdapter(controlTree) {
@Override
public void create(String node, boolean ephemeral) {
super.create(protect(node), ephemeral);
}
@Override
public void set(String node, byte[] data) {
super.set(protect(node), data);
}
@Override
public void delete(String node) {
super.delete(protect(node));
}
private String protect(String node) {
if (node.startsWith(NODES)) {
throw new IllegalArgumentException("Tree contents under " + NODES + " are reserved for internal use only!");
}
return node;
}
};
}
/**
* This is perhaps ugly, but overriding implementations must call this method at the end, or, at least, after calling
* setControlTree
*
* @throws Exception
*/
@Override
protected void postInit() throws Exception {
if (controlTree == null) {
throw new RuntimeException("controlTree not set");
}
controlTree.create(NODES, false);
controlTree.create(LEADERS, false);
if (controlTree.exists(myNodeInfo.treeNodePath)) {
LOG.error("A node with the name " + myNodeInfo.getName() + " already exists!");
throw new RuntimeException("Initialization failure");
}
LOG.info("Required peer node properties: {}", requiredPeerNodeProperties);
LOG.info("Required server properties: {}", requiredServerProperties);
final Set<String> requiredProperties = (myId == 0 ? requiredServerProperties : requiredPeerNodeProperties);
for (String property : requiredProperties) {
if (!property.equals("id") && myNodeInfo.get(property) == null) {
LOG.error("Required property {} not set!", property);
throw new RuntimeException("Initialization failure");
}
}
/// the calls in the demarcated section need to be in this specific order to avoid a possible race between nodes and leaders
// `>>>> BEGIN CAREFULLY ORDERED SECTION
branch = new DistributedBranchHelper(controlTree, NODES, false) {
@Override
protected boolean isNodeComplete(String node, Set<String> properties) {
if (!properties.contains("id")) {
return false;
}
final short id = Short.parseShort(new String(controlTree.get(node + "/id"), Charsets.UTF_8));
final Set<String> requiredProperties = (id == 0 ? requiredServerProperties : requiredPeerNodeProperties);
final boolean success = properties.containsAll(requiredProperties);
return success;
}
};
branch.addListener(new DistributedTree.ListenerAdapter() {
@Override
public void nodeChildAdded(String parentPath, String childName) {
AbstractCluster.this.nodeAdded(childName);
}
@Override
public void nodeChildDeleted(String parentPath, String childName) {
AbstractCluster.this.nodeRemoved(childName);
}
});
// This read and handles the nodes.
branch.init();
controlTree.addListener(LEADERS, new DistributedTree.ListenerAdapter() {
@Override
public void nodeChildAdded(String parentPath, String childName) {
AbstractCluster.this.leaderAdded(childName);
}
@Override
public void nodeChildDeleted(String parentPath, String childName) {
AbstractCluster.this.leaderRemoved(childName);
}
});
myNodeInfo.writeToTree();
setReady(true);
super.postInit();
joined = true;
fireJoinedCluster();
for (short id : masters.keySet()) {
if (id != myId)
fireNodeAdded(id);
}
}
@Override
protected void available(boolean value) {
super.available(value);
if (!value)
goOffline();
}
@Override
public DistributedTree getDistributedTree() {
return protectedTree;
}
@Override
public short getMyNodeId() {
return myId;
}
@Override
public NodeInfo getMyNodeInfo() {
return myNodeInfo;
}
@Override
public NodeInfo getNodeInfoByName(String nodeName) {
return nodes.get(nodeName);
}
@Override
public Collection<NodeInfo> getMasters() {
return Collections.unmodifiableCollection((Collection<? extends NodeInfo>) masters.values());
}
public Collection<NodeInfo> getAllSlaves() {
return Collections.unmodifiableCollection((Collection<? extends NodeInfo>) slaveServers);
}
@Override
public NodeInfo getMaster(short node) {
return masters.get(node);
}
@Override
public boolean isMaster(NodeInfo node) {
return masters.get(node.getNodeId()) == node;
}
@Override
public NodeInfo getMyMaster() {
return myMaster;
}
@Override
public List<NodeInfo> getMySlaves() {
return ImmutableList.copyOf((List<? extends NodeInfo>) mySlaves);
}
@Override
public boolean isMaster() {
return master;
}
@Override
public boolean isOnline() {
return online;
}
public boolean isJoined() {
return joined;
}
@Override
public void shutdown() {
controlTree.shutdown();
// moved to setOnline(false)
// if (myNodeInfo.getName() != null) {
// controlTree.delete(LEADERS + "/" + myNodeInfo.getName());
// }
// controlTree.delete(myNodeInfo.treeNodePath);
}
public void goOnline() {
if (isOnline())
return;
if (isSecondSlave()) {
LOG.error("THERE ALREADY EXISTS A SLAVE FOR NODE " + getMyNodeId() + ". ABORTING.");
goOffline();
throw new UnsupportedOperationException("Second slave is not supported");
}
try {
awaitAvailable();
LOG.info("NODE IS NOW ATTEMPTING TO GO ONLINE");
controlTree.createEphemeralOrdered(LEADERS + "/" + myNodeInfo.getName());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@Override
public void goOffline() {
setOnline(false);
}
private void setOnline(boolean value) {
if (this.online == value)
return;
if (value) {
if (isSecondSlave()) {
LOG.error("THERE ALREADY EXISTS A SLAVE FOR NODE " + getMyNodeId() + ". ABORTING.");
value = false;
}
}
this.online = value;
if (online) {
fireOnline();
for (NodeInfo slave : mySlaves)
fireSlaveAdded(slave);
} else {
LOG.info("NODE IS GOING OFFLINE!");
// TODO: check if web have to test (myNodeInfo.getName() != null) before
controlTree.delete(LEADERS + "/" + myNodeInfo.getName());
// this was inside the shutdown.
// if (myNodeInfo.getName() != null) {
// controlTree.delete(LEADERS + "/" + myNodeInfo.getName());
// }
// TODO: test if it helps and can be done here
// controlTree.delete(myNodeInfo.treeNodePath);
fireOffline();
shutdown();
}
}
private void setMaster(boolean value) {
if (this.master == value) {
return;
}
this.master = value;
if (master) {
fireSwitchToMaster();
} else {
LOG.error("Switch to slave??? Souldn't happen!!!!");
//fireSwitchToSlave();
}
for (NodeInfoImpl slave : findSlaves(myId)) {
mySlaves.add(slave);
fireSlaveAdded(slave);
}
}
private void setMyMaster(NodeInfoImpl newMaster) {
if (this.myMaster == newMaster) {
return;
}
this.myMaster = newMaster;
fireNewMaster(newMaster);
}
@Override
public Set<Short> getNodes() {
return Collections.unmodifiableSet(activeNodes);
}
@Override
public Set<NodeInfo> getNodesByProperty(String propertyName, Object value) {
final Set<NodeInfo> ns = new HashSet<NodeInfo>();
for (NodeInfoImpl n : nodes.values()) {
final Object v = n.get(propertyName);
if ((v == value) || (v != null && v.equals(value))) {
ns.add(n);
}
}
return Collections.unmodifiableSet(ns);
}
protected abstract boolean isMe(NodeInfoImpl node);
private void nodeAdded(String nodeName) {
LOG.info("New node added: {}", nodeName);
// Thread.dumpStack();
final NodeInfoImpl node = createNodeInfo(nodeName, true);
nodes.put(nodeName, node);
LOG.debug("nodes: {}", nodes);
if (leaders.contains(nodeName)) // leader event waited for node data
finishLeaderAdded(node);
}
private void nodeRemoved(String nodeName) {
LOG.info("Node removed: {}", nodeName);
final NodeInfoImpl node = nodes.get(nodeName);
if (isMe(node))
setOnline(false);
// else
// leaderRemoved(nodeName);
nodes.remove(nodeName);
}
private void leaderAdded(String nodeName) {
leaders.add(nodeName);
LOG.info("New leader added: {}", nodeName);
//Thread.dumpStack();
final NodeInfoImpl node = nodes.get(nodeName);
if (node == null) {
LOG.info("Node {} does not have a complete node info in the control tree. Waiting for node data completition", nodeName);
// finish_leader_add will be called after node data is completed
return;
}
finishLeaderAdded(node);
}
private void finishLeaderAdded(final NodeInfoImpl node) {
LOG.info("Finishing leader addition: {}", node.getName());
final NodeInfoImpl nodesMaster = findMaster(node.getNodeId(), null);
final boolean nodeIsServer = (node.getNodeId() == 0);
if (node.getNodeId() == myId) {
if (isMe(node)) {
this.master = (node == nodesMaster);
if (!this.master)
assert this.myMaster == nodesMaster; // this.myMaster = master;
LOG.info("=================================");
LOG.info("GOING ONLINE AS {} {} {}", new Object[]{myId, this.master ? "MASTER" : "SLAVE.", this.master ? "" : "MASTER IS " + nodesMaster});
LOG.info("=================================");
setOnline(true);
} else if (node == nodesMaster) {
LOG.info("Node {} is my master.", node);
this.myMaster = node;
masters.put(node.getNodeId(), node);
} else if (online && this.master) {
LOG.info("Node {} is added as slave.", node);
mySlaves.add(node);
fireSlaveAdded(node);
}
} else if (node == nodesMaster) {
LOG.info("New master for node {} is {}", node.getNodeId(), node);
final boolean switchover = masters.containsKey(node.getNodeId()); // could only happen in the extreme circumstance of the master going offline simultaneously with this node going online
masters.put(node.getNodeId(), node);
if (nodeIsServer) {
this.server = node;
}
if (switchover) {
fireNodeSwitched(node.getNodeId());
} else {
activeNodes.add(node.getNodeId());
fireNodeAdded(node.getNodeId());
}
} else {
if (nodeIsServer)
slaveServers.add(node);
LOG.info("New slave for {}: {}", node.getNodeId(), node);
}
}
private void leaderRemoved(String nodeName) {
LOG.info("Leader removed: {}", nodeName);
final NodeInfoImpl node = nodes.get(nodeName);
if (node == null) {
LOG.info("Leader {} has been removed but it has no valid node info.", nodeName);
return;
}
final boolean nodeIsServer = (node.getNodeId() == 0);
final NodeInfoImpl oldMaster = masters.get(node.getNodeId());
if (nodeIsServer) {
slaveServers.remove(node);
}
final NodeInfoImpl newMaster = findMaster(node.getNodeId(), nodeName); // we may have been called by removeNode before the leader is actually removed, so we ask findMaster to ignore it
if (oldMaster != node) {
// a slave node has died
if (node.getNodeId() == myId && this.master) {
if (mySlaves.remove(node)) { // protects against multiple calls
LOG.info("My slave node {} has gone offline.", node);
fireSlaveRemoved(node);
}
} else
LOG.info("Slave node {} has gone offline.", node);
} else {
// a master node has died
if (node.getNodeId() == myId) {
if (newMaster == null) {
LOG.info("No master found for node {}. (I am {})", node.getNodeId(), online ? "ONLINE" : "NOT ONLINE");
setOnline(false);
} else if (isMe(newMaster)) {
if (!this.master) { // protects against multiple calls
LOG.info("=====================");
LOG.info("SWITCHING TO MASTER");
LOG.info("=====================");
this.myMaster = null;
masters.remove(myId);
setMaster(true);
}
} else {
if (this.master) { // shouldn't happen
LOG.error("Switch to slave??? Souldn't happen!!!!");
//LOG.info("Switching to slave!");
//this.myMaster = newMaster;
//setMaster(false);
} else {
LOG.info("New master: {}", newMaster);
masters.put(newMaster.getNodeId(), newMaster);
setMyMaster(newMaster);
}
}
} else {
if (nodeIsServer)
this.server = newMaster;
if (newMaster == null) {
if (masters.remove(node.getNodeId()) != null) { // protects against multiple calls
LOG.info("No master for node {} - it's going offline!", node.getNodeId());
activeNodes.remove(node.getNodeId());
fireNodeRemoved(node.getNodeId());
}
} else {
if (masters.put(newMaster.getNodeId(), newMaster) != newMaster) { // protects against multiple calls
LOG.info("New master for node {} is {}", node.getNodeId(), newMaster);
fireNodeSwitched(newMaster.getNodeId());
}
}
}
}
}
private NodeInfoImpl findMaster(short nodeId, String oldMaster) {
for (String nodeName : controlTree.getChildren(LEADERS)) {
if (oldMaster != null && oldMaster.equals(nodeName))
continue;
final NodeInfoImpl node = nodes.get(nodeName);
if (node != null) {
if (node.getNodeId() == nodeId)
return node;
}
}
return null;
}
private boolean isSecondSlave() {
int counter = 0;
for (String nodeName : controlTree.getChildren(LEADERS)) {
final NodeInfoImpl node = nodes.get(nodeName);
if (node != null) {
if (isMe(node))
break;
else if (node.getNodeId() == getMyNodeId())
counter++;
}
}
return (counter > 1);
}
private List<NodeInfoImpl> findSlaves(short nodeId) {
List<NodeInfoImpl> slaves = new ArrayList<NodeInfoImpl>();
boolean foundMaster = false;
for (String nodeName : controlTree.getChildren(LEADERS)) {
final NodeInfoImpl node = nodes.get(nodeName);
assert node != null;
if (node.getNodeId() == nodeId) {
if (foundMaster)
slaves.add(node);
else
foundMaster = true;
}
}
return slaves;
}
protected NodeInfoImpl createNodeInfo(String nodeName, boolean attachToTree) {
return new NodeInfoImpl(nodeName, attachToTree);
}
@Override
public void addLifecycleListener(LifecycleListener listener) {
lifecycleListeners.add(listener);
}
@Override
public void removeLifecycleListener(LifecycleListener listener) {
lifecycleListeners.remove(listener);
}
@Override
public void addSlaveConfigurationListener(SlaveConfigurationListener listener) {
slaveConfigurationListeners.add(listener);
}
@Override
public void removeSlaveConfigurationListener(SlaveConfigurationListener listener) {
slaveConfigurationListeners.remove(listener);
}
@Override
public void addNodeChangeListener(NodeChangeListener listener) {
nodeChangeListeners.add(listener);
}
@Override
public void removeNodeChangeListener(NodeChangeListener listener) {
nodeChangeListeners.remove(listener);
}
@Override
public void addMasterNodePropertyListener(String property, NodePropertyListener listener) {
assertPropertyRegistered(property);
masterNodePropertyListeners.put(property, listener);
}
@Override
public void removeMasterNodePropertyListener(String property, NodePropertyListener listener) {
masterNodePropertyListeners.remove(property, listener);
}
@Override
public void addSlaveNodePropertyListener(String property, NodePropertyListener listener) {
assertPropertyRegistered(property);
slaveNodePropertyListeners.put(property, listener);
}
@Override
public void removeSlaveNodePropertyListener(String property, NodePropertyListener listener) {
slaveNodePropertyListeners.remove(property, listener);
}
private void fireJoinedCluster() {
for (LifecycleListener listener : lifecycleListeners) {
try {
listener.joinedCluster();
} catch (Exception e) {
LOG.error("Listener threw an exception.", e);
}
}
}
private void fireOnline() {
for (LifecycleListener listener : lifecycleListeners) {
try {
listener.online(master);
} catch (Exception e) {
LOG.error("Listener threw an exception.", e);
}
}
}
private void fireOffline() {
for (LifecycleListener listener : lifecycleListeners) {
try {
listener.offline();
} catch (Exception e) {
LOG.error("Listener threw an exception.", e);
}
}
}
private void fireSwitchToMaster() {
for (LifecycleListener listener : lifecycleListeners) {
try {
listener.switchToMaster();
} catch (Exception e) {
LOG.error("Listener threw an exception.", e);
}
}
}
private void fireNodeAdded(short id) {
if (!isJoined())
return;
for (NodeChangeListener listener : nodeChangeListeners) {
try {
listener.nodeAdded(id);
} catch (Exception e) {
LOG.error("Listener threw an exception for node " + id, e);
}
}
}
private void fireNodeSwitched(short id) {
if (!isJoined())
return;
for (NodeChangeListener listener : nodeChangeListeners) {
try {
listener.nodeSwitched(id);
} catch (Exception e) {
LOG.error("Listener threw an exception.", e);
}
}
}
private void fireNodeRemoved(short id) {
if (!isJoined())
return;
for (NodeChangeListener listener : nodeChangeListeners) {
try {
listener.nodeRemoved(id);
} catch (Exception e) {
LOG.error("Listener threw an exception.", e);
}
}
}
private void fireSlaveAdded(NodeInfo node) {
if (!isOnline())
return;
for (SlaveConfigurationListener listener : slaveConfigurationListeners) {
try {
listener.slaveAdded(node);
} catch (Exception e) {
LOG.error("Listener threw an exception.", e);
}
}
}
private void fireSlaveRemoved(NodeInfo node) {
if (!isOnline())
return;
for (SlaveConfigurationListener listener : slaveConfigurationListeners) {
try {
listener.slaveRemoved(node);
} catch (Exception e) {
LOG.error("Listener threw an exception.", e);
}
}
}
private void fireNewMaster(NodeInfo node) {
if (!isOnline())
return;
for (SlaveConfigurationListener listener : slaveConfigurationListeners) {
try {
listener.newMaster(node);
} catch (Exception e) {
LOG.error("Listener threw an exception.", e);
}
}
}
void fireNodePropertyChanged(NodeInfoImpl node, String property, Object value) {
if (node == myNodeInfo)
return;
if (masters.containsValue(node)) {
for (NodePropertyListener listener : masterNodePropertyListeners.get(property)) {
try {
listener.propertyChanged(node, property, value);
} catch (Exception e) {
LOG.error("Listener threw an exception.", e);
}
}
} else if (mySlaves.contains(node)) {
for (NodePropertyListener listener : slaveNodePropertyListeners.get(property)) {
try {
listener.propertyChanged(node, property, value);
} catch (Exception e) {
LOG.error("Listener threw an exception.", e);
}
}
}
}
private ReaderWriter assertPropertyRegistered(String property) {
final ReaderWriter rw = readerWriters.get(property);
if (rw == null)
throw new RuntimeException("No ReaderWriter set for property " + property);
return rw;
}
protected final Object readProperty(String property, byte[] value) {
ReaderWriter rw = assertPropertyRegistered(property);
return rw.read(value);
}
protected final byte[] writeProperty(String property, Object value) {
ReaderWriter rw = assertPropertyRegistered(property);
return rw.write(value);
}
protected class NodeInfoImpl extends DistributedTree.ListenerAdapter implements NodeInfo {
private String name;
private String treeNodePath;
private short nodeId = -1;
private final Map<String, Object> properties = new ConcurrentHashMap<String, Object>();
private final boolean attached;
public NodeInfoImpl() {
this.attached = false;
}
public NodeInfoImpl(String name) {
this(name, true);
}
public NodeInfoImpl(String name, boolean attachToTree) {
setName(name);
this.attached = attachToTree;
if (attached) {
controlTree.addListener(treeNodePath, this);
for (String child : controlTree.getChildren(treeNodePath))
nodeChildUpdated(treeNodePath, child);
}
}
private void setName(String name) {
this.name = name;
this.treeNodePath = NODES + '/' + name;
}
@Override
public String getName() {
return name;
}
@Override
public Collection<String> getProperties() {
return properties.keySet();
}
public String getTreeNodePath() {
return treeNodePath;
}
@Override
public short getNodeId() {
return nodeId;
}
private void setNodeId(short nodeId) {
assertDuringInitialization();
if (attached) {
throw new IllegalStateException("Node is attached to the tree!");
}
this.nodeId = nodeId;
}
public void addProperty(String property) {
assertInitialized();
if (attached)
throw new IllegalStateException("Node is attached to the tree!");
controlTree.create(treeNodePath + '/' + property, true);
}
public synchronized void set(String property, Object value) {
if (attached)
throw new IllegalStateException("Node is attached to the tree!");
if (properties.get(property) != null) {
if (!properties.get(property).equals(value))
throw new IllegalStateException("Property " + property + " has already been set do a different value");
} else {
properties.put(property, value);
if (isInitialized()) {
LOG.info("Publishing additional node info: {} = {}", property, value);
controlTree.set(treeNodePath + '/' + property, writeProperty(property, value));
}
}
}
@Override
public Object get(String property) {
return properties.get(property);
}
public void writeToTree() {
assertDuringInitialization();
if (attached)
throw new IllegalStateException("Node is attached to tree -> cannot be written!");
if (name == null || nodeId < 0)
throw new AssertionError("Incomplete node data");
LOG.info("Publishing node info: name = {}, id = {}", name, nodeId);
controlTree.create(treeNodePath, true);
controlTree.create(treeNodePath + '/' + "id", true);
controlTree.set(treeNodePath + '/' + "id", Short.toString(nodeId).getBytes(Charsets.UTF_8));
for (Map.Entry<String, Object> property : properties.entrySet()) {
LOG.info("Publishing node info: {} = {}", property.getKey(), property.getValue());
controlTree.create(treeNodePath + '/' + property.getKey(), true);
controlTree.set(treeNodePath + '/' + property.getKey(), writeProperty(property.getKey(), property.getValue()));
}
controlTree.flush();
}
protected void readChild(String childName, byte[] value) {
if ("id".equals(childName)) {
final short nodeValue = Short.parseShort(new String(value, Charsets.UTF_8));
if (this.nodeId >= 0) {
if (this.nodeId != nodeValue)
throw new RuntimeException("Id for node " + name + " is already set to " + nodeId);
return;
}
this.nodeId = nodeValue;
} else {
if (value == null)
return;
if (readerWriters.get(childName) == null) {
LOG.warn("No reader set for property {} (found in node {})", childName, name);
return;
}
final Object currentProperty = properties.get(childName);
final Object newValProperty = readProperty(childName, value);
// required properties shouldn't be changed
if ((requiredPeerNodeProperties.contains(childName) || requiredServerProperties.contains(childName)) && currentProperty != null) {
if (!currentProperty.equals(newValProperty))
throw new RuntimeException("Required property " + childName + " for node " + name + " is already set to " + properties.get(childName));
else
return;
}
properties.put(childName, newValProperty);
}
}
@Override
public void nodeChildAdded(String parentPath, String childName) {
nodeChildUpdated(parentPath, childName);
}
@Override
public final void nodeChildUpdated(String parentPath, String childName) {
assert parentPath.equals(treeNodePath);
try {
readChild(childName, controlTree.get(parentPath + '/' + childName));
fireNodePropertyChanged(this, childName, properties.get(childName));
} catch (Exception e) {
LOG.error("Exception while reading control tree value.", e);
}
}
@Override
public void nodeChildDeleted(String node, String childName) {
assert !requiredPeerNodeProperties.contains(childName) && !requiredServerProperties.contains(childName);
properties.remove(childName);
fireNodePropertyChanged(this, childName, null);
}
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (obj == this)
return true;
if (!(obj instanceof NodeInfo))
return false;
final NodeInfoImpl other = (NodeInfoImpl) obj;
if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name))
return false;
return true;
}
@Override
public int hashCode() {
int hash = 3;
hash = 31 * hash + (this.name != null ? this.name.hashCode() : 0);
return hash;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("NODE ").append(name);
sb.append(" id: ").append(nodeId);
for (Map.Entry<String, Object> entry : new TreeMap<String, Object>(properties).entrySet())
sb.append(' ').append(entry.getKey()).append(": ").append(entry.getValue());
return sb.toString();
}
}
}