2 * jSite2 - FcpCollector.java -
3 * Copyright © 2008 David Roden
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 package net.pterodactylus.jsite.core;
22 import java.beans.PropertyChangeEvent;
23 import java.beans.PropertyChangeListener;
25 import java.io.FileInputStream;
26 import java.io.FileOutputStream;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.io.OutputStream;
30 import java.net.UnknownHostException;
31 import java.util.ArrayList;
32 import java.util.Collections;
33 import java.util.HashMap;
34 import java.util.Iterator;
35 import java.util.List;
37 import java.util.Properties;
38 import java.util.logging.Level;
39 import java.util.logging.Logger;
41 import net.pterodactylus.fcp.highlevel.HighLevelClient;
42 import net.pterodactylus.fcp.highlevel.HighLevelClientListener;
43 import net.pterodactylus.util.io.Closer;
44 import net.pterodactylus.util.logging.Logging;
49 * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
52 public class NodeManager implements Iterable<Node>, PropertyChangeListener, HighLevelClientListener {
55 private static final Logger logger = Logging.getLogger(NodeManager.class.getName());
57 /** The FCP client name. */
58 private final String clientName;
60 /** The directory for the configuration. */
61 private final String directory;
63 /** Object used for synchronization. */
64 private final Object syncObject = new Object();
66 /** Node listeners. */
67 private List<NodeListener> nodeListeners = Collections.synchronizedList(new ArrayList<NodeListener>());
70 private List<Node> nodes = Collections.synchronizedList(new ArrayList<Node>());
72 /** All FCP connections. */
73 private Map<Node, HighLevelClient> nodeClients = Collections.synchronizedMap(new HashMap<Node, HighLevelClient>());
75 /** Maps nodes to high-level clients. */
76 private Map<HighLevelClient, Node> clientNodes = Collections.synchronizedMap(new HashMap<HighLevelClient, Node>());
79 * Creates a new FCP collector.
82 * The name of the FCP client
84 * The directory in which to store the nodes
86 public NodeManager(String clientName, String directory) {
87 this.clientName = clientName;
88 this.directory = directory;
96 * Adds the given listener to the list of listeners.
101 public void addNodeListener(NodeListener nodeListener) {
102 nodeListeners.add(nodeListener);
106 * Removes the given listener from the list of listeners.
108 * @param nodeListener
109 * The listener to remove
111 public void removeNodeListener(NodeListener nodeListener) {
112 nodeListeners.remove(nodeListener);
116 * Notifies all listeners that a node was added.
119 * The node that was added.
121 private void fireNodeAdded(Node node) {
122 for (NodeListener nodeListener: nodeListeners) {
123 nodeListener.nodeAdded(node);
128 * Notifies all listeners that a node was removed.
131 * The node that was removed
133 private void fireNodeRemoved(Node node) {
134 for (NodeListener nodeListener: nodeListeners) {
135 nodeListener.nodeRemoved(node);
140 * Notifies all listeners that the given node was connected.
143 * The node that is now connected
145 private void fireNodeConnected(Node node) {
146 for (NodeListener nodeListener: nodeListeners) {
147 nodeListener.nodeConnected(node);
152 * Notifies all listeners that a connection to a node has failed.
155 * The node that could not be connected
157 * The cause of the failure
159 private void fireNodeConnectionFailed(Node node, Throwable cause) {
160 for (NodeListener nodeListener: nodeListeners) {
161 nodeListener.nodeConnectionFailed(node, cause);
166 * Notifies all listeners that the given node was disconnected.
169 * The node that is now disconnected
171 * The exception that caused the disconnect, or <code>null</code>
172 * if there was no exception
174 private void fireNodeDisconnected(Node node, Throwable throwable) {
175 for (NodeListener nodeListener: nodeListeners) {
176 nodeListener.nodeDisconnected(node, throwable);
185 * Returns the directory in which the nodes are stored.
187 * @return The directory the nodes are stored in
189 public String getDirectory() {
194 * Checks whether the given node is already connected.
198 * @return <code>true</code> if the node is already connected,
199 * <code>false</code> otherwise
201 public boolean hasNode(Node node) {
202 return nodes.contains(node);
208 public Iterator<Node> iterator() {
209 return nodes.iterator();
219 * @throws IOException
220 * if an I/O error occurs loading the nodes
222 public void load() throws IOException {
223 File directoryFile = new File(directory);
224 File nodeFile = new File(directoryFile, "nodes.properties");
225 if (!nodeFile.exists() || !nodeFile.isFile() || !nodeFile.canRead()) {
228 Properties nodeProperties = new Properties();
229 InputStream nodeInputStream = null;
231 nodeInputStream = new FileInputStream(nodeFile);
232 nodeProperties.load(nodeInputStream);
234 Closer.close(nodeInputStream);
237 List<Node> loadedNodes = new ArrayList<Node>();
238 while (nodeProperties.containsKey("nodes." + ++nodeIndex + ".name")) {
239 String nodePrefix = "nodes." + nodeIndex;
240 String nodeName = nodeProperties.getProperty(nodePrefix + ".name");
241 if (!Verifier.verifyNodeName(nodeName)) {
242 logger.log(Level.WARNING, "invalid node name “" + nodeName + "”, skipping…");
245 String nodeHostname = nodeProperties.getProperty(nodePrefix + ".hostname");
246 if (!Verifier.verifyHostname(nodeHostname)) {
247 logger.log(Level.WARNING, "invalid host name “" + nodeHostname + "”");
248 /* not fatal, might be valid later on. */
250 String nodePortString = nodeProperties.getProperty(nodePrefix + ".port");
251 if (!Verifier.verifyPort(nodePortString)) {
252 logger.log(Level.WARNING, "invalid port number “" + nodePortString + "”, skipping…");
257 nodePort = Integer.valueOf(nodePortString);
258 } catch (NumberFormatException nfe1) {
259 /* shouldn't happen, port number was checked before. */
260 logger.log(Level.SEVERE, "invalid port number “" + nodePortString + "”, check failed! skipping…");
263 Node newNode = new Node();
264 newNode.setName(nodeName);
265 newNode.setHostname(nodeHostname);
266 newNode.setPort(nodePort);
267 loadedNodes.add(newNode);
269 logger.fine("loaded " + loadedNodes.size() + " nodes from config");
270 synchronized (syncObject) {
272 for (Node node: loadedNodes) {
279 * Saves all configured nodes.
281 * @throws IOException
282 * if an I/O error occurs saving the nodes
284 public void save() throws IOException {
285 File directoryFile = new File(directory);
286 if (!directoryFile.exists()) {
287 if (!directoryFile.mkdirs()) {
288 throw new IOException("could not create directory: " + directory);
291 Properties nodeProperties = new Properties();
293 for (Node node: nodes) {
294 String nodePrefix = "nodes." + ++nodeIndex;
295 nodeProperties.setProperty(nodePrefix + ".name", node.getName());
296 nodeProperties.setProperty(nodePrefix + ".hostname", node.getHostname());
297 nodeProperties.setProperty(nodePrefix + ".port", String.valueOf(node.getPort()));
299 File projectFile = new File(directoryFile, "nodes.properties");
300 OutputStream nodeOutputStream = null;
302 nodeOutputStream = new FileOutputStream(projectFile);
303 nodeProperties.store(nodeOutputStream, "jSite nodes");
305 Closer.close(nodeOutputStream);
310 * Adds the given node to this manager.
312 * @see #connect(Node)
314 * The node to connect to
315 * @return <code>true</code> if the node was added, <code>false</code>
316 * if the node was not added because it was already known
318 public boolean addNode(Node node) {
319 if (nodes.contains(node)) {
320 logger.warning("was told to add already known node: " + node);
323 node.addPropertyChangeListener(this);
324 HighLevelClient highLevelClient = new HighLevelClient(clientName);
326 clientNodes.put(highLevelClient, node);
327 nodeClients.put(node, highLevelClient);
328 highLevelClient.addHighLevelClientListener(this);
334 * Removes the given node from the node manager, disconnecting it if it is
335 * currently connected.
340 public void removeNode(Node node) {
341 synchronized (syncObject) {
342 if (!nodes.contains(node)) {
345 if (nodeClients.containsKey(node)) {
348 node.removePropertyChangeListener(this);
349 fireNodeRemoved(node);
354 * Tries to establish a connection with the given node.
357 * The node to connect to
359 public void connect(Node node) {
360 HighLevelClient highLevelClient;
361 highLevelClient = nodeClients.get(node);
362 if (highLevelClient == null) {
363 logger.warning("was told to connect to unknown node: " + node);
367 highLevelClient.connect(node.getHostname(), node.getPort());
368 } catch (UnknownHostException uhe1) {
369 fireNodeConnectionFailed(node, uhe1);
370 } catch (IOException ioe1) {
371 fireNodeConnectionFailed(node, ioe1);
376 * Disconnects the given node without removing it.
379 * The node to disconnect
381 public void disconnect(Node node) {
382 synchronized (syncObject) {
383 if (!nodes.contains(node)) {
386 HighLevelClient highLevelClient = nodeClients.get(node);
387 highLevelClient.disconnect();
392 * Returns a list of all nodes.
394 * @return A list of all nodes
396 public List<Node> getNodes() {
397 return Collections.unmodifiableList(nodes);
401 * Returns the high-level client for a given node.
404 * The node to get a high-level client for
405 * @return The high-level client for a node, or <code>null</code> if the
406 * node was disconnected or removed
408 public HighLevelClient getHighLevelClient(Node node) {
409 return nodeClients.get(node);
413 * Returns the node for a high-level client.
415 * @param highLevelClient
416 * The high-level client to get the node for
417 * @return The node for the high-level client, or <code>null</code> if the
418 * high-level client is not known
420 public Node getNode(HighLevelClient highLevelClient) {
421 return clientNodes.get(highLevelClient);
429 // INTERFACE HighLevelClientListener
435 public void clientConnected(HighLevelClient highLevelClient) {
436 logger.log(Level.FINER, "clientConnected(c=" + highLevelClient + ")");
437 Node node = clientNodes.get(highLevelClient);
439 logger.log(Level.WARNING, "got event for unknown client");
442 fireNodeConnected(node);
448 public void clientDisconnected(HighLevelClient highLevelClient, Throwable throwable) {
449 logger.log(Level.FINER, "clientDisconnected(c=" + highLevelClient + ",t=" + throwable + ")");
450 synchronized (syncObject) {
451 Node node = clientNodes.get(highLevelClient);
453 logger.log(Level.WARNING, "got event for unknown client");
456 fireNodeDisconnected(node, throwable);
461 // INTERFACE PropertyChangeListener
467 public void propertyChange(PropertyChangeEvent propertyChangeEvent) {
468 Object eventSource = propertyChangeEvent.getSource();
469 if (eventSource instanceof Node) {
470 String propertyName = propertyChangeEvent.getPropertyName();
471 if ("hostname".equals(propertyName) || "port".equals(propertyName)) {
472 Node node = (Node) eventSource;
473 HighLevelClient highLevelClient = nodeClients.get(node);
474 if (highLevelClient == null) {
475 logger.log(Level.WARNING, "got property change event for unknown node: " + node);
478 if (highLevelClient.isConnected()) {
479 highLevelClient.disconnect();
481 highLevelClient.connect(node.getHostname(), node.getPort());
482 } catch (UnknownHostException uhe1) {
483 fireNodeConnectionFailed(node, uhe1);
484 } catch (IOException ioe1) {
485 fireNodeConnectionFailed(node, ioe1);