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