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