send a message to wot and see what comes back
[Sone.git] / src / main / java / net / pterodactylus / sone / freenet / wot / WebOfTrustConnector.java
1 /*
2  * Sone - WebOfTrustConnector.java - Copyright © 2010–2015 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.sone.freenet.wot;
19
20 import static java.util.logging.Logger.getLogger;
21 import static net.pterodactylus.sone.utils.NumberParsers.parseInt;
22
23 import java.util.HashMap;
24 import java.util.HashSet;
25 import java.util.Map;
26 import java.util.Set;
27 import java.util.concurrent.atomic.AtomicLong;
28 import java.util.logging.Level;
29 import java.util.logging.Logger;
30
31 import net.pterodactylus.sone.freenet.plugin.PluginConnector;
32 import net.pterodactylus.sone.freenet.plugin.PluginException;
33 import net.pterodactylus.sone.freenet.plugin.event.ReceivedReplyEvent;
34
35 import com.google.common.base.Optional;
36 import com.google.common.collect.MapMaker;
37 import com.google.common.eventbus.Subscribe;
38 import com.google.inject.Inject;
39 import com.google.inject.Singleton;
40
41 import freenet.support.SimpleFieldSet;
42 import freenet.support.api.Bucket;
43
44 /**
45  * Connector for the Web of Trust plugin.
46  *
47  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
48  */
49 @Singleton
50 public class WebOfTrustConnector {
51
52         /** The logger. */
53         private static final Logger logger = getLogger(WebOfTrustConnector.class.getName());
54
55         /** The name of the WoT plugin. */
56         private static final String WOT_PLUGIN_NAME = "plugins.WebOfTrust.WebOfTrust";
57
58         /** Counter for connection identifiers. */
59         private final AtomicLong counter = new AtomicLong();
60
61         /** The plugin connector. */
62         private final PluginConnector pluginConnector;
63
64         /** Map for replies. */
65         private final Map<PluginIdentifier, Reply> replies = new MapMaker().makeMap();
66
67         /**
68          * Creates a new Web of Trust connector that uses the given plugin
69          * connector.
70          *
71          * @param pluginConnector
72          *            The plugin connector
73          */
74         @Inject
75         public WebOfTrustConnector(PluginConnector pluginConnector) {
76                 this.pluginConnector = pluginConnector;
77         }
78
79         //
80         // ACTIONS
81         //
82
83         public void start() {
84                 try {
85                         pluginConnector.start();
86                 } catch (PluginException e) {
87                         e.printStackTrace();
88                 }
89         }
90
91         /**
92          * Stops the web of trust connector.
93          */
94         public void stop() {
95                 /* does nothing. */
96         }
97
98         /**
99          * Loads all own identities from the Web of Trust plugin.
100          *
101          * @return All own identity
102          * @throws WebOfTrustException
103          *             if the own identities can not be loaded
104          */
105         public Set<OwnIdentity> loadAllOwnIdentities() throws WebOfTrustException {
106                 Reply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetOwnIdentities").get());
107                 SimpleFieldSet fields = reply.getFields();
108                 int ownIdentityCounter = -1;
109                 Set<OwnIdentity> ownIdentities = new HashSet<OwnIdentity>();
110                 while (true) {
111                         String id = fields.get("Identity" + ++ownIdentityCounter);
112                         if (id == null) {
113                                 break;
114                         }
115                         String requestUri = fields.get("RequestURI" + ownIdentityCounter);
116                         String insertUri = fields.get("InsertURI" + ownIdentityCounter);
117                         String nickname = fields.get("Nickname" + ownIdentityCounter);
118                         DefaultOwnIdentity ownIdentity = new DefaultOwnIdentity(id, nickname, requestUri, insertUri);
119                         ownIdentity.setContexts(parseContexts("Contexts" + ownIdentityCounter + ".", fields));
120                         ownIdentity.setProperties(parseProperties("Properties" + ownIdentityCounter + ".", fields));
121                         ownIdentities.add(ownIdentity);
122                 }
123                 return ownIdentities;
124         }
125
126         /**
127          * Loads all identities that the given identities trusts with a score of
128          * more than 0.
129          *
130          * @param ownIdentity
131          *            The own identity
132          * @return All trusted identities
133          * @throws PluginException
134          *             if an error occured talking to the Web of Trust plugin
135          */
136         public Set<Identity> loadTrustedIdentities(OwnIdentity ownIdentity) throws PluginException {
137                 return loadTrustedIdentities(ownIdentity, Optional.<String>absent());
138         }
139
140         /**
141          * Loads all identities that the given identities trusts with a score of
142          * more than 0 and the (optional) given context.
143          *
144          * @param ownIdentity
145          *            The own identity
146          * @param context
147          *            The context to filter, or {@code null}
148          * @return All trusted identities
149          * @throws PluginException
150          *             if an error occured talking to the Web of Trust plugin
151          */
152         public Set<Identity> loadTrustedIdentities(OwnIdentity ownIdentity, Optional<String> context) throws PluginException {
153                 Reply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetIdentitiesByScore").put("Truster", ownIdentity.getId()).put("Selection", "+").put("Context", context.or("")).put("WantTrustValues", "true").get());
154                 SimpleFieldSet fields = reply.getFields();
155                 Set<Identity> identities = new HashSet<Identity>();
156                 int identityCounter = -1;
157                 while (true) {
158                         String id = fields.get("Identity" + ++identityCounter);
159                         if (id == null) {
160                                 break;
161                         }
162                         String nickname = fields.get("Nickname" + identityCounter);
163                         String requestUri = fields.get("RequestURI" + identityCounter);
164                         DefaultIdentity identity = new DefaultIdentity(id, nickname, requestUri);
165                         identity.setContexts(parseContexts("Contexts" + identityCounter + ".", fields));
166                         identity.setProperties(parseProperties("Properties" + identityCounter + ".", fields));
167                         Integer trust = parseInt(fields.get("Trust" + identityCounter), null);
168                         int score = parseInt(fields.get("Score" + identityCounter), 0);
169                         int rank = parseInt(fields.get("Rank" + identityCounter), 0);
170                         identity.setTrust(ownIdentity, new Trust(trust, score, rank));
171                         identities.add(identity);
172                 }
173                 return identities;
174         }
175
176         /**
177          * Adds the given context to the given identity.
178          *
179          * @param ownIdentity
180          *            The identity to add the context to
181          * @param context
182          *            The context to add
183          * @throws PluginException
184          *             if an error occured talking to the Web of Trust plugin
185          */
186         public void addContext(OwnIdentity ownIdentity, String context) throws PluginException {
187                 performRequest(SimpleFieldSetConstructor.create().put("Message", "AddContext").put("Identity", ownIdentity.getId()).put("Context", context).get());
188         }
189
190         /**
191          * Removes the given context from the given identity.
192          *
193          * @param ownIdentity
194          *            The identity to remove the context from
195          * @param context
196          *            The context to remove
197          * @throws PluginException
198          *             if an error occured talking to the Web of Trust plugin
199          */
200         public void removeContext(OwnIdentity ownIdentity, String context) throws PluginException {
201                 performRequest(SimpleFieldSetConstructor.create().put("Message", "RemoveContext").put("Identity", ownIdentity.getId()).put("Context", context).get());
202         }
203
204         /**
205          * Returns the value of the property with the given name.
206          *
207          * @param identity
208          *            The identity whose properties to check
209          * @param name
210          *            The name of the property to return
211          * @return The value of the property, or {@code null} if there is no value
212          * @throws PluginException
213          *             if an error occured talking to the Web of Trust plugin
214          */
215         public String getProperty(Identity identity, String name) throws PluginException {
216                 Reply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetProperty").put("Identity", identity.getId()).put("Property", name).get());
217                 return reply.getFields().get("Property");
218         }
219
220         /**
221          * Sets the property with the given name to the given value.
222          *
223          * @param ownIdentity
224          *            The identity to set the property on
225          * @param name
226          *            The name of the property to set
227          * @param value
228          *            The value to set
229          * @throws PluginException
230          *             if an error occured talking to the Web of Trust plugin
231          */
232         public void setProperty(OwnIdentity ownIdentity, String name, String value) throws PluginException {
233                 performRequest(SimpleFieldSetConstructor.create().put("Message", "SetProperty").put("Identity", ownIdentity.getId()).put("Property", name).put("Value", value).get());
234         }
235
236         /**
237          * Removes the property with the given name.
238          *
239          * @param ownIdentity
240          *            The identity to remove the property from
241          * @param name
242          *            The name of the property to remove
243          * @throws PluginException
244          *             if an error occured talking to the Web of Trust plugin
245          */
246         public void removeProperty(OwnIdentity ownIdentity, String name) throws PluginException {
247                 performRequest(SimpleFieldSetConstructor.create().put("Message", "RemoveProperty").put("Identity", ownIdentity.getId()).put("Property", name).get());
248         }
249
250         /**
251          * Returns the trust for the given identity assigned to it by the given own
252          * identity.
253          *
254          * @param ownIdentity
255          *            The own identity
256          * @param identity
257          *            The identity to get the trust for
258          * @return The trust for the given identity
259          * @throws PluginException
260          *             if an error occured talking to the Web of Trust plugin
261          */
262         public Trust getTrust(OwnIdentity ownIdentity, Identity identity) throws PluginException {
263                 Reply getTrustReply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetIdentity").put("Truster", ownIdentity.getId()).put("Identity", identity.getId()).get());
264                 String trust = getTrustReply.getFields().get("Trust");
265                 String score = getTrustReply.getFields().get("Score");
266                 String rank = getTrustReply.getFields().get("Rank");
267                 Integer explicit = null;
268                 Integer implicit = null;
269                 Integer distance = null;
270                 try {
271                         explicit = Integer.valueOf(trust);
272                 } catch (NumberFormatException nfe1) {
273                         /* ignore. */
274                 }
275                 try {
276                         implicit = Integer.valueOf(score);
277                         distance = Integer.valueOf(rank);
278                 } catch (NumberFormatException nfe1) {
279                         /* ignore. */
280                 }
281                 return new Trust(explicit, implicit, distance);
282         }
283
284         /**
285          * Sets the trust for the given identity.
286          *
287          * @param ownIdentity
288          *            The trusting identity
289          * @param identity
290          *            The trusted identity
291          * @param trust
292          *            The amount of trust (-100 thru 100)
293          * @param comment
294          *            The comment or explanation of the trust value
295          * @throws PluginException
296          *             if an error occured talking to the Web of Trust plugin
297          */
298         public void setTrust(OwnIdentity ownIdentity, Identity identity, int trust, String comment) throws PluginException {
299                 performRequest(SimpleFieldSetConstructor.create().put("Message", "SetTrust").put("Truster", ownIdentity.getId()).put("Trustee", identity.getId()).put("Value", String.valueOf(trust)).put("Comment", comment).get());
300         }
301
302         /**
303          * Removes any trust assignment of the given own identity for the given
304          * identity.
305          *
306          * @param ownIdentity
307          *            The own identity
308          * @param identity
309          *            The identity to remove all trust for
310          * @throws WebOfTrustException
311          *             if an error occurs
312          */
313         public void removeTrust(OwnIdentity ownIdentity, Identity identity) throws WebOfTrustException {
314                 performRequest(SimpleFieldSetConstructor.create().put("Message", "RemoveTrust").put("Truster", ownIdentity.getId()).put("Trustee", identity.getId()).get());
315         }
316
317         /**
318          * Pings the Web of Trust plugin. If the plugin can not be reached, a
319          * {@link PluginException} is thrown.
320          *
321          * @throws PluginException
322          *             if the plugin is not loaded
323          */
324         public void ping() throws PluginException {
325                 performRequest(SimpleFieldSetConstructor.create().put("Message", "Ping").get());
326         }
327
328         //
329         // PRIVATE ACTIONS
330         //
331
332         /**
333          * Parses the contexts from the given fields.
334          *
335          * @param prefix
336          *            The prefix to use to access the contexts
337          * @param fields
338          *            The fields to parse the contexts from
339          * @return The parsed contexts
340          */
341         private static Set<String> parseContexts(String prefix, SimpleFieldSet fields) {
342                 Set<String> contexts = new HashSet<String>();
343                 int contextCounter = -1;
344                 while (true) {
345                         String context = fields.get(prefix + "Context" + ++contextCounter);
346                         if (context == null) {
347                                 break;
348                         }
349                         contexts.add(context);
350                 }
351                 return contexts;
352         }
353
354         /**
355          * Parses the properties from the given fields.
356          *
357          * @param prefix
358          *            The prefix to use to access the properties
359          * @param fields
360          *            The fields to parse the properties from
361          * @return The parsed properties
362          */
363         private static Map<String, String> parseProperties(String prefix, SimpleFieldSet fields) {
364                 Map<String, String> properties = new HashMap<String, String>();
365                 int propertiesCounter = -1;
366                 while (true) {
367                         String propertyName = fields.get(prefix + "Property" + ++propertiesCounter + ".Name");
368                         if (propertyName == null) {
369                                 break;
370                         }
371                         String propertyValue = fields.get(prefix + "Property" + propertiesCounter + ".Value");
372                         properties.put(propertyName, propertyValue);
373                 }
374                 return properties;
375         }
376
377         /**
378          * Sends a request containing the given fields and waits for the target
379          * message.
380          *
381          * @param fields
382          *            The fields of the message
383          * @return The reply message
384          * @throws PluginException
385          *             if the request could not be sent
386          */
387         private Reply performRequest(SimpleFieldSet fields) throws PluginException {
388                 return performRequest(fields, null);
389         }
390
391         /**
392          * Sends a request containing the given fields and waits for the target
393          * message.
394          *
395          * @param fields
396          *            The fields of the message
397          * @param data
398          *            The payload of the message
399          * @return The reply message
400          * @throws PluginException
401          *             if the request could not be sent
402          */
403         private Reply performRequest(SimpleFieldSet fields, Bucket data) throws PluginException {
404                 String identifier = "FCP-Command-" + System.currentTimeMillis() + "-" + counter.getAndIncrement();
405                 Reply reply = new Reply();
406                 PluginIdentifier pluginIdentifier = new PluginIdentifier(WOT_PLUGIN_NAME, identifier);
407                 replies.put(pluginIdentifier, reply);
408
409                 logger.log(Level.FINE, String.format("Sending FCP Request: %s", fields.get("Message")));
410                 synchronized (reply) {
411                         try {
412                                 pluginConnector.sendRequest(WOT_PLUGIN_NAME, identifier, fields, data);
413                                 while (reply.getFields() == null) {
414                                         try {
415                                                 reply.wait();
416                                         } catch (InterruptedException ie1) {
417                                                 logger.log(Level.WARNING, String.format("Got interrupted while waiting for reply on %s.", fields.get("Message")), ie1);
418                                         }
419                                 }
420                         } finally {
421                                 replies.remove(pluginIdentifier);
422                         }
423                 }
424                 logger.log(Level.FINEST, String.format("Received FCP Response for %s: %s", fields.get("Message"), (reply.getFields() != null) ? reply.getFields().get("Message") : null));
425                 if ((reply.getFields() == null) || "Error".equals(reply.getFields().get("Message"))) {
426                         throw new PluginException("Could not perform request for " + fields.get("Message"));
427                 }
428                 return reply;
429         }
430
431         /**
432          * Notifies the connector that a plugin reply was received.
433          *
434          * @param receivedReplyEvent
435          *            The event
436          */
437         @Subscribe
438         public void receivedReply(ReceivedReplyEvent receivedReplyEvent) {
439                 PluginIdentifier pluginIdentifier = new PluginIdentifier(receivedReplyEvent.pluginName(), receivedReplyEvent.identifier());
440                 Reply reply = replies.remove(pluginIdentifier);
441                 if (reply == null) {
442                         return;
443                 }
444                 logger.log(Level.FINEST, String.format("Received Reply from Plugin: %s", receivedReplyEvent.fieldSet().get("Message")));
445                 synchronized (reply) {
446                         reply.setFields(receivedReplyEvent.fieldSet());
447                         reply.setData(receivedReplyEvent.data());
448                         reply.notify();
449                 }
450         }
451
452         /**
453          * Container for the data of the reply from a plugin.
454          *
455          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
456          */
457         private static class Reply {
458
459                 /** The fields of the reply. */
460                 private SimpleFieldSet fields;
461
462                 /** The payload of the reply. */
463                 private Bucket data;
464
465                 /** Empty constructor. */
466                 public Reply() {
467                         /* do nothing. */
468                 }
469
470                 /**
471                  * Returns the fields of the reply.
472                  *
473                  * @return The fields of the reply
474                  */
475                 public SimpleFieldSet getFields() {
476                         return fields;
477                 }
478
479                 /**
480                  * Sets the fields of the reply.
481                  *
482                  * @param fields
483                  *            The fields of the reply
484                  */
485                 public void setFields(SimpleFieldSet fields) {
486                         this.fields = fields;
487                 }
488
489                 /**
490                  * Returns the payload of the reply.
491                  *
492                  * @return The payload of the reply (may be {@code null})
493                  */
494                 @SuppressWarnings("unused")
495                 public Bucket getData() {
496                         return data;
497                 }
498
499                 /**
500                  * Sets the payload of the reply.
501                  *
502                  * @param data
503                  *            The payload of the reply (may be {@code null})
504                  */
505                 public void setData(Bucket data) {
506                         this.data = data;
507                 }
508
509         }
510
511         /**
512          * Helper method to create {@link SimpleFieldSet}s with terser code.
513          *
514          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
515          */
516         private static class SimpleFieldSetConstructor {
517
518                 /** The field set being created. */
519                 private SimpleFieldSet simpleFieldSet;
520
521                 /**
522                  * Creates a new simple field set constructor.
523                  *
524                  * @param shortLived
525                  *            {@code true} if the resulting simple field set should be
526                  *            short-lived, {@code false} otherwise
527                  */
528                 private SimpleFieldSetConstructor(boolean shortLived) {
529                         simpleFieldSet = new SimpleFieldSet(shortLived);
530                 }
531
532                 //
533                 // ACCESSORS
534                 //
535
536                 /**
537                  * Returns the created simple field set.
538                  *
539                  * @return The created simple field set
540                  */
541                 public SimpleFieldSet get() {
542                         return simpleFieldSet;
543                 }
544
545                 /**
546                  * Sets the field with the given name to the given value.
547                  *
548                  * @param name
549                  *            The name of the fleld
550                  * @param value
551                  *            The value of the field
552                  * @return This constructor (for method chaining)
553                  */
554                 public SimpleFieldSetConstructor put(String name, String value) {
555                         simpleFieldSet.putOverwrite(name, value);
556                         return this;
557                 }
558
559                 //
560                 // ACTIONS
561                 //
562
563                 /**
564                  * Creates a new simple field set constructor.
565                  *
566                  * @return The created simple field set constructor
567                  */
568                 public static SimpleFieldSetConstructor create() {
569                         return create(true);
570                 }
571
572                 /**
573                  * Creates a new simple field set constructor.
574                  *
575                  * @param shortLived
576                  *            {@code true} if the resulting simple field set should be
577                  *            short-lived, {@code false} otherwise
578                  * @return The created simple field set constructor
579                  */
580                 public static SimpleFieldSetConstructor create(boolean shortLived) {
581                         return new SimpleFieldSetConstructor(shortLived);
582                 }
583
584         }
585
586         /**
587          * Container for identifying plugins. Plugins are identified by their plugin
588          * name and their unique identifier.
589          *
590          * @author <a href="mailto:d.roden@xplosion.de">David Roden</a>
591          */
592         private static class PluginIdentifier {
593
594                 /** The plugin name. */
595                 private final String pluginName;
596
597                 /** The plugin identifier. */
598                 private final String identifier;
599
600                 /**
601                  * Creates a new plugin identifier.
602                  *
603                  * @param pluginName
604                  *            The name of the plugin
605                  * @param identifier
606                  *            The identifier of the plugin
607                  */
608                 public PluginIdentifier(String pluginName, String identifier) {
609                         this.pluginName = pluginName;
610                         this.identifier = identifier;
611                 }
612
613                 //
614                 // OBJECT METHODS
615                 //
616
617                 /**
618                  * {@inheritDoc}
619                  */
620                 @Override
621                 public int hashCode() {
622                         return pluginName.hashCode() ^ identifier.hashCode();
623                 }
624
625                 /**
626                  * {@inheritDoc}
627                  */
628                 @Override
629                 public boolean equals(Object object) {
630                         if (!(object instanceof PluginIdentifier)) {
631                                 return false;
632                         }
633                         PluginIdentifier pluginIdentifier = (PluginIdentifier) object;
634                         return pluginName.equals(pluginIdentifier.pluginName) && identifier.equals(pluginIdentifier.identifier);
635                 }
636
637         }
638
639 }