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