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