X-Git-Url: https://git.pterodactylus.net/?a=blobdiff_plain;f=src%2Fnet%2Fpterodactylus%2Fjsite%2Fcore%2FNodeManager.java;h=db63df93c2f20a06ac1bf35a6a579a75becc140c;hb=4f10436f68115773d86be96fa3a2040d859ade1f;hp=971d5c514ac11f29c72248873c83dbf087d6c224;hpb=fa2475af2c06ae03a4338a3fc1e327dc1970faf4;p=jSite2.git diff --git a/src/net/pterodactylus/jsite/core/NodeManager.java b/src/net/pterodactylus/jsite/core/NodeManager.java index 971d5c5..db63df9 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; @@ -38,18 +40,20 @@ import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; -import net.pterodactylus.fcp.highlevel.HighLevelClient; -import net.pterodactylus.fcp.highlevel.HighLevelClientListener; +import net.pterodactylus.fcp.SSKKeypair; +import net.pterodactylus.fcp.highlevel.FcpClient; +import net.pterodactylus.fcp.highlevel.FcpException; +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 implements Iterable, HighLevelClientListener { +public class NodeManager implements Iterable, PropertyChangeListener { /** Logger. */ private static final Logger logger = Logging.getLogger(NodeManager.class.getName()); @@ -63,24 +67,24 @@ public class NodeManager implements Iterable, HighLevelClientListener { /** Object used for synchronization. */ private final Object syncObject = new Object(); - /** Node listeners. */ - private List nodeListeners = Collections.synchronizedList(new ArrayList()); + /** Node listener support. */ + private final NodeListenerSupport nodeListenerManager = new NodeListenerSupport(); /** All nodes. */ - private List nodes = Collections.synchronizedList(new ArrayList()); + private final List nodes = Collections.synchronizedList(new ArrayList()); - /** All FCP connections. */ - private Map nodeClients = Collections.synchronizedMap(new HashMap()); + /** Map from node ID to node. */ + private final Map idNodes = Collections.synchronizedMap(new HashMap()); - /** Keeps track of which connection is in use right now. */ - private Set usedConnections = Collections.synchronizedSet(new HashSet()); + /** Map from node to client. */ + private final Map nodeClients = Collections.synchronizedMap(new HashMap()); - /** Maps nodes to high-level clients. */ - private Map clientNodes = Collections.synchronizedMap(new HashMap()); + /** Collection of currently connected nodes. */ + private final Set connectedNodes = Collections.synchronizedSet(new HashSet()); /** * Creates a new FCP collector. - * + * * @param clientName * The name of the FCP client * @param directory @@ -97,73 +101,22 @@ public class NodeManager implements Iterable, HighLevelClientListener { /** * Adds the given listener to the list of listeners. - * + * * @param nodeListener * The listener to add */ public void addNodeListener(NodeListener nodeListener) { - nodeListeners.add(nodeListener); + nodeListenerManager.addListener(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 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); - } + nodeListenerManager.removeListener(nodeListener); } // @@ -172,7 +125,7 @@ public class NodeManager implements Iterable, HighLevelClientListener { /** * Returns the directory in which the nodes are stored. - * + * * @return The directory the nodes are stored in */ public String getDirectory() { @@ -181,7 +134,7 @@ public class NodeManager implements Iterable, HighLevelClientListener { /** * Checks whether the given node is already connected. - * + * * @param node * The node to check * @return true if the node is already connected, @@ -192,6 +145,18 @@ public class NodeManager implements Iterable, HighLevelClientListener { } /** + * Returns whether the given node is currently connected. + * + * @param node + * The node to check + * @return true if the node is currently connected, + * false otherwise + */ + public boolean isNodeConnected(Node node) { + return connectedNodes.contains(node); + } + + /** * {@inheritDoc} */ public Iterator iterator() { @@ -204,11 +169,12 @@ public class NodeManager implements Iterable, HighLevelClientListener { /** * Loads nodes. - * + * * @throws IOException * if an I/O error occurs loading the nodes */ public void load() throws IOException { + logger.log(Level.FINEST, "load()"); File directoryFile = new File(directory); File nodeFile = new File(directoryFile, "nodes.properties"); if (!nodeFile.exists() || !nodeFile.isFile() || !nodeFile.canRead()) { @@ -226,6 +192,10 @@ public class NodeManager implements Iterable, HighLevelClientListener { 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…"); @@ -250,15 +220,16 @@ public class NodeManager implements Iterable, HighLevelClientListener { 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"); + logger.log(Level.FINE, "loaded " + loadedNodes.size() + " nodes from config"); synchronized (syncObject) { nodes.clear(); - for (Node node: nodes) { + for (Node node : loadedNodes) { addNode(node); } } @@ -266,11 +237,12 @@ public class NodeManager implements Iterable, HighLevelClientListener { /** * Saves all configured nodes. - * + * * @throws IOException * if an I/O error occurs saving the nodes */ public void save() throws IOException { + logger.log(Level.FINEST, "save()"); File directoryFile = new File(directory); if (!directoryFile.exists()) { if (!directoryFile.mkdirs()) { @@ -279,8 +251,9 @@ public class NodeManager implements Iterable, HighLevelClientListener { } 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())); @@ -297,82 +270,96 @@ public class NodeManager implements Iterable, HighLevelClientListener { /** * Adds the given node to this manager. - * + * * @see #connect(Node) * @param node * The node to connect to - * @throws UnknownHostException - * if the hostname of the node can not be resolved + * @return true if the node was added, false if + * the node was not added because it was already known */ - public void addNode(Node node) throws UnknownHostException { - synchronized (syncObject) { - if (!nodes.contains(node)) { - HighLevelClient highLevelClient= new HighLevelClient(clientName, node.getHostname(), node.getPort()); - nodes.add(node); - clientNodes.put(highLevelClient, node); - nodeClients.put(node, highLevelClient); - highLevelClient.addHighLevelClientListener(this); - fireNodeAdded(node); - } + public boolean addNode(Node node) { + logger.log(Level.FINEST, "addNode(node=" + node + ")"); + if (nodes.contains(node)) { + logger.log(Level.WARNING, "was told to add already known node: " + node); + return false; } + node.addPropertyChangeListener(this); + nodes.add(node); + idNodes.put(node.getId(), node); + nodeListenerManager.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) { + logger.log(Level.FINEST, "removeNode(node=" + node + ")"); synchronized (syncObject) { if (!nodes.contains(node)) { return; } - if (nodeClients.containsKey(node)) { - disconnect(node); - } - fireNodeRemoved(node); + nodes.remove(node); + idNodes.remove(node.getId()); + node.removePropertyChangeListener(this); + nodeListenerManager.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; - synchronized (syncObject) { - highLevelClient = nodeClients.get(node); + logger.log(Level.FINEST, "connect(node=" + node + ")"); + if (!nodes.contains(node)) { + logger.log(Level.WARNING, "Was told to connect to node (" + node + ") I don’t know about!"); + return; } try { - highLevelClient.connect(); + FcpClient fcpClient = new FcpClient(clientName, node.getHostname(), node.getPort()); + fcpClient.connect(); + nodeClients.put(node, fcpClient); + nodeListenerManager.fireNodeConnected(node); + } catch (UnknownHostException uhe1) { + nodeListenerManager.fireNodeConnectionFailed(node, uhe1); } catch (IOException ioe1) { - fireNodeDisconnected(node, ioe1); + nodeListenerManager.fireNodeConnectionFailed(node, ioe1); + } catch (FcpException fe1) { + nodeListenerManager.fireNodeConnectionFailed(node, fe1); } } /** * 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(); + logger.log(Level.FINEST, "disconnect(node=" + node + ")"); + if (!nodes.contains(node)) { + logger.log(Level.WARNING, "Was told to disconnect from a node (" + node + ") I don’t know about!"); + return; + } + FcpClient fcpClient = nodeClients.remove(node); + if (fcpClient == null) { + logger.log(Level.WARNING, "No FCP client for node (" + node + ")!"); + return; } + fcpClient.disconnect(); + nodeListenerManager.fireNodeDisconnected(node, null); } /** * Returns a list of all nodes. - * + * * @return A list of all nodes */ public List getNodes() { @@ -380,134 +367,74 @@ public class NodeManager implements Iterable, HighLevelClientListener { } /** - * “Borrows” a high-level client for the given node. A borrowed client - * has to be returned to the node manager using - * {@link #returnHighLevelClient(HighLevelClient)} when it is no longer in - * use, i.e. after a message has been sent! This method will block until a - * high-level client for the given node is available. - * - * @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 + * Returns the node identified by the given ID. + * + * @param id + * The ID of the node + * @return The node with the given ID, or null if no such node + * was found */ - public HighLevelClient borrowHighLevelClient(Node node) { - synchronized (syncObject) { - if (!nodeClients.containsKey(node)) { - return null; - } - HighLevelClient highLevelClient = nodeClients.get(node); - while (nodeClients.containsKey(node) && usedConnections.contains(highLevelClient)) { - try { - syncObject.wait(); - } catch (InterruptedException ie1) { - /* ignore. TODO - check. */ - } - } - if (!nodeClients.containsKey(node)) { - return null; - } - usedConnections.add(highLevelClient); - return highLevelClient; - } + Node getNode(String id) { + return idNodes.get(id); } /** - * Returns a borrowed high-level client. - * - * @see #borrowHighLevelClient(Node) - * @param highLevelClient - * The high-level client to return + * Returns the FCP client for the given node. + * + * @param node + * The node to get the FCP client for + * @return The FCP client for the given node, or {@code null} if the node + * does not have an associated FCP client */ - public void returnHighLevelClient(HighLevelClient highLevelClient) { - synchronized (syncObject) { - if (!clientNodes.containsKey(highLevelClient)) { - return; - } - usedConnections.remove(highLevelClient); - syncObject.notifyAll(); - } + FcpClient getFcpClient(Node node) { + return nodeClients.get(node); } - // - // PRIVATE METHODS - // - /** - * 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 + * 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 no connected node */ - @SuppressWarnings("unused") - private HighLevelClient findUnusedClient(boolean wait, boolean markAsUsed) { - synchronized (syncObject) { - HighLevelClient freeHighLevelClient = null; - while (freeHighLevelClient == null) { - for (HighLevelClient highLevelClient: nodeClients.values()) { - if (!usedConnections.contains(highLevelClient)) { - freeHighLevelClient = highLevelClient; - break; - } - } - if (freeHighLevelClient != null) { - if (markAsUsed) { - usedConnections.add(freeHighLevelClient); - } - return freeHighLevelClient; - } - if (!wait) { - return null; - } - try { - syncObject.wait(); - } catch (InterruptedException e) { - /* ignore, just re-check. */ - } + public String[] generateKeyPair() throws IOException, JSiteException { + logger.log(Level.FINEST, "generateKeyPair()"); + if (nodes.isEmpty()) { + throw new NoNodeException("no node configured"); + } + FcpException fcpException = null; + for (FcpClient fcpClient : nodeClients.values()) { + try { + SSKKeypair sskKeypair = fcpClient.generateKeyPair(); + return new String[] { sskKeypair.getInsertURI(), sskKeypair.getRequestURI() }; + } catch (FcpException fcpe1) { + fcpException = fcpe1; } - /* we never get here, but the compiler doesn't realize. */ - return null; } + throw new JSiteException("Could not get SSK key pair from any node.", fcpException); } // - // INTERFACE HighLevelClientListener + // PRIVATE METHODS // - /** - * {@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); - } + // + // INTERFACE PropertyChangeListener + // /** * {@inheritDoc} */ - public void clientDisconnected(HighLevelClient highLevelClient, Throwable throwable) { - logger.log(Level.FINER, "clientDisconnected(c=" + highLevelClient + ",t=" + throwable + ")"); - synchronized (syncObject) { - Node node = clientNodes.remove(highLevelClient); - if (node == null) { - logger.log(Level.WARNING, "got event for unknown client"); - return; + public void propertyChange(PropertyChangeEvent propertyChangeEvent) { + Object eventSource = propertyChangeEvent.getSource(); + if (eventSource instanceof Node) { + String propertyName = propertyChangeEvent.getPropertyName(); + if ("hostname".equals(propertyName) || "port".equals(propertyName)) { + /* TODO - reconnect. */ } - nodeClients.remove(node); - usedConnections.remove(highLevelClient); - fireNodeDisconnected(node, throwable); } }