X-Git-Url: https://git.pterodactylus.net/?a=blobdiff_plain;f=src%2Fnet%2Fpterodactylus%2Fjsite%2Fcore%2FNodeManager.java;h=4224cf16888194217c02e2401b48060f4e719200;hb=464bd5d54b6b55a84dd7245f48164ec0a72cd49c;hp=71bb95dcef39957dec5dae0de7001dc6f6a96a40;hpb=029548705a1fa60731ad7f492103333acdd48001;p=jSite2.git diff --git a/src/net/pterodactylus/jsite/core/NodeManager.java b/src/net/pterodactylus/jsite/core/NodeManager.java index 71bb95d..4224cf1 100644 --- a/src/net/pterodactylus/jsite/core/NodeManager.java +++ b/src/net/pterodactylus/jsite/core/NodeManager.java @@ -19,6 +19,8 @@ package net.pterodactylus.jsite.core; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -29,29 +31,31 @@ import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; -import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; -import net.pterodactylus.fcp.highlevel.ConnectResult; -import net.pterodactylus.fcp.highlevel.HighLevelCallback; import net.pterodactylus.fcp.highlevel.HighLevelClient; +import net.pterodactylus.fcp.highlevel.HighLevelClientListener; +import net.pterodactylus.fcp.highlevel.HighLevelException; +import net.pterodactylus.fcp.highlevel.KeyGenerationResult; +import net.pterodactylus.jsite.util.IdGenerator; import net.pterodactylus.util.io.Closer; +import net.pterodactylus.util.logging.Logging; +import net.pterodactylus.util.number.Hex; /** * TODO - * + * * @author David ‘Bombe’ Roden <bombe@freenetproject.org> - * @version $Id$ */ -public class NodeManager { +public class NodeManager implements Iterable, PropertyChangeListener, HighLevelClientListener { /** Logger. */ - private static final Logger logger = Logger.getLogger(NodeManager.class.getName()); + private static final Logger logger = Logging.getLogger(NodeManager.class.getName()); /** The FCP client name. */ private final String clientName; @@ -62,21 +66,21 @@ public class NodeManager { /** Object used for synchronization. */ private final Object syncObject = new Object(); + /** Node listeners. */ + private List nodeListeners = Collections.synchronizedList(new ArrayList()); + /** All nodes. */ private List nodes = Collections.synchronizedList(new ArrayList()); /** All FCP connections. */ - private Map nodeConnections = Collections.synchronizedMap(new HashMap()); - - /** Keeps track of which connection is in use right now. */ - private Set usedConnections = Collections.synchronizedSet(new HashSet()); + private Map nodeClients = Collections.synchronizedMap(new HashMap()); /** Maps nodes to high-level clients. */ private Map clientNodes = Collections.synchronizedMap(new HashMap()); /** * Creates a new FCP collector. - * + * * @param clientName * The name of the FCP client * @param directory @@ -88,12 +92,101 @@ public class NodeManager { } // + // EVENT MANAGEMENT + // + + /** + * Adds the given listener to the list of listeners. + * + * @param nodeListener + * The listener to add + */ + public void addNodeListener(NodeListener nodeListener) { + nodeListeners.add(nodeListener); + } + + /** + * Removes the given listener from the list of listeners. + * + * @param nodeListener + * The listener to remove + */ + public void removeNodeListener(NodeListener nodeListener) { + nodeListeners.remove(nodeListener); + } + + /** + * Notifies all listeners that a node was added. + * + * @param node + * The node that was added. + */ + private void fireNodeAdded(Node node) { + for (NodeListener nodeListener : nodeListeners) { + nodeListener.nodeAdded(node); + } + } + + /** + * Notifies all listeners that a node was removed. + * + * @param node + * The node that was removed + */ + private void fireNodeRemoved(Node node) { + for (NodeListener nodeListener : nodeListeners) { + nodeListener.nodeRemoved(node); + } + } + + /** + * Notifies all listeners that the given node was connected. + * + * @param node + * The node that is now connected + */ + private void fireNodeConnected(Node node) { + for (NodeListener nodeListener : nodeListeners) { + nodeListener.nodeConnected(node); + } + } + + /** + * Notifies all listeners that a connection to a node has failed. + * + * @param node + * The node that could not be connected + * @param cause + * The cause of the failure + */ + private void fireNodeConnectionFailed(Node node, Throwable cause) { + for (NodeListener nodeListener : nodeListeners) { + nodeListener.nodeConnectionFailed(node, cause); + } + } + + /** + * Notifies all listeners that the given node was disconnected. + * + * @param node + * The node that is now disconnected + * @param throwable + * The exception that caused the disconnect, or null + * if there was no exception + */ + private void fireNodeDisconnected(Node node, Throwable throwable) { + for (NodeListener nodeListener : nodeListeners) { + nodeListener.nodeDisconnected(node, throwable); + } + } + + // // ACCESSORS // /** * Returns the directory in which the nodes are stored. - * + * * @return The directory the nodes are stored in */ public String getDirectory() { @@ -102,7 +195,7 @@ public class NodeManager { /** * Checks whether the given node is already connected. - * + * * @param node * The node to check * @return true if the node is already connected, @@ -112,13 +205,20 @@ public class NodeManager { return nodes.contains(node); } + /** + * {@inheritDoc} + */ + public Iterator iterator() { + return nodes.iterator(); + } + // // ACTIONS // /** * Loads nodes. - * + * * @throws IOException * if an I/O error occurs loading the nodes */ @@ -140,6 +240,10 @@ public class NodeManager { List loadedNodes = new ArrayList(); while (nodeProperties.containsKey("nodes." + ++nodeIndex + ".name")) { String nodePrefix = "nodes." + nodeIndex; + String nodeId = nodeProperties.getProperty(nodePrefix + ".id"); + if (nodeId == null) { + nodeId = Hex.toHex(IdGenerator.generateId()); + } String nodeName = nodeProperties.getProperty(nodePrefix + ".name"); if (!Verifier.verifyNodeName(nodeName)) { logger.log(Level.WARNING, "invalid node name “" + nodeName + "”, skipping…"); @@ -164,20 +268,24 @@ public class NodeManager { continue; } Node newNode = new Node(); + newNode.setId(nodeId); newNode.setName(nodeName); newNode.setHostname(nodeHostname); newNode.setPort(nodePort); loadedNodes.add(newNode); } + logger.fine("loaded " + loadedNodes.size() + " nodes from config"); synchronized (syncObject) { nodes.clear(); - nodes.addAll(loadedNodes); + for (Node node : loadedNodes) { + addNode(node); + } } } /** * Saves all configured nodes. - * + * * @throws IOException * if an I/O error occurs saving the nodes */ @@ -190,8 +298,9 @@ public class NodeManager { } Properties nodeProperties = new Properties(); int nodeIndex = -1; - for (Node node: nodes) { + for (Node node : nodes) { String nodePrefix = "nodes." + ++nodeIndex; + nodeProperties.setProperty(nodePrefix + ".id", node.getId()); nodeProperties.setProperty(nodePrefix + ".name", node.getName()); nodeProperties.setProperty(nodePrefix + ".hostname", node.getHostname()); nodeProperties.setProperty(nodePrefix + ".port", String.valueOf(node.getPort())); @@ -207,92 +316,213 @@ public class NodeManager { } /** - * Adds a connection to the given node. The connection is made instantly so - * this method may block. If the node can not be connected, it will not be - * added to the list of nodes. - * + * Adds the given node to this manager. + * + * @see #connect(Node) * @param node * The node to connect to - * @return true if the connection to the node could be - * established - * @throws UnknownHostException - * if the hostname of the node can not be resolved - * @throws IOException - * if an I/O error occurs connecting to the node + * @return true if the node was added, false + * if the node was not added because it was already known */ - public boolean addNode(Node node) throws UnknownHostException, IOException { + public boolean addNode(Node node) { if (nodes.contains(node)) { - return true; + logger.warning("was told to add already known node: " + node); + return false; } - HighLevelClient highLevelClient = new HighLevelClient(clientName, node.getHostname(), node.getPort()); - HighLevelCallback connectCallback = highLevelClient.connect(); - ConnectResult connectResult = null; - while (connectResult == null) { - try { - connectResult = connectCallback.getResult(); - } catch (InterruptedException e) { - /* ignore. */ + node.addPropertyChangeListener(this); + HighLevelClient highLevelClient = new HighLevelClient(clientName); + nodes.add(node); + clientNodes.put(highLevelClient, node); + nodeClients.put(node, highLevelClient); + highLevelClient.addHighLevelClientListener(this); + fireNodeAdded(node); + return true; + } + + /** + * Removes the given node from the node manager, disconnecting it if it is + * currently connected. + * + * @param node + * The node to remove + */ + public void removeNode(Node node) { + synchronized (syncObject) { + if (!nodes.contains(node)) { + return; } + if (nodeClients.containsKey(node)) { + disconnect(node); + } + nodes.remove(node); + node.removePropertyChangeListener(this); + fireNodeRemoved(node); + } + } + + /** + * Tries to establish a connection with the given node. + * + * @param node + * The node to connect to + */ + public void connect(Node node) { + HighLevelClient highLevelClient; + highLevelClient = nodeClients.get(node); + if (highLevelClient == null) { + logger.warning("was told to connect to unknown node: " + node); + return; } - if (connectResult.isConnected()) { - synchronized (syncObject) { - nodes.add(node); - nodeConnections.put(node, highLevelClient); - clientNodes.put(highLevelClient, node); + try { + highLevelClient.connect(node.getHostname(), node.getPort()); + } catch (UnknownHostException uhe1) { + fireNodeConnectionFailed(node, uhe1); + } catch (IOException ioe1) { + fireNodeConnectionFailed(node, ioe1); + } + } + + /** + * Disconnects the given node without removing it. + * + * @param node + * The node to disconnect + */ + public void disconnect(Node node) { + synchronized (syncObject) { + if (!nodes.contains(node)) { + return; } + HighLevelClient highLevelClient = nodeClients.get(node); + highLevelClient.disconnect(); } - return connectResult.isConnected(); } /** * Returns a list of all nodes. - * + * * @return A list of all nodes */ public List getNodes() { - return new ArrayList(clientNodes.values()); + return Collections.unmodifiableList(nodes); + } + + /** + * Returns the high-level client for a given node. + * + * @param node + * The node to get a high-level client for + * @return The high-level client for a node, or null if the + * node was disconnected or removed + */ + public HighLevelClient getHighLevelClient(Node node) { + return nodeClients.get(node); + } + + /** + * Returns the node for a high-level client. + * + * @param highLevelClient + * The high-level client to get the node for + * @return The node for the high-level client, or null if the + * high-level client is not known + */ + public Node getNode(HighLevelClient highLevelClient) { + return clientNodes.get(highLevelClient); + } + + /** + * Generates a new SSK key pair. + * + * @return An array with the private key at index 0 and the + * public key at index 1 + * @throws IOException + * if an I/O error occurs communicating with the node + * @throws JSiteException + * if there is a problem with the node + */ + public String[] generateKeyPair() throws IOException, JSiteException { + if (nodes.isEmpty()) { + throw new NoNodeException("no node configured"); + } + Node node = nodes.get(0); + HighLevelClient highLevelClient = nodeClients.get(node); + try { + KeyGenerationResult keyGenerationResult = highLevelClient.generateKey().getResult(); + return new String[] { keyGenerationResult.getInsertURI(), keyGenerationResult.getRequestURI() }; + } catch (HighLevelException hle1) { + throw new BackendException(hle1); + } catch (InterruptedException e) { + /* ignore. */ + } + return null; } // // PRIVATE METHODS // + // + // INTERFACE HighLevelClientListener + // + + /** + * {@inheritDoc} + */ + public void clientConnected(HighLevelClient highLevelClient) { + logger.log(Level.FINER, "clientConnected(c=" + highLevelClient + ")"); + Node node = clientNodes.get(highLevelClient); + if (node == null) { + logger.log(Level.WARNING, "got event for unknown client"); + return; + } + fireNodeConnected(node); + } + /** - * Finds a currently unused high-level client, optionally waiting until a - * client is free and marking it used. - * - * @param wait - * true to wait for a free connection, - * false to return null - * @param markAsUsed - * true to mark the connection as used before - * returning it, false not to mark it - * @return An unused FCP connection, or null if no connection - * could be found + * {@inheritDoc} */ - @SuppressWarnings("unused") - private HighLevelClient findUnusedClient(boolean wait, boolean markAsUsed) { + public void clientDisconnected(HighLevelClient highLevelClient, Throwable throwable) { + logger.log(Level.FINER, "clientDisconnected(c=" + highLevelClient + ",t=" + throwable + ")"); synchronized (syncObject) { - HighLevelClient freeHighLevelClient = null; - while (freeHighLevelClient == null) { - for (HighLevelClient highLevelClient: nodeConnections.values()) { - if (!usedConnections.contains(highLevelClient)) { - freeHighLevelClient = highLevelClient; - break; - } + Node node = clientNodes.get(highLevelClient); + if (node == null) { + logger.log(Level.WARNING, "got event for unknown client"); + return; + } + fireNodeDisconnected(node, throwable); + } + } + + // + // INTERFACE PropertyChangeListener + // + + /** + * {@inheritDoc} + */ + public void propertyChange(PropertyChangeEvent propertyChangeEvent) { + Object eventSource = propertyChangeEvent.getSource(); + if (eventSource instanceof Node) { + String propertyName = propertyChangeEvent.getPropertyName(); + if ("hostname".equals(propertyName) || "port".equals(propertyName)) { + Node node = (Node) eventSource; + HighLevelClient highLevelClient = nodeClients.get(node); + if (highLevelClient == null) { + logger.log(Level.WARNING, "got property change event for unknown node: " + node); + return; } - if (freeHighLevelClient != null) { - if (markAsUsed) { - usedConnections.add(freeHighLevelClient); + if (highLevelClient.isConnected()) { + highLevelClient.disconnect(); + try { + highLevelClient.connect(node.getHostname(), node.getPort()); + } catch (UnknownHostException uhe1) { + fireNodeConnectionFailed(node, uhe1); + } catch (IOException ioe1) { + fireNodeConnectionFailed(node, ioe1); } - return freeHighLevelClient; - } - if (!wait) { - return null; } } - /* we never get here, but the compiler doesn't realize. */ - return null; } }