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