2 * Sone - WebOfTrustConnector.java - Copyright © 2010–2013 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.freenet.wot;
20 import static java.util.logging.Logger.getLogger;
22 import java.util.HashMap;
23 import java.util.HashSet;
26 import java.util.concurrent.atomic.AtomicLong;
27 import java.util.logging.Level;
28 import java.util.logging.Logger;
30 import javax.inject.Inject;
31 import javax.inject.Singleton;
33 import net.pterodactylus.freenet.plugin.PluginConnector;
34 import net.pterodactylus.freenet.plugin.PluginException;
35 import net.pterodactylus.freenet.plugin.event.ReceivedReplyEvent;
37 import freenet.support.SimpleFieldSet;
38 import freenet.support.api.Bucket;
40 import com.google.common.base.Optional;
41 import com.google.common.collect.MapMaker;
42 import com.google.common.eventbus.Subscribe;
43 import com.google.common.primitives.Ints;
46 * Connector for the Web of Trust plugin.
48 * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
51 public class WebOfTrustConnector {
53 private static final Logger logger = getLogger("Sone.WoT.Connector");
54 private static final String WOT_PLUGIN_NAME = "plugins.WebOfTrust.WebOfTrust";
56 private final AtomicLong counter = new AtomicLong();
57 private final PluginConnector pluginConnector;
58 private final Map<PluginIdentifier, Reply> replies = new MapMaker().makeMap();
61 public WebOfTrustConnector(PluginConnector pluginConnector) {
62 this.pluginConnector = pluginConnector;
65 public Set<OwnIdentity> loadAllOwnIdentities() throws PluginException {
66 Reply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetOwnIdentities").get());
67 SimpleFieldSet fields = reply.getFields();
68 int ownIdentityCounter = -1;
69 Set<OwnIdentity> ownIdentities = new HashSet<>();
71 String id = fields.get("Identity" + ++ownIdentityCounter);
75 String requestUri = fields.get("RequestURI" + ownIdentityCounter);
76 String insertUri = fields.get("InsertURI" + ownIdentityCounter);
77 String nickname = fields.get("Nickname" + ownIdentityCounter);
78 DefaultOwnIdentity ownIdentity = new DefaultOwnIdentity(id, nickname, requestUri, insertUri);
79 ownIdentity.setContexts(parseContexts("Contexts" + ownIdentityCounter + ".", fields));
80 ownIdentity.setProperties(parseProperties("Properties" + ownIdentityCounter + ".", fields));
81 ownIdentities.add(ownIdentity);
87 * Loads all identities that the given identities trusts with a score of
92 * @return All trusted identities
93 * @throws PluginException
94 * if an error occured talking to the Web of Trust plugin
96 public Set<Identity> loadTrustedIdentities(OwnIdentity ownIdentity) throws PluginException {
97 return loadTrustedIdentities(ownIdentity, null);
101 * Loads all identities that the given identities trusts with a score of
102 * more than 0 and the (optional) given context.
107 * The context to filter, or {@code null}
108 * @return All trusted identities
109 * @throws PluginException
110 * if an error occured talking to the Web of Trust plugin
112 public Set<Identity> loadTrustedIdentities(OwnIdentity ownIdentity, Optional<String> context) throws PluginException {
113 Reply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetIdentitiesByScore").put("Truster", ownIdentity.getId()).put("Selection", "+").put("Context", context.or("")).put("WantTrustValues", "true").get());
114 SimpleFieldSet fields = reply.getFields();
115 Set<Identity> identities = new HashSet<>();
116 int identityCounter = -1;
118 String id = fields.get("Identity" + ++identityCounter);
122 String nickname = fields.get("Nickname" + identityCounter);
123 String requestUri = fields.get("RequestURI" + identityCounter);
124 DefaultIdentity identity = new DefaultIdentity(id, nickname, requestUri);
125 identity.setContexts(parseContexts("Contexts" + identityCounter + ".", fields));
126 identity.setProperties(parseProperties("Properties" + identityCounter + ".", fields));
127 Integer trust = parseInt(fields.get("Trust" + identityCounter), null);
128 int score = parseInt(fields.get("Score" + identityCounter), 0);
129 int rank = parseInt(fields.get("Rank" + identityCounter), 0);
130 identity.setTrust(ownIdentity, new Trust(trust, score, rank));
131 identities.add(identity);
136 private static Integer parseInt(String value, Integer defaultValue) {
137 return java.util.Optional.ofNullable(value).map(Ints::tryParse).orElse(defaultValue);
141 * Adds the given context to the given identity.
144 * The identity to add the context to
147 * @throws PluginException
148 * if an error occured talking to the Web of Trust plugin
150 public void addContext(OwnIdentity ownIdentity, String context) throws PluginException {
151 performRequest(SimpleFieldSetConstructor.create().put("Message", "AddContext").put("Identity", ownIdentity.getId()).put("Context", context).get());
155 * Removes the given context from the given identity.
158 * The identity to remove the context from
160 * The context to remove
161 * @throws PluginException
162 * if an error occured talking to the Web of Trust plugin
164 public void removeContext(OwnIdentity ownIdentity, String context) throws PluginException {
165 performRequest(SimpleFieldSetConstructor.create().put("Message", "RemoveContext").put("Identity", ownIdentity.getId()).put("Context", context).get());
169 * Returns the value of the property with the given name.
172 * The identity whose properties to check
174 * The name of the property to return
175 * @return The value of the property, or {@code null} if there is no value
176 * @throws PluginException
177 * if an error occured talking to the Web of Trust plugin
179 public String getProperty(Identity identity, String name) throws PluginException {
180 Reply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetProperty").put("Identity", identity.getId()).put("Property", name).get());
181 return reply.getFields().get("Property");
185 * Sets the property with the given name to the given value.
188 * The identity to set the property on
190 * The name of the property to set
193 * @throws PluginException
194 * if an error occured talking to the Web of Trust plugin
196 public void setProperty(OwnIdentity ownIdentity, String name, String value) throws PluginException {
197 performRequest(SimpleFieldSetConstructor.create().put("Message", "SetProperty").put("Identity", ownIdentity.getId()).put("Property", name).put("Value", value).get());
201 * Removes the property with the given name.
204 * The identity to remove the property from
206 * The name of the property to remove
207 * @throws PluginException
208 * if an error occured talking to the Web of Trust plugin
210 public void removeProperty(OwnIdentity ownIdentity, String name) throws PluginException {
211 performRequest(SimpleFieldSetConstructor.create().put("Message", "RemoveProperty").put("Identity", ownIdentity.getId()).put("Property", name).get());
215 * Returns the trust for the given identity assigned to it by the given own
221 * The identity to get the trust for
222 * @return The trust for the given identity
223 * @throws PluginException
224 * if an error occured talking to the Web of Trust plugin
226 public Trust getTrust(OwnIdentity ownIdentity, Identity identity) throws PluginException {
227 Reply getTrustReply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetIdentity").put("Truster", ownIdentity.getId()).put("Identity", identity.getId()).get());
228 String trust = getTrustReply.getFields().get("Trust");
229 String score = getTrustReply.getFields().get("Score");
230 String rank = getTrustReply.getFields().get("Rank");
231 Integer explicit = null;
232 Integer implicit = null;
233 Integer distance = null;
235 explicit = Integer.valueOf(trust);
236 } catch (NumberFormatException nfe1) {
240 implicit = Integer.valueOf(score);
241 distance = Integer.valueOf(rank);
242 } catch (NumberFormatException nfe1) {
245 return new Trust(explicit, implicit, distance);
249 * Sets the trust for the given identity.
252 * The trusting identity
254 * The trusted identity
256 * The amount of trust (-100 thru 100)
258 * The comment or explanation of the trust value
259 * @throws PluginException
260 * if an error occured talking to the Web of Trust plugin
262 public void setTrust(OwnIdentity ownIdentity, Identity identity, int trust, String comment) throws PluginException {
263 performRequest(SimpleFieldSetConstructor.create().put("Message", "SetTrust").put("Truster", ownIdentity.getId()).put("Trustee", identity.getId()).put("Value", String
264 .valueOf(trust)).put("Comment", comment).get());
268 * Removes any trust assignment of the given own identity for the given
274 * The identity to remove all trust for
275 * @throws WebOfTrustException
278 public void removeTrust(OwnIdentity ownIdentity, Identity identity) throws PluginException {
279 performRequest(SimpleFieldSetConstructor.create().put("Message", "RemoveTrust").put("Truster", ownIdentity.getId()).put("Trustee", identity.getId()).get());
283 * Pings the Web of Trust plugin. If the plugin can not be reached, a
284 * {@link PluginException} is thrown.
286 * @throws PluginException
287 * if the plugin is not loaded
289 public void ping() throws PluginException {
290 performRequest(SimpleFieldSetConstructor.create().put("Message", "Ping").get());
298 * Parses the contexts from the given fields.
301 * The prefix to use to access the contexts
303 * The fields to parse the contexts from
304 * @return The parsed contexts
306 private static Set<String> parseContexts(String prefix, SimpleFieldSet fields) {
307 Set<String> contexts = new HashSet<>();
308 int contextCounter = -1;
310 String context = fields.get(prefix + "Context" + ++contextCounter);
311 if (context == null) {
314 contexts.add(context);
320 * Parses the properties from the given fields.
323 * The prefix to use to access the properties
325 * The fields to parse the properties from
326 * @return The parsed properties
328 private static Map<String, String> parseProperties(String prefix, SimpleFieldSet fields) {
329 Map<String, String> properties = new HashMap<>();
330 int propertiesCounter = -1;
332 String propertyName = fields.get(prefix + "Property" + ++propertiesCounter + ".Name");
333 if (propertyName == null) {
336 String propertyValue = fields.get(prefix + "Property" + propertiesCounter + ".Value");
337 properties.put(propertyName, propertyValue);
343 * Sends a request containing the given fields and waits for the target
347 * The fields of the message
348 * @return The reply message
349 * @throws PluginException
350 * if the request could not be sent
352 private Reply performRequest(SimpleFieldSet fields) throws PluginException {
353 return performRequest(fields, null);
357 * Sends a request containing the given fields and waits for the target
361 * The fields of the message
363 * The payload of the message
364 * @return The reply message
365 * @throws PluginException
366 * if the request could not be sent
368 private Reply performRequest(SimpleFieldSet fields, Bucket data) throws PluginException {
369 String identifier = "FCP-Command-" + System.currentTimeMillis() + "-" + counter.getAndIncrement();
370 Reply reply = new Reply();
371 PluginIdentifier pluginIdentifier = new PluginIdentifier(WOT_PLUGIN_NAME, identifier);
372 replies.put(pluginIdentifier, reply);
374 logger.log(Level.FINE, String.format("Sending FCP Request: %s", fields.get("Message")));
375 synchronized (reply) {
377 pluginConnector.sendRequest(WOT_PLUGIN_NAME, identifier, fields, data);
378 while (reply.getFields() == null) {
381 } catch (InterruptedException ie1) {
382 logger.log(Level.WARNING, String.format("Got interrupted while waiting for reply on %s.",
383 fields.get("Message")), ie1);
387 replies.remove(pluginIdentifier);
390 logger.log(Level.FINEST, String.format("Received FCP Response for %s: %s", fields.get("Message"),
391 (reply.getFields() != null) ? reply.getFields().get("Message") : null));
392 if ((reply.getFields() == null) || "Error".equals(reply.getFields().get("Message"))) {
393 throw new PluginException("Could not perform request for " + fields.get("Message"));
399 * Notifies the connector that a plugin reply was received.
401 * @param receivedReplyEvent
405 public void receivedReply(ReceivedReplyEvent receivedReplyEvent) {
406 PluginIdentifier pluginIdentifier = new PluginIdentifier(receivedReplyEvent.pluginName(), receivedReplyEvent.identifier());
407 Reply reply = replies.remove(pluginIdentifier);
411 logger.log(Level.FINEST, String.format("Received Reply from Plugin: %s",
412 receivedReplyEvent.fieldSet().get("Message")));
413 synchronized (reply) {
414 reply.setFields(receivedReplyEvent.fieldSet());
415 reply.setData(receivedReplyEvent.data());
421 * Container for the data of the reply from a plugin.
423 * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
425 private static class Reply {
427 /** The fields of the reply. */
428 private SimpleFieldSet fields;
430 /** The payload of the reply. */
433 /** Empty constructor. */
439 * Returns the fields of the reply.
441 * @return The fields of the reply
443 public SimpleFieldSet getFields() {
448 * Sets the fields of the reply.
451 * The fields of the reply
453 public void setFields(SimpleFieldSet fields) {
454 this.fields = fields;
458 * Returns the payload of the reply.
460 * @return The payload of the reply (may be {@code null})
462 @SuppressWarnings("unused")
463 public Bucket getData() {
468 * Sets the payload of the reply.
471 * The payload of the reply (may be {@code null})
473 public void setData(Bucket data) {
480 * Helper method to create {@link SimpleFieldSet}s with terser code.
482 * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
484 private static class SimpleFieldSetConstructor {
486 /** The field set being created. */
487 private final SimpleFieldSet simpleFieldSet;
490 * Creates a new simple field set constructor.
493 * {@code true} if the resulting simple field set should be
494 * short-lived, {@code false} otherwise
496 private SimpleFieldSetConstructor(boolean shortLived) {
497 simpleFieldSet = new SimpleFieldSet(shortLived);
505 * Returns the created simple field set.
507 * @return The created simple field set
509 public SimpleFieldSet get() {
510 return simpleFieldSet;
514 * Sets the field with the given name to the given value.
517 * The name of the fleld
519 * The value of the field
520 * @return This constructor (for method chaining)
522 public SimpleFieldSetConstructor put(String name, String value) {
523 simpleFieldSet.putOverwrite(name, value);
532 * Creates a new simple field set constructor.
534 * @return The created simple field set constructor
536 public static SimpleFieldSetConstructor create() {
541 * Creates a new simple field set constructor.
544 * {@code true} if the resulting simple field set should be
545 * short-lived, {@code false} otherwise
546 * @return The created simple field set constructor
548 public static SimpleFieldSetConstructor create(boolean shortLived) {
549 return new SimpleFieldSetConstructor(shortLived);
555 * Container for identifying plugins. Plugins are identified by their plugin
556 * name and their unique identifier.
558 * @author <a href="mailto:d.roden@xplosion.de">David Roden</a>
560 private static class PluginIdentifier {
562 /** The plugin name. */
563 private final String pluginName;
565 /** The plugin identifier. */
566 private final String identifier;
569 * Creates a new plugin identifier.
572 * The name of the plugin
574 * The identifier of the plugin
576 public PluginIdentifier(String pluginName, String identifier) {
577 this.pluginName = pluginName;
578 this.identifier = identifier;
589 public int hashCode() {
590 return pluginName.hashCode() ^ identifier.hashCode();
597 public boolean equals(Object object) {
598 if (!(object instanceof PluginIdentifier)) {
601 PluginIdentifier pluginIdentifier = (PluginIdentifier) object;
602 return pluginName.equals(pluginIdentifier.pluginName) && identifier.equals(pluginIdentifier.identifier);