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;
23 import java.io.FileInputStream;
24 import java.io.FileOutputStream;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.OutputStream;
28 import java.net.UnknownHostException;
29 import java.util.ArrayList;
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.HashSet;
33 import java.util.List;
35 import java.util.Properties;
37 import java.util.logging.Level;
38 import java.util.logging.Logger;
40 import net.pterodactylus.fcp.highlevel.ConnectResult;
41 import net.pterodactylus.fcp.highlevel.HighLevelCallback;
42 import net.pterodactylus.fcp.highlevel.HighLevelClient;
43 import net.pterodactylus.util.io.Closer;
48 * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
51 public class NodeManager {
54 private static final Logger logger = Logger.getLogger(NodeManager.class.getName());
56 /** The FCP client name. */
57 private final String clientName;
59 /** The directory for the configuration. */
60 private final String directory;
62 /** Object used for synchronization. */
63 private final Object syncObject = new Object();
66 private List<Node> nodes = Collections.synchronizedList(new ArrayList<Node>());
68 /** All FCP connections. */
69 private Map<Node, HighLevelClient> nodeConnections = Collections.synchronizedMap(new HashMap<Node, HighLevelClient>());
71 /** Keeps track of which connection is in use right now. */
72 private Set<HighLevelClient> usedConnections = Collections.synchronizedSet(new HashSet<HighLevelClient>());
74 /** Maps nodes to high-level clients. */
75 private Map<HighLevelClient, Node> clientNodes = Collections.synchronizedMap(new HashMap<HighLevelClient, Node>());
78 * Creates a new FCP collector.
81 * The name of the FCP client
83 * The directory in which to store the nodes
85 public NodeManager(String clientName, String directory) {
86 this.clientName = clientName;
87 this.directory = directory;
95 * Returns the directory in which the nodes are stored.
97 * @return The directory the nodes are stored in
99 public String getDirectory() {
104 * Checks whether the given node is already connected.
108 * @return <code>true</code> if the node is already connected,
109 * <code>false</code> otherwise
111 public boolean hasNode(Node node) {
112 return nodes.contains(node);
122 * @throws IOException
123 * if an I/O error occurs loading the nodes
125 public void load() throws IOException {
126 File directoryFile = new File(directory);
127 File nodeFile = new File(directoryFile, "nodes.properties");
128 if (!nodeFile.exists() || !nodeFile.isFile() || !nodeFile.canRead()) {
131 Properties nodeProperties = new Properties();
132 InputStream nodeInputStream = null;
134 nodeInputStream = new FileInputStream(nodeFile);
135 nodeProperties.load(nodeInputStream);
137 Closer.close(nodeInputStream);
140 List<Node> loadedNodes = new ArrayList<Node>();
141 while (nodeProperties.containsKey("nodes." + ++nodeIndex + ".name")) {
142 String nodePrefix = "nodes." + nodeIndex;
143 String nodeName = nodeProperties.getProperty(nodePrefix + ".name");
144 if (!Verifier.verifyNodeName(nodeName)) {
145 logger.log(Level.WARNING, "invalid node name “" + nodeName + "”, skipping…");
148 String nodeHostname = nodeProperties.getProperty(nodePrefix + ".hostname");
149 if (!Verifier.verifyHostname(nodeHostname)) {
150 logger.log(Level.WARNING, "invalid host name “" + nodeHostname + "”");
151 /* not fatal, might be valid later on. */
153 String nodePortString = nodeProperties.getProperty(nodePrefix + ".port");
154 if (!Verifier.verifyPort(nodePortString)) {
155 logger.log(Level.WARNING, "invalid port number “" + nodePortString + "”, skipping…");
160 nodePort = Integer.valueOf(nodePortString);
161 } catch (NumberFormatException nfe1) {
162 /* shouldn't happen, port number was checked before. */
163 logger.log(Level.SEVERE, "invalid port number “" + nodePortString + "”, check failed! skipping…");
166 Node newNode = new Node();
167 newNode.setName(nodeName);
168 newNode.setHostname(nodeHostname);
169 newNode.setPort(nodePort);
170 loadedNodes.add(newNode);
172 synchronized (syncObject) {
174 nodes.addAll(loadedNodes);
179 * Saves all configured nodes.
181 * @throws IOException
182 * if an I/O error occurs saving the nodes
184 public void save() throws IOException {
185 File directoryFile = new File(directory);
186 if (!directoryFile.exists()) {
187 if (!directoryFile.mkdirs()) {
188 throw new IOException("could not create directory: " + directory);
191 Properties nodeProperties = new Properties();
193 for (Node node: nodes) {
194 String nodePrefix = "nodes." + ++nodeIndex;
195 nodeProperties.setProperty(nodePrefix + ".name", node.getName());
196 nodeProperties.setProperty(nodePrefix + ".hostname", node.getHostname());
197 nodeProperties.setProperty(nodePrefix + ".port", String.valueOf(node.getPort()));
199 File projectFile = new File(directoryFile, "nodes.properties");
200 OutputStream nodeOutputStream = null;
202 nodeOutputStream = new FileOutputStream(projectFile);
203 nodeProperties.store(nodeOutputStream, "jSite nodes");
205 Closer.close(nodeOutputStream);
210 * Adds a connection to the given node. The connection is made instantly so
211 * this method may block. If the node can not be connected, it will not be
212 * added to the list of nodes.
215 * The node to connect to
216 * @return <code>true</code> if the connection to the node could be
218 * @throws UnknownHostException
219 * if the hostname of the node can not be resolved
220 * @throws IOException
221 * if an I/O error occurs connecting to the node
223 public boolean addNode(Node node) throws UnknownHostException, IOException {
224 if (nodes.contains(node)) {
227 HighLevelClient highLevelClient = new HighLevelClient(clientName, node.getHostname(), node.getPort());
228 HighLevelCallback<ConnectResult> connectCallback = highLevelClient.connect();
229 ConnectResult connectResult = null;
230 while (connectResult == null) {
232 connectResult = connectCallback.getResult();
233 } catch (InterruptedException e) {
237 if (connectResult.isConnected()) {
238 synchronized (syncObject) {
240 nodeConnections.put(node, highLevelClient);
241 clientNodes.put(highLevelClient, node);
244 return connectResult.isConnected();
248 * Returns a list of all nodes.
250 * @return A list of all nodes
252 public List<Node> getNodes() {
253 return new ArrayList<Node>(clientNodes.values());
261 * Finds a currently unused high-level client, optionally waiting until a
262 * client is free and marking it used.
265 * <code>true</code> to wait for a free connection,
266 * <code>false</code> to return <code>null</code>
268 * <code>true</code> to mark the connection as used before
269 * returning it, <code>false</code> not to mark it
270 * @return An unused FCP connection, or <code>null</code> if no connection
273 @SuppressWarnings("unused")
274 private HighLevelClient findUnusedClient(boolean wait, boolean markAsUsed) {
275 synchronized (syncObject) {
276 HighLevelClient freeHighLevelClient = null;
277 while (freeHighLevelClient == null) {
278 for (HighLevelClient highLevelClient: nodeConnections.values()) {
279 if (!usedConnections.contains(highLevelClient)) {
280 freeHighLevelClient = highLevelClient;
284 if (freeHighLevelClient != null) {
286 usedConnections.add(freeHighLevelClient);
288 return freeHighLevelClient;
294 /* we never get here, but the compiler doesn't realize. */