Add Freenet plugin and WoT subprojects
[fwot.git] / wot / src / main / java / net / pterodactylus / freenet / wot / WebOfTrustConnector.java
1 /*
2  * Sone - WebOfTrustConnector.java - Copyright © 2010–2013 David Roden
3  *
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.
8  *
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.
13  *
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/>.
16  */
17
18 package net.pterodactylus.freenet.wot;
19
20 import static java.util.logging.Logger.getLogger;
21
22 import java.util.HashMap;
23 import java.util.HashSet;
24 import java.util.Map;
25 import java.util.Set;
26 import java.util.concurrent.atomic.AtomicLong;
27 import java.util.logging.Level;
28 import java.util.logging.Logger;
29
30 import javax.inject.Inject;
31 import javax.inject.Singleton;
32
33 import net.pterodactylus.freenet.plugin.PluginConnector;
34 import net.pterodactylus.freenet.plugin.PluginException;
35 import net.pterodactylus.freenet.plugin.event.ReceivedReplyEvent;
36
37 import freenet.support.SimpleFieldSet;
38 import freenet.support.api.Bucket;
39
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;
44
45 /**
46  * Connector for the Web of Trust plugin.
47  *
48  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
49  */
50 @Singleton
51 public class WebOfTrustConnector {
52
53         private static final Logger logger = getLogger("Sone.WoT.Connector");
54         private static final String WOT_PLUGIN_NAME = "plugins.WebOfTrust.WebOfTrust";
55
56         private final AtomicLong counter = new AtomicLong();
57         private final PluginConnector pluginConnector;
58         private final Map<PluginIdentifier, Reply> replies = new MapMaker().makeMap();
59
60         @Inject
61         public WebOfTrustConnector(PluginConnector pluginConnector) {
62                 this.pluginConnector = pluginConnector;
63         }
64
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<>();
70                 while (true) {
71                         String id = fields.get("Identity" + ++ownIdentityCounter);
72                         if (id == null) {
73                                 break;
74                         }
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);
82                 }
83                 return ownIdentities;
84         }
85
86         /**
87          * Loads all identities that the given identities trusts with a score of
88          * more than 0.
89          *
90          * @param ownIdentity
91          *            The own identity
92          * @return All trusted identities
93          * @throws PluginException
94          *             if an error occured talking to the Web of Trust plugin
95          */
96         public Set<Identity> loadTrustedIdentities(OwnIdentity ownIdentity) throws PluginException {
97                 return loadTrustedIdentities(ownIdentity, null);
98         }
99
100         /**
101          * Loads all identities that the given identities trusts with a score of
102          * more than 0 and the (optional) given context.
103          *
104          * @param ownIdentity
105          *            The own identity
106          * @param 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
111          */
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;
117                 while (true) {
118                         String id = fields.get("Identity" + ++identityCounter);
119                         if (id == null) {
120                                 break;
121                         }
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);
132                 }
133                 return identities;
134         }
135
136         private static Integer parseInt(String value, Integer defaultValue) {
137                 return java.util.Optional.ofNullable(value).map(Ints::tryParse).orElse(defaultValue);
138         }
139
140         /**
141          * Adds the given context to the given identity.
142          *
143          * @param ownIdentity
144          *            The identity to add the context to
145          * @param context
146          *            The context to add
147          * @throws PluginException
148          *             if an error occured talking to the Web of Trust plugin
149          */
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());
152         }
153
154         /**
155          * Removes the given context from the given identity.
156          *
157          * @param ownIdentity
158          *            The identity to remove the context from
159          * @param context
160          *            The context to remove
161          * @throws PluginException
162          *             if an error occured talking to the Web of Trust plugin
163          */
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());
166         }
167
168         /**
169          * Returns the value of the property with the given name.
170          *
171          * @param identity
172          *            The identity whose properties to check
173          * @param name
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
178          */
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");
182         }
183
184         /**
185          * Sets the property with the given name to the given value.
186          *
187          * @param ownIdentity
188          *            The identity to set the property on
189          * @param name
190          *            The name of the property to set
191          * @param value
192          *            The value to set
193          * @throws PluginException
194          *             if an error occured talking to the Web of Trust plugin
195          */
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());
198         }
199
200         /**
201          * Removes the property with the given name.
202          *
203          * @param ownIdentity
204          *            The identity to remove the property from
205          * @param name
206          *            The name of the property to remove
207          * @throws PluginException
208          *             if an error occured talking to the Web of Trust plugin
209          */
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());
212         }
213
214         /**
215          * Returns the trust for the given identity assigned to it by the given own
216          * identity.
217          *
218          * @param ownIdentity
219          *            The own identity
220          * @param identity
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
225          */
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;
234                 try {
235                         explicit = Integer.valueOf(trust);
236                 } catch (NumberFormatException nfe1) {
237                         /* ignore. */
238                 }
239                 try {
240                         implicit = Integer.valueOf(score);
241                         distance = Integer.valueOf(rank);
242                 } catch (NumberFormatException nfe1) {
243                         /* ignore. */
244                 }
245                 return new Trust(explicit, implicit, distance);
246         }
247
248         /**
249          * Sets the trust for the given identity.
250          *
251          * @param ownIdentity
252          *            The trusting identity
253          * @param identity
254          *            The trusted identity
255          * @param trust
256          *            The amount of trust (-100 thru 100)
257          * @param comment
258          *            The comment or explanation of the trust value
259          * @throws PluginException
260          *             if an error occured talking to the Web of Trust plugin
261          */
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());
265         }
266
267         /**
268          * Removes any trust assignment of the given own identity for the given
269          * identity.
270          *
271          * @param ownIdentity
272          *            The own identity
273          * @param identity
274          *            The identity to remove all trust for
275          * @throws WebOfTrustException
276          *             if an error occurs
277          */
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());
280         }
281
282         /**
283          * Pings the Web of Trust plugin. If the plugin can not be reached, a
284          * {@link PluginException} is thrown.
285          *
286          * @throws PluginException
287          *             if the plugin is not loaded
288          */
289         public void ping() throws PluginException {
290                 performRequest(SimpleFieldSetConstructor.create().put("Message", "Ping").get());
291         }
292
293         //
294         // PRIVATE ACTIONS
295         //
296
297         /**
298          * Parses the contexts from the given fields.
299          *
300          * @param prefix
301          *            The prefix to use to access the contexts
302          * @param fields
303          *            The fields to parse the contexts from
304          * @return The parsed contexts
305          */
306         private static Set<String> parseContexts(String prefix, SimpleFieldSet fields) {
307                 Set<String> contexts = new HashSet<>();
308                 int contextCounter = -1;
309                 while (true) {
310                         String context = fields.get(prefix + "Context" + ++contextCounter);
311                         if (context == null) {
312                                 break;
313                         }
314                         contexts.add(context);
315                 }
316                 return contexts;
317         }
318
319         /**
320          * Parses the properties from the given fields.
321          *
322          * @param prefix
323          *            The prefix to use to access the properties
324          * @param fields
325          *            The fields to parse the properties from
326          * @return The parsed properties
327          */
328         private static Map<String, String> parseProperties(String prefix, SimpleFieldSet fields) {
329                 Map<String, String> properties = new HashMap<>();
330                 int propertiesCounter = -1;
331                 while (true) {
332                         String propertyName = fields.get(prefix + "Property" + ++propertiesCounter + ".Name");
333                         if (propertyName == null) {
334                                 break;
335                         }
336                         String propertyValue = fields.get(prefix + "Property" + propertiesCounter + ".Value");
337                         properties.put(propertyName, propertyValue);
338                 }
339                 return properties;
340         }
341
342         /**
343          * Sends a request containing the given fields and waits for the target
344          * message.
345          *
346          * @param fields
347          *            The fields of the message
348          * @return The reply message
349          * @throws PluginException
350          *             if the request could not be sent
351          */
352         private Reply performRequest(SimpleFieldSet fields) throws PluginException {
353                 return performRequest(fields, null);
354         }
355
356         /**
357          * Sends a request containing the given fields and waits for the target
358          * message.
359          *
360          * @param fields
361          *            The fields of the message
362          * @param data
363          *            The payload of the message
364          * @return The reply message
365          * @throws PluginException
366          *             if the request could not be sent
367          */
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);
373
374                 logger.log(Level.FINE, String.format("Sending FCP Request: %s", fields.get("Message")));
375                 synchronized (reply) {
376                         try {
377                                 pluginConnector.sendRequest(WOT_PLUGIN_NAME, identifier, fields, data);
378                                 while (reply.getFields() == null) {
379                                         try {
380                                                 reply.wait();
381                                         } catch (InterruptedException ie1) {
382                                                 logger.log(Level.WARNING, String.format("Got interrupted while waiting for reply on %s.",
383                                                                 fields.get("Message")), ie1);
384                                         }
385                                 }
386                         } finally {
387                                 replies.remove(pluginIdentifier);
388                         }
389                 }
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"));
394                 }
395                 return reply;
396         }
397
398         /**
399          * Notifies the connector that a plugin reply was received.
400          *
401          * @param receivedReplyEvent
402          *            The event
403          */
404         @Subscribe
405         public void receivedReply(ReceivedReplyEvent receivedReplyEvent) {
406                 PluginIdentifier pluginIdentifier = new PluginIdentifier(receivedReplyEvent.pluginName(), receivedReplyEvent.identifier());
407                 Reply reply = replies.remove(pluginIdentifier);
408                 if (reply == null) {
409                         return;
410                 }
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());
416                         reply.notify();
417                 }
418         }
419
420         /**
421          * Container for the data of the reply from a plugin.
422          *
423          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
424          */
425         private static class Reply {
426
427                 /** The fields of the reply. */
428                 private SimpleFieldSet fields;
429
430                 /** The payload of the reply. */
431                 private Bucket data;
432
433                 /** Empty constructor. */
434                 public Reply() {
435                         /* do nothing. */
436                 }
437
438                 /**
439                  * Returns the fields of the reply.
440                  *
441                  * @return The fields of the reply
442                  */
443                 public SimpleFieldSet getFields() {
444                         return fields;
445                 }
446
447                 /**
448                  * Sets the fields of the reply.
449                  *
450                  * @param fields
451                  *            The fields of the reply
452                  */
453                 public void setFields(SimpleFieldSet fields) {
454                         this.fields = fields;
455                 }
456
457                 /**
458                  * Returns the payload of the reply.
459                  *
460                  * @return The payload of the reply (may be {@code null})
461                  */
462                 @SuppressWarnings("unused")
463                 public Bucket getData() {
464                         return data;
465                 }
466
467                 /**
468                  * Sets the payload of the reply.
469                  *
470                  * @param data
471                  *            The payload of the reply (may be {@code null})
472                  */
473                 public void setData(Bucket data) {
474                         this.data = data;
475                 }
476
477         }
478
479         /**
480          * Helper method to create {@link SimpleFieldSet}s with terser code.
481          *
482          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
483          */
484         private static class SimpleFieldSetConstructor {
485
486                 /** The field set being created. */
487                 private final SimpleFieldSet simpleFieldSet;
488
489                 /**
490                  * Creates a new simple field set constructor.
491                  *
492                  * @param shortLived
493                  *            {@code true} if the resulting simple field set should be
494                  *            short-lived, {@code false} otherwise
495                  */
496                 private SimpleFieldSetConstructor(boolean shortLived) {
497                         simpleFieldSet = new SimpleFieldSet(shortLived);
498                 }
499
500                 //
501                 // ACCESSORS
502                 //
503
504                 /**
505                  * Returns the created simple field set.
506                  *
507                  * @return The created simple field set
508                  */
509                 public SimpleFieldSet get() {
510                         return simpleFieldSet;
511                 }
512
513                 /**
514                  * Sets the field with the given name to the given value.
515                  *
516                  * @param name
517                  *            The name of the fleld
518                  * @param value
519                  *            The value of the field
520                  * @return This constructor (for method chaining)
521                  */
522                 public SimpleFieldSetConstructor put(String name, String value) {
523                         simpleFieldSet.putOverwrite(name, value);
524                         return this;
525                 }
526
527                 //
528                 // ACTIONS
529                 //
530
531                 /**
532                  * Creates a new simple field set constructor.
533                  *
534                  * @return The created simple field set constructor
535                  */
536                 public static SimpleFieldSetConstructor create() {
537                         return create(true);
538                 }
539
540                 /**
541                  * Creates a new simple field set constructor.
542                  *
543                  * @param shortLived
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
547                  */
548                 public static SimpleFieldSetConstructor create(boolean shortLived) {
549                         return new SimpleFieldSetConstructor(shortLived);
550                 }
551
552         }
553
554         /**
555          * Container for identifying plugins. Plugins are identified by their plugin
556          * name and their unique identifier.
557          *
558          * @author <a href="mailto:d.roden@xplosion.de">David Roden</a>
559          */
560         private static class PluginIdentifier {
561
562                 /** The plugin name. */
563                 private final String pluginName;
564
565                 /** The plugin identifier. */
566                 private final String identifier;
567
568                 /**
569                  * Creates a new plugin identifier.
570                  *
571                  * @param pluginName
572                  *            The name of the plugin
573                  * @param identifier
574                  *            The identifier of the plugin
575                  */
576                 public PluginIdentifier(String pluginName, String identifier) {
577                         this.pluginName = pluginName;
578                         this.identifier = identifier;
579                 }
580
581                 //
582                 // OBJECT METHODS
583                 //
584
585                 /**
586                  * {@inheritDoc}
587                  */
588                 @Override
589                 public int hashCode() {
590                         return pluginName.hashCode() ^ identifier.hashCode();
591                 }
592
593                 /**
594                  * {@inheritDoc}
595                  */
596                 @Override
597                 public boolean equals(Object object) {
598                         if (!(object instanceof PluginIdentifier)) {
599                                 return false;
600                         }
601                         PluginIdentifier pluginIdentifier = (PluginIdentifier) object;
602                         return pluginName.equals(pluginIdentifier.pluginName) && identifier.equals(pluginIdentifier.identifier);
603                 }
604
605         }
606
607 }