🎨 Use nullable type instead of optional
[Sone.git] / src / main / java / net / pterodactylus / sone / freenet / wot / WebOfTrustConnector.java
1 /*
2  * Sone - WebOfTrustConnector.java - Copyright Â© 2010–2019 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 javax.annotation.*;
32
33 import net.pterodactylus.sone.freenet.plugin.PluginConnector;
34 import net.pterodactylus.sone.freenet.plugin.PluginException;
35 import net.pterodactylus.sone.freenet.plugin.event.ReceivedReplyEvent;
36
37 import com.google.common.base.Optional;
38 import com.google.common.collect.MapMaker;
39 import com.google.common.eventbus.Subscribe;
40 import com.google.inject.Inject;
41 import com.google.inject.Singleton;
42
43 import freenet.support.SimpleFieldSet;
44 import freenet.support.api.Bucket;
45
46 /**
47  * Connector for the Web of Trust plugin.
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<>();
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, null);
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, @Nullable String context) throws PluginException {
145                 Reply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetIdentitiesByScore").put("Truster", ownIdentity.getId()).put("Selection", "+").put("Context", (context ==null) ? "" : context).put("WantTrustValues", "true").get());
146                 SimpleFieldSet fields = reply.getFields();
147                 Set<Identity> identities = new HashSet<>();
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<>();
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<>();
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         private static class Reply {
448
449                 /** The fields of the reply. */
450                 private SimpleFieldSet fields;
451
452                 /** The payload of the reply. */
453                 private Bucket data;
454
455                 /** Empty constructor. */
456                 public Reply() {
457                         /* do nothing. */
458                 }
459
460                 /**
461                  * Returns the fields of the reply.
462                  *
463                  * @return The fields of the reply
464                  */
465                 public SimpleFieldSet getFields() {
466                         return fields;
467                 }
468
469                 /**
470                  * Sets the fields of the reply.
471                  *
472                  * @param fields
473                  *            The fields of the reply
474                  */
475                 public void setFields(SimpleFieldSet fields) {
476                         this.fields = fields;
477                 }
478
479                 /**
480                  * Returns the payload of the reply.
481                  *
482                  * @return The payload of the reply (may be {@code null})
483                  */
484                 @SuppressWarnings("unused")
485                 public Bucket getData() {
486                         return data;
487                 }
488
489                 /**
490                  * Sets the payload of the reply.
491                  *
492                  * @param data
493                  *            The payload of the reply (may be {@code null})
494                  */
495                 public void setData(Bucket data) {
496                         this.data = data;
497                 }
498
499         }
500
501         /**
502          * Helper method to create {@link SimpleFieldSet}s with terser code.
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                         return new SimpleFieldSetConstructor(shortLived);
570                 }
571
572         }
573
574         /**
575          * Container for identifying plugins. Plugins are identified by their plugin
576          * name and their unique identifier.
577          */
578         private static class PluginIdentifier {
579
580                 /** The plugin name. */
581                 private final String pluginName;
582
583                 /** The plugin identifier. */
584                 private final String identifier;
585
586                 /**
587                  * Creates a new plugin identifier.
588                  *
589                  * @param pluginName
590                  *            The name of the plugin
591                  * @param identifier
592                  *            The identifier of the plugin
593                  */
594                 public PluginIdentifier(String pluginName, String identifier) {
595                         this.pluginName = pluginName;
596                         this.identifier = identifier;
597                 }
598
599                 //
600                 // OBJECT METHODS
601                 //
602
603                 /**
604                  * {@inheritDoc}
605                  */
606                 @Override
607                 public int hashCode() {
608                         return pluginName.hashCode() ^ identifier.hashCode();
609                 }
610
611                 /**
612                  * {@inheritDoc}
613                  */
614                 @Override
615                 public boolean equals(Object object) {
616                         if (!(object instanceof PluginIdentifier)) {
617                                 return false;
618                         }
619                         PluginIdentifier pluginIdentifier = (PluginIdentifier) object;
620                         return pluginName.equals(pluginIdentifier.pluginName) && identifier.equals(pluginIdentifier.identifier);
621                 }
622
623         }
624
625 }