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