2 * Sone - WebOfTrustConnector.java - Copyright © 2010 David Roden
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 package net.pterodactylus.sone.freenet.wot;
20 import java.util.Collections;
21 import java.util.HashMap;
22 import java.util.HashSet;
25 import java.util.logging.Level;
26 import java.util.logging.Logger;
28 import net.pterodactylus.util.logging.Logging;
29 import freenet.support.SimpleFieldSet;
30 import freenet.support.api.Bucket;
33 * Connector for the Web of Trust plugin.
35 * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
37 public class WebOfTrustConnector implements ConnectorListener {
40 private static final Logger logger = Logging.getLogger(WebOfTrustConnector.class);
42 /** The name of the WoT plugin. */
43 private static final String WOT_PLUGIN_NAME = "plugins.WoT.WoT";
45 /** A random connection identifier. */
46 private static final String PLUGIN_CONNECTION_IDENTIFIER = "Sone-WoT-Connector-" + Math.abs(Math.random());
48 /** The current replies that we wait for. */
49 private final Map<String, Reply> replies = Collections.synchronizedMap(new HashMap<String, Reply>());
51 /** The plugin connector. */
52 private final PluginConnector pluginConnector;
55 * Creates a new Web of Trust connector that uses the given plugin
58 * @param pluginConnector
59 * The plugin connector
61 public WebOfTrustConnector(PluginConnector pluginConnector) {
62 this.pluginConnector = pluginConnector;
63 pluginConnector.addConnectorListener(WOT_PLUGIN_NAME, PLUGIN_CONNECTION_IDENTIFIER, this);
71 * Loads all own identities from the Web of Trust plugin.
73 * @return All own identity
74 * @throws PluginException
75 * if the own identities can not be loaded
77 public Set<OwnIdentity> loadAllOwnIdentities() throws PluginException {
78 Reply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetOwnIdentities").get(), "OwnIdentities");
79 SimpleFieldSet fields = reply.getFields();
80 int ownIdentityCounter = -1;
81 Set<OwnIdentity> ownIdentities = new HashSet<OwnIdentity>();
83 String id = fields.get("Identity" + ++ownIdentityCounter);
87 String requestUri = fields.get("RequestURI" + ownIdentityCounter);
88 String insertUri = fields.get("InsertURI" + ownIdentityCounter);
89 String nickname = fields.get("Nickname" + ownIdentityCounter);
90 OwnIdentity ownIdentity = new OwnIdentity(id, nickname, requestUri, insertUri);
91 ownIdentity.setContexts(parseContexts("Contexts" + ownIdentityCounter + ".", fields));
92 ownIdentity.setProperties(parseProperties("Properties" + ownIdentityCounter + ".", fields));
93 ownIdentities.add(ownIdentity);
99 * Loads all identities that the given identities trusts with a score of
104 * @return All trusted identities
105 * @throws PluginException
106 * if an error occured talking to the Web of Trust plugin
108 public Set<Identity> loadTrustedIdentities(OwnIdentity ownIdentity) throws PluginException {
109 return loadTrustedIdentities(ownIdentity, null);
113 * Loads all identities that the given identities trusts with a score of
114 * more than 0 and the (optional) given context.
119 * The context to filter, or {@code null}
120 * @return All trusted identities
121 * @throws PluginException
122 * if an error occured talking to the Web of Trust plugin
124 public Set<Identity> loadTrustedIdentities(OwnIdentity ownIdentity, String context) throws PluginException {
125 Reply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetIdentitiesByScore").put("TreeOwner", ownIdentity.getId()).put("Selection", "+").put("Context", (context == null) ? "" : context).get(), "Identities");
126 SimpleFieldSet fields = reply.getFields();
127 Set<Identity> identities = new HashSet<Identity>();
128 int identityCounter = -1;
130 String id = fields.get("Identity" + ++identityCounter);
134 String nickname = fields.get("Nickname" + identityCounter);
135 String requestUri = fields.get("RequestURI" + identityCounter);
136 Identity identity = new Identity(id, nickname, requestUri);
137 identity.setContexts(parseContexts("Contexts" + identityCounter + ".", fields));
138 identity.setProperties(parseProperties("Properties" + identityCounter + ".", fields));
139 identities.add(identity);
145 * Adds the given context to the given identity.
148 * The identity to add the context to
151 * @throws PluginException
152 * if an error occured talking to the Web of Trust plugin
154 public void addContext(OwnIdentity ownIdentity, String context) throws PluginException {
155 performRequest(SimpleFieldSetConstructor.create().put("Message", "AddContext").put("Identity", ownIdentity.getId()).put("Context", context).get(), "ContextAdded");
159 * Removes the given context from the given identity.
162 * The identity to remove the context from
164 * The context to remove
165 * @throws PluginException
166 * if an error occured talking to the Web of Trust plugin
168 public void removeContext(OwnIdentity ownIdentity, String context) throws PluginException {
169 performRequest(SimpleFieldSetConstructor.create().put("Message", "RemoveContext").put("Identity", ownIdentity.getId()).put("Context", context).get(), "ContextRemoved");
173 * Returns the value of the property with the given name.
176 * The identity whose properties to check
178 * The name of the property to return
179 * @return The value of the property, or {@code null} if there is no value
180 * @throws PluginException
181 * if an error occured talking to the Web of Trust plugin
183 public String getProperty(Identity identity, String name) throws PluginException {
184 Reply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetProperty").put("Identity", identity.getId()).put("Property", name).get(), "PropertyValue");
185 return reply.getFields().get("Property");
189 * Sets the property with the given name to the given value.
192 * The identity to set the property on
194 * The name of the property to set
197 * @throws PluginException
198 * if an error occured talking to the Web of Trust plugin
200 public void setProperty(OwnIdentity ownIdentity, String name, String value) throws PluginException {
201 performRequest(SimpleFieldSetConstructor.create().put("Message", "SetProperty").put("Identity", ownIdentity.getId()).put("Property", name).put("Value", value).get(), "PropertyAdded");
205 * Removes the property with the given name.
208 * The identity to remove the property from
210 * The name of the property to remove
211 * @throws PluginException
212 * if an error occured talking to the Web of Trust plugin
214 public void removeProperty(OwnIdentity ownIdentity, String name) throws PluginException {
215 performRequest(SimpleFieldSetConstructor.create().put("Message", "RemoveProperty").put("Identity", ownIdentity.getId()).put("Property", name).get(), "PropertyRemoved");
219 * Pings the Web of Trust plugin. If the plugin can not be reached, a
220 * {@link PluginException} is thrown.
222 * @throws PluginException
223 * if the plugin is not loaded
225 public void ping() throws PluginException {
226 performRequest(SimpleFieldSetConstructor.create().put("Message", "Ping").get(), "Pong");
234 * Parses the contexts from the given fields.
237 * The prefix to use to access the contexts
239 * The fields to parse the contexts from
240 * @return The parsed contexts
242 private Set<String> parseContexts(String prefix, SimpleFieldSet fields) {
243 Set<String> contexts = new HashSet<String>();
244 int contextCounter = -1;
246 String context = fields.get(prefix + "Context" + ++contextCounter);
247 if (context == null) {
250 contexts.add(context);
256 * Parses the properties from the given fields.
259 * The prefix to use to access the properties
261 * The fields to parse the properties from
262 * @return The parsed properties
264 private Map<String, String> parseProperties(String prefix, SimpleFieldSet fields) {
265 Map<String, String> properties = new HashMap<String, String>();
266 int propertiesCounter = -1;
268 String propertyName = fields.get(prefix + "Property" + ++propertiesCounter + ".Name");
269 if (propertyName == null) {
272 String propertyValue = fields.get(prefix + "Property" + propertiesCounter + ".Value");
273 properties.put(propertyName, propertyValue);
279 * Sends a request containing the given fields and waits for the target
283 * The fields of the message
284 * @param targetMessages
285 * The messages of the reply to wait for
286 * @return The reply message
287 * @throws PluginException
288 * if the request could not be sent
290 private Reply performRequest(SimpleFieldSet fields, String... targetMessages) throws PluginException {
291 return performRequest(fields, null, targetMessages);
295 * Sends a request containing the given fields and waits for the target
299 * The fields of the message
301 * The payload of the message
302 * @param targetMessages
303 * The messages of the reply to wait for
304 * @return The reply message
305 * @throws PluginException
306 * if the request could not be sent
308 private Reply performRequest(SimpleFieldSet fields, Bucket data, String... targetMessages) throws PluginException {
309 @SuppressWarnings("synthetic-access")
310 Reply reply = new Reply();
311 for (String targetMessage : targetMessages) {
312 replies.put(targetMessage, reply);
314 replies.put("Error", reply);
315 synchronized (reply) {
316 pluginConnector.sendRequest(WOT_PLUGIN_NAME, PLUGIN_CONNECTION_IDENTIFIER, fields, data);
318 long now = System.currentTimeMillis();
319 while ((reply.getFields() == null) && ((System.currentTimeMillis() - now) < 60000)) {
320 reply.wait(60000 - (System.currentTimeMillis() - now));
322 if (reply.getFields() == null) {
323 for (String targetMessage : targetMessages) {
324 replies.remove(targetMessage);
326 replies.remove("Error");
327 throw new PluginException("Timeout waiting for " + targetMessages[0] + "!");
329 } catch (InterruptedException ie1) {
330 logger.log(Level.WARNING, "Got interrupted while waiting for reply on " + targetMessages[0] + ".", ie1);
333 for (String targetMessage : targetMessages) {
334 replies.remove(targetMessage);
336 replies.remove("Error");
337 if ((reply.getFields() != null) && reply.getFields().get("Message").equals("Error")) {
338 throw new PluginException("Could not perform request for " + targetMessages[0]);
344 // INTERFACE ConnectorListener
351 public void receivedReply(PluginConnector pluginConnector, SimpleFieldSet fields, Bucket data) {
352 String messageName = fields.get("Message");
353 logger.log(Level.FINEST, "Received Reply from Plugin: " + messageName);
354 Reply reply = replies.remove(messageName);
356 logger.log(Level.FINE, "Not waiting for a “%s” message.", messageName);
359 synchronized (reply) {
360 reply.setFields(fields);
367 * Container for the data of the reply from a plugin.
369 * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
371 private static class Reply {
373 /** The fields of the reply. */
374 private SimpleFieldSet fields;
376 /** The payload of the reply. */
380 * Returns the fields of the reply.
382 * @return The fields of the reply
384 public SimpleFieldSet getFields() {
389 * Sets the fields of the reply.
392 * The fields of the reply
394 public void setFields(SimpleFieldSet fields) {
395 this.fields = fields;
399 * Returns the payload of the reply.
401 * @return The payload of the reply (may be {@code null})
403 @SuppressWarnings("unused")
404 public Bucket getData() {
409 * Sets the payload of the reply.
412 * The payload of the reply (may be {@code null})
414 public void setData(Bucket data) {
421 * Helper method to create {@link SimpleFieldSet}s with terser code.
423 * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
425 private static class SimpleFieldSetConstructor {
427 /** The field set being created. */
428 private SimpleFieldSet simpleFieldSet;
431 * Creates a new simple field set constructor.
434 * {@code true} if the resulting simple field set should be
435 * short-lived, {@code false} otherwise
437 private SimpleFieldSetConstructor(boolean shortLived) {
438 simpleFieldSet = new SimpleFieldSet(shortLived);
446 * Returns the created simple field set.
448 * @return The created simple field set
450 public SimpleFieldSet get() {
451 return simpleFieldSet;
455 * Sets the field with the given name to the given value.
458 * The name of the fleld
460 * The value of the field
461 * @return This constructor (for method chaining)
463 public SimpleFieldSetConstructor put(String name, String value) {
464 simpleFieldSet.putOverwrite(name, value);
473 * Creates a new simple field set constructor.
475 * @return The created simple field set constructor
477 public static SimpleFieldSetConstructor create() {
482 * Creates a new simple field set constructor.
485 * {@code true} if the resulting simple field set should be
486 * short-lived, {@code false} otherwise
487 * @return The created simple field set constructor
489 public static SimpleFieldSetConstructor create(boolean shortLived) {
490 SimpleFieldSetConstructor simpleFieldSetConstructor = new SimpleFieldSetConstructor(shortLived);
491 return simpleFieldSetConstructor;