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