/* * Galaxy * Copyright (C) 2012 Parallel Universe Software Co. * * This file is part of Galaxy. * * Galaxy 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 3 of * the License, or (at your option) any later version. * * Galaxy 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 Galaxy. If not, see <http://www.gnu.org/licenses/>. */ package co.paralleluniverse.galaxy.core; import co.paralleluniverse.galaxy.cluster.DistributedTree; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; import java.io.PrintStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Scanner; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class LocalTree implements DistributedTree { public static final char SEPARATOR = '/'; public static final String SSEPARATOR = Character.toString(SEPARATOR); private static final int INDENT = 4; private static final Logger LOG = LoggerFactory.getLogger(LocalTree.class); private final Node root = new Node("", SSEPARATOR, null, false, null); private final Multimap<String, Listener> pendingListeners = Multimaps.synchronizedMultimap((Multimap) HashMultimap.create()); public LocalTree() { } @Override public void addListener(String node, Listener listener) { final Node n = findNode(node); if (n == null) pendingListeners.put(node, listener); else n.addListener(listener); List<String> children = getChildren(node); if (children==null) return; for (String child : children) { listener.nodeChildAdded(node, child); } } @Override public void removeListener(String node, Listener listener) { final Node n = findNode(node); if (n == null) pendingListeners.remove(node, listener); else n.removeListener(listener); } @Override public void create(String fqn, boolean ephemeral) { if (fqn == null) return; findNode(fqn, true, ephemeral); // create all nodes if they don't exist } @Override public void createEphemeralOrdered(String fqn) { create(fqn, true); } @Override public void set(String fqn, byte[] data) { if (fqn == null) return; final Node n = findNode(fqn); // create all nodes if they don't exist if (n != null && ((n.getData() == null && data != null) || !Arrays.equals(n.getData(), data))) { LOG.info("Modifying data for node {}", fqn); n.setData(data); } else LOG.warn("Attempted to modify nonexistent node {}", fqn); } @Override public void delete(String fqn) { if (fqn == null) return; if (fqn.equals(SSEPARATOR)) { LOG.info("Clearing tree"); root.removeAll(); return; } final Node parent = findNode(parent(fqn)); if (parent == null) { LOG.warn("Parent {} of node {} not found.", parent(fqn), fqn); return; } LOG.info("Removing node {}", fqn); parent.removeChild(child(fqn)); } @Override public void flush() { } @Override public boolean exists(String fqn) { if (fqn == null) return false; return findNode(fqn) != null; } @Override public byte[] get(String fqn) { final Node n = findNode(fqn); if (n == null) return null; final byte[] buffer = n.getData(); if(buffer == null) return null; return Arrays.copyOf(buffer, buffer.length); } @Override public List<String> getChildren(String fqn) { final Node n = findNode(fqn); if (n == null) return null; final Set<String> names = n.getChildrenNames(); return new ArrayList<String>(names); } @Override public void print(String fqn, PrintStream out) { final Node n = findNode(fqn); if (n == null) return; StringBuilder sb = new StringBuilder(); n.print(sb, 0); out.println(sb.toString()); } public String toString(String fqn) { final Node n = findNode(fqn); if (n == null) return null; return n.toString(); } @Override public String toString() { StringBuilder sb = new StringBuilder(); root.print(sb, 0); return sb.toString(); } private Node findNode(String fqn, boolean create, boolean ephemeral) { if (fqn == null || fqn.equals(SSEPARATOR) || "".equals(fqn)) return root; final Scanner scanner = new Scanner(fqn).useDelimiter(SSEPARATOR); Node curr = root; while (scanner.hasNext()) { final String name = scanner.next(); Node node = curr.getChild(name); if (create) { if (node == null) { LOG.info("Creating node {}", curr.fqn + (!curr.fqn.equals(SSEPARATOR) ? SEPARATOR : "") + name); node = curr.createChild(name, ephemeral, null); } else { if (node.isEphemeral()) { if (!ephemeral) { LOG.info("Making node {} non-ephemeral.", node.fqn); node.setNotEphemeral(); } } } } if (node == null) return null; else curr = node; } return curr; } private Node findNode(String fqn) { return findNode(fqn, false, false); } public static String parent(String fqn) { final int index = fqn.lastIndexOf(SEPARATOR); if (index < 0) return null; final String parent = fqn.substring(0, index); return parent; } public static String child(String fqn) { final int index = fqn.lastIndexOf(SEPARATOR); if (index < 0 || index == fqn.length() - 1) return null; final String child = fqn.substring(index + 1, fqn.length()); return child; } @Override public void shutdown() { } private class Node { final String name; // relative name (e.g. "Security") final String fqn; // fully qualified name (e.g. "/federations/fed1/servers/Security") final Node parent; private boolean ephemeral; private byte[] data; private Map<String, Node> children; private transient List<Listener> listeners = null; Node(String childName, String fqn, Node parent, boolean ephemeral, byte[] data) { this.name = childName; this.fqn = fqn; this.parent = parent; this.ephemeral = ephemeral; this.data = data != null ? Arrays.copyOf(data, data.length) : null; } boolean isEphemeral() { return ephemeral; } void setNotEphemeral() { ephemeral = false; } synchronized byte[] getData() { return data; } final synchronized void setData(byte[] data) { this.data = data != null ? Arrays.copyOf(data, data.length) : null; notifyNodeModified(); } synchronized Map<String, Node> getChildren() { return children != null ? children : (Map<String, Node>) Collections.EMPTY_MAP; } synchronized Set<String> getChildrenNames() { return children != null ? Collections.unmodifiableSet(children.keySet()) : (Set<String>) Collections.EMPTY_SET; } synchronized Node getChild(String childName) { return childName == null ? null : children == null ? null : children.get(childName); } synchronized boolean hasChild(String childName) { return childName != null && children != null && children.containsKey(childName); } private synchronized Node addChild(Node child) { assert fqn.equals(parent(child.fqn)) || (fqn.equals("/") && parent(child.fqn).equals("")); if (children == null) children = new LinkedHashMap<String, Node>(); children.put(child.name, child); child.notifyNodeAdded(); return child; } synchronized Node createChild(String childName, boolean ephemeral, byte[] data) { if (childName == null) return null; assert children == null || !children.containsKey(childName); final Node child = new Node(childName, (!fqn.equals(SSEPARATOR) ? fqn : "") + SEPARATOR + childName, this, ephemeral, data); for (Listener listener : pendingListeners.removeAll(child.fqn)) child.addListener(listener); addChild(child); return child; } synchronized Node removeChild(String childName) { if (childName != null && children != null) { final Node child = children.remove(childName); child.notifyNodeRemoved(); return child; } return null; } synchronized void removeAll() { children = null; } public void addListener(Listener listener) { synchronized (this) { if (listeners == null) listeners = new CopyOnWriteArrayList<Listener>(); } if (!listeners.contains(listener)) listeners.add(listener); } public void removeListener(Listener listener) { synchronized (this) { if (listeners == null) return; } listeners.remove(listener); } void notifyNodeAdded() { if (listeners != null) { for (Listener listener : listeners) listener.nodeAdded(fqn); } if (parent != null && parent.listeners != null) { for (Listener listener : parent.listeners) listener.nodeChildAdded(parent.fqn, name); } } void notifyNodeRemoved() { notifyNodeRemoved1(); if (parent != null && parent.listeners != null) { for (Listener listener : parent.listeners) listener.nodeChildDeleted(parent.fqn, name); } } private void notifyNodeRemoved1() { if (children != null) { for (Node child : children.values()) child.notifyNodeRemoved1(); } if (listeners != null) { for (Listener listener : listeners) listener.nodeDeleted(fqn); } } void notifyNodeModified() { if (listeners != null && parent.listeners != null) { for (Listener listener : listeners) listener.nodeUpdated(fqn); } if (parent != null && parent.listeners != null) { for (Listener listener : parent.listeners) listener.nodeChildUpdated(parent.fqn, name); } } synchronized StringBuilder print(StringBuilder sb, int indent) { for (int i = 0; i < indent; i++) sb.append(' '); sb.append(SEPARATOR).append(name); if (children != null) { for (Node n : children.values()) { sb.append('\n'); n.print(sb, indent + INDENT); } } return sb; } @Override public synchronized String toString() { return "Node{" + "name: " + name + ", fqn: " + fqn + ", data: " + Arrays.toString(data) + '}'; } } }