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.util.ArrayList;
31 import java.util.Collections;
32 import java.util.HashMap;
33 import java.util.HashSet;
34 import java.util.Iterator;
35 import java.util.List;
37 import java.util.Properties;
39 import java.util.logging.Level;
40 import java.util.logging.Logger;
42 import net.pterodactylus.jsite.util.IdGenerator;
43 import net.pterodactylus.util.io.Closer;
44 import net.pterodactylus.util.logging.Logging;
45 import net.pterodactylus.util.number.Hex;
50 * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
52 public class NodeManager implements Iterable<Node>, PropertyChangeListener {
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 listener support. */
67 private final NodeListenerSupport nodeListenerSupport = new NodeListenerSupport();
70 private final List<Node> nodes = Collections.synchronizedList(new ArrayList<Node>());
72 /** Map from node ID to node. */
73 private final Map<String, Node> idNodes = Collections.synchronizedMap(new HashMap<String, Node>());
75 /** Collection of currently connected nodes. */
76 private final Set<Node> connectedNodes = Collections.synchronizedSet(new HashSet<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 nodeListenerSupport.addListener(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 nodeListenerSupport.removeListener(nodeListener);
120 * Returns the directory in which the nodes are stored.
122 * @return The directory the nodes are stored in
124 public String getDirectory() {
129 * Checks whether the given node is already connected.
133 * @return <code>true</code> if the node is already connected,
134 * <code>false</code> otherwise
136 public boolean hasNode(Node node) {
137 return nodes.contains(node);
141 * Returns whether the given node is currently connected.
145 * @return <code>true</code> if the node is currently connected,
146 * <code>false</code> otherwise
148 public boolean isNodeConnected(Node node) {
149 return connectedNodes.contains(node);
155 public Iterator<Node> iterator() {
156 return nodes.iterator();
166 * @throws IOException
167 * if an I/O error occurs loading the nodes
169 public void load() throws IOException {
170 logger.log(Level.FINEST, "load()");
171 File directoryFile = new File(directory);
172 File nodeFile = new File(directoryFile, "nodes.properties");
173 if (!nodeFile.exists() || !nodeFile.isFile() || !nodeFile.canRead()) {
176 Properties nodeProperties = new Properties();
177 InputStream nodeInputStream = null;
179 nodeInputStream = new FileInputStream(nodeFile);
180 nodeProperties.load(nodeInputStream);
182 Closer.close(nodeInputStream);
185 List<Node> loadedNodes = new ArrayList<Node>();
186 while (nodeProperties.containsKey("nodes." + ++nodeIndex + ".name")) {
187 String nodePrefix = "nodes." + nodeIndex;
188 String nodeId = nodeProperties.getProperty(nodePrefix + ".id");
189 if (nodeId == null) {
190 nodeId = Hex.toHex(IdGenerator.generateId());
192 String nodeName = nodeProperties.getProperty(nodePrefix + ".name");
193 if (!Verifier.verifyNodeName(nodeName)) {
194 logger.log(Level.WARNING, "invalid node name “" + nodeName + "”, skipping…");
197 String nodeHostname = nodeProperties.getProperty(nodePrefix + ".hostname");
198 if (!Verifier.verifyHostname(nodeHostname)) {
199 logger.log(Level.WARNING, "invalid host name “" + nodeHostname + "”");
200 /* not fatal, might be valid later on. */
202 String nodePortString = nodeProperties.getProperty(nodePrefix + ".port");
203 if (!Verifier.verifyPort(nodePortString)) {
204 logger.log(Level.WARNING, "invalid port number “" + nodePortString + "”, skipping…");
209 nodePort = Integer.valueOf(nodePortString);
210 } catch (NumberFormatException nfe1) {
211 /* shouldn't happen, port number was checked before. */
212 logger.log(Level.SEVERE, "invalid port number “" + nodePortString + "”, check failed! skipping…");
215 Node newNode = new Node();
216 newNode.setId(nodeId);
217 newNode.setName(nodeName);
218 newNode.setHostname(nodeHostname);
219 newNode.setPort(nodePort);
220 loadedNodes.add(newNode);
222 logger.log(Level.FINE, "loaded " + loadedNodes.size() + " nodes from config");
223 synchronized (syncObject) {
225 for (Node node : loadedNodes) {
232 * Saves all configured nodes.
234 * @throws IOException
235 * if an I/O error occurs saving the nodes
237 public void save() throws IOException {
238 logger.log(Level.FINEST, "save()");
239 File directoryFile = new File(directory);
240 if (!directoryFile.exists()) {
241 if (!directoryFile.mkdirs()) {
242 throw new IOException("could not create directory: " + directory);
245 Properties nodeProperties = new Properties();
247 for (Node node : nodes) {
248 String nodePrefix = "nodes." + ++nodeIndex;
249 nodeProperties.setProperty(nodePrefix + ".id", node.getId());
250 nodeProperties.setProperty(nodePrefix + ".name", node.getName());
251 nodeProperties.setProperty(nodePrefix + ".hostname", node.getHostname());
252 nodeProperties.setProperty(nodePrefix + ".port", String.valueOf(node.getPort()));
254 File projectFile = new File(directoryFile, "nodes.properties");
255 OutputStream nodeOutputStream = null;
257 nodeOutputStream = new FileOutputStream(projectFile);
258 nodeProperties.store(nodeOutputStream, "jSite nodes");
260 Closer.close(nodeOutputStream);
265 * Adds the given node to this manager.
267 * @see #connect(Node)
269 * The node to connect to
270 * @return <code>true</code> if the node was added, <code>false</code> if
271 * the node was not added because it was already known
273 public boolean addNode(Node node) {
274 logger.log(Level.FINEST, "addNode(node=" + node + ")");
275 if (nodes.contains(node)) {
276 logger.log(Level.WARNING, "was told to add already known node: " + node);
279 node.addPropertyChangeListener(this);
281 idNodes.put(node.getId(), node);
282 nodeListenerSupport.fireNodeAdded(node);
287 * Removes the given node from the node manager, disconnecting it if it is
288 * currently connected.
293 public void removeNode(Node node) {
294 logger.log(Level.FINEST, "removeNode(node=" + node + ")");
295 synchronized (syncObject) {
296 if (!nodes.contains(node)) {
300 idNodes.remove(node.getId());
301 node.removePropertyChangeListener(this);
302 nodeListenerSupport.fireNodeRemoved(node);
307 * Tries to establish a connection with the given node.
310 * The node to connect to
312 public void connect(Node node) {
313 logger.log(Level.FINEST, "connect(node=" + node + ")");
317 * Disconnects the given node without removing it.
320 * The node to disconnect
322 public void disconnect(Node node) {
323 logger.log(Level.FINEST, "disconnect(node=" + node + ")");
327 * Returns a list of all nodes.
329 * @return A list of all nodes
331 public List<Node> getNodes() {
332 return Collections.unmodifiableList(nodes);
336 * Returns the node identified by the given ID.
340 * @return The node with the given ID, or <code>null</code> if no such node
343 Node getNode(String id) {
344 return idNodes.get(id);
348 * Generates a new SSK key pair.
350 * @return An array with the private key at index <code>0</code> and the
351 * public key at index <code>1</code>
352 * @throws IOException
353 * if an I/O error occurs communicating with the node
354 * @throws JSiteException
355 * if there is a problem with the node
357 public String[] generateKeyPair() throws IOException, JSiteException {
358 logger.log(Level.FINEST, "generateKeyPair()");
359 if (nodes.isEmpty()) {
360 throw new NoNodeException("no node configured");
370 // INTERFACE PropertyChangeListener
376 public void propertyChange(PropertyChangeEvent propertyChangeEvent) {
377 Object eventSource = propertyChangeEvent.getSource();
378 if (eventSource instanceof Node) {
379 String propertyName = propertyChangeEvent.getPropertyName();
380 if ("hostname".equals(propertyName) || "port".equals(propertyName)) {
381 /* TODO - reconnect. */