Package co.paralleluniverse.galaxy.core

Source Code of co.paralleluniverse.galaxy.core.AbstractCluster$NodeInfoImpl

/*
* 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("nodId " + 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() {
// 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();
        }
    }
}
TOP

Related Classes of co.paralleluniverse.galaxy.core.AbstractCluster$NodeInfoImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.