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