Throw better suited exception.
[WoTNS.git] / src / main / java / net / pterodactylus / wotns / 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.wotns.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.util.logging.Logging;
28 import net.pterodactylus.util.number.Numbers;
29 import net.pterodactylus.wotns.freenet.plugin.ConnectorListener;
30 import net.pterodactylus.wotns.freenet.plugin.PluginConnector;
31 import net.pterodactylus.wotns.freenet.plugin.PluginException;
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 implements ConnectorListener {
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         /** A random connection identifier. */
49         private static final String PLUGIN_CONNECTION_IDENTIFIER = "WoTNS-WoT-Connector-" + Math.abs(Math.random());
50
51         /** The current reply. */
52         private Reply reply;
53
54         /** The plugin connector. */
55         private final PluginConnector pluginConnector;
56
57         /**
58          * Creates a new Web of Trust connector that uses the given plugin
59          * connector.
60          *
61          * @param pluginConnector
62          *            The plugin connector
63          */
64         public WebOfTrustConnector(PluginConnector pluginConnector) {
65                 this.pluginConnector = pluginConnector;
66                 pluginConnector.addConnectorListener(WOT_PLUGIN_NAME, PLUGIN_CONNECTION_IDENTIFIER, this);
67         }
68
69         //
70         // ACTIONS
71         //
72
73         /**
74          * Stops the web of trust connector and disconnects from the plugin
75          * connector.
76          */
77         public void stop() {
78                 pluginConnector.removeConnectorListener(WOT_PLUGIN_NAME, PLUGIN_CONNECTION_IDENTIFIER, this);
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                 @SuppressWarnings("hiding")
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 WebOfTrustException
118          *             if an error occured talking to the Web of Trust plugin
119          */
120         public Set<Identity> loadTrustedIdentities(OwnIdentity ownIdentity) throws WebOfTrustException {
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 WebOfTrustException
134          *             if an error occured talking to the Web of Trust plugin
135          */
136         public Set<Identity> loadTrustedIdentities(OwnIdentity ownIdentity, String context) throws WebOfTrustException {
137                 @SuppressWarnings("hiding")
138                 Reply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetIdentitiesByScore").put("TreeOwner", ownIdentity.getId()).put("Selection", "+").put("Context", (context == null) ? "" : context).get());
139                 SimpleFieldSet fields = reply.getFields();
140                 Set<Identity> identities = new HashSet<Identity>();
141                 int identityCounter = -1;
142                 while (true) {
143                         String id = fields.get("Identity" + ++identityCounter);
144                         if (id == null) {
145                                 break;
146                         }
147                         String nickname = fields.get("Nickname" + identityCounter);
148                         String requestUri = fields.get("RequestURI" + identityCounter);
149                         DefaultIdentity identity = new DefaultIdentity(this, id, nickname, requestUri);
150                         identity.setContextsPrivate(parseContexts("Contexts" + identityCounter + ".", fields));
151                         identity.setPropertiesPrivate(parseProperties("Properties" + identityCounter + ".", fields));
152                         identity.setTrustPrivate(ownIdentity, new Trust(Numbers.safeParseInteger(fields.get("Trust" + identityCounter)), Numbers.safeParseInteger(fields.get("Score" + identityCounter)), Numbers.safeParseInteger(fields.get("Rank" + identityCounter))));
153                         identities.add(identity);
154                 }
155                 return identities;
156         }
157
158         /**
159          * Adds the given context to the given identity.
160          *
161          * @param ownIdentity
162          *            The identity to add the context to
163          * @param context
164          *            The context to add
165          * @throws PluginException
166          *             if an error occured talking to the Web of Trust plugin
167          */
168         public void addContext(OwnIdentity ownIdentity, String context) throws PluginException {
169                 performRequest(SimpleFieldSetConstructor.create().put("Message", "AddContext").put("Identity", ownIdentity.getId()).put("Context", context).get());
170         }
171
172         /**
173          * Removes the given context from the given identity.
174          *
175          * @param ownIdentity
176          *            The identity to remove the context from
177          * @param context
178          *            The context to remove
179          * @throws PluginException
180          *             if an error occured talking to the Web of Trust plugin
181          */
182         public void removeContext(OwnIdentity ownIdentity, String context) throws PluginException {
183                 performRequest(SimpleFieldSetConstructor.create().put("Message", "RemoveContext").put("Identity", ownIdentity.getId()).put("Context", context).get());
184         }
185
186         /**
187          * Returns the value of the property with the given name.
188          *
189          * @param identity
190          *            The identity whose properties to check
191          * @param name
192          *            The name of the property to return
193          * @return The value of the property, or {@code null} if there is no value
194          * @throws PluginException
195          *             if an error occured talking to the Web of Trust plugin
196          */
197         public String getProperty(Identity identity, String name) throws PluginException {
198                 @SuppressWarnings("hiding")
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 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 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 synchronized Reply performRequest(SimpleFieldSet fields, Bucket data) throws PluginException {
387                 reply = new Reply();
388                 logger.log(Level.FINE, "Sending FCP Request: " + fields.get("Message"));
389                 synchronized (reply) {
390                         pluginConnector.sendRequest(WOT_PLUGIN_NAME, PLUGIN_CONNECTION_IDENTIFIER, fields, data);
391                         try {
392                                 reply.wait();
393                         } catch (InterruptedException ie1) {
394                                 logger.log(Level.WARNING, "Got interrupted while waiting for reply on " + fields.get("Message") + ".", ie1);
395                         }
396                 }
397                 logger.log(Level.FINEST, "Received FCP Response for %s: %s", new Object[] { fields.get("Message"), (reply.getFields() != null) ? reply.getFields().get("Message") : null });
398                 if ((reply.getFields() == null) || "Error".equals(reply.getFields().get("Message"))) {
399                         throw new PluginException("Could not perform request for " + fields.get("Message"));
400                 }
401                 return reply;
402         }
403
404         //
405         // INTERFACE ConnectorListener
406         //
407
408         /**
409          * {@inheritDoc}
410          */
411         @Override
412         public void receivedReply(@SuppressWarnings("hiding") PluginConnector pluginConnector, SimpleFieldSet fields, Bucket data) {
413                 String messageName = fields.get("Message");
414                 logger.log(Level.FINEST, "Received Reply from Plugin: " + messageName);
415                 synchronized (reply) {
416                         reply.setFields(fields);
417                         reply.setData(data);
418                         reply.notify();
419                 }
420         }
421
422         /**
423          * Container for the data of the reply from a plugin.
424          *
425          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
426          */
427         private static class Reply {
428
429                 /** The fields of the reply. */
430                 private SimpleFieldSet fields;
431
432                 /** The payload of the reply. */
433                 private Bucket data;
434
435                 /** Empty constructor. */
436                 public Reply() {
437                         /* do nothing. */
438                 }
439
440                 /**
441                  * Returns the fields of the reply.
442                  *
443                  * @return The fields of the reply
444                  */
445                 public SimpleFieldSet getFields() {
446                         return fields;
447                 }
448
449                 /**
450                  * Sets the fields of the reply.
451                  *
452                  * @param fields
453                  *            The fields of the reply
454                  */
455                 public void setFields(SimpleFieldSet fields) {
456                         this.fields = fields;
457                 }
458
459                 /**
460                  * Returns the payload of the reply.
461                  *
462                  * @return The payload of the reply (may be {@code null})
463                  */
464                 @SuppressWarnings("unused")
465                 public Bucket getData() {
466                         return data;
467                 }
468
469                 /**
470                  * Sets the payload of the reply.
471                  *
472                  * @param data
473                  *            The payload of the reply (may be {@code null})
474                  */
475                 public void setData(Bucket data) {
476                         this.data = data;
477                 }
478
479         }
480
481         /**
482          * Helper method to create {@link SimpleFieldSet}s with terser code.
483          *
484          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
485          */
486         private static class SimpleFieldSetConstructor {
487
488                 /** The field set being created. */
489                 private SimpleFieldSet simpleFieldSet;
490
491                 /**
492                  * Creates a new simple field set constructor.
493                  *
494                  * @param shortLived
495                  *            {@code true} if the resulting simple field set should be
496                  *            short-lived, {@code false} otherwise
497                  */
498                 private SimpleFieldSetConstructor(boolean shortLived) {
499                         simpleFieldSet = new SimpleFieldSet(shortLived);
500                 }
501
502                 //
503                 // ACCESSORS
504                 //
505
506                 /**
507                  * Returns the created simple field set.
508                  *
509                  * @return The created simple field set
510                  */
511                 public SimpleFieldSet get() {
512                         return simpleFieldSet;
513                 }
514
515                 /**
516                  * Sets the field with the given name to the given value.
517                  *
518                  * @param name
519                  *            The name of the fleld
520                  * @param value
521                  *            The value of the field
522                  * @return This constructor (for method chaining)
523                  */
524                 public SimpleFieldSetConstructor put(String name, String value) {
525                         simpleFieldSet.putOverwrite(name, value);
526                         return this;
527                 }
528
529                 //
530                 // ACTIONS
531                 //
532
533                 /**
534                  * Creates a new simple field set constructor.
535                  *
536                  * @return The created simple field set constructor
537                  */
538                 public static SimpleFieldSetConstructor create() {
539                         return create(true);
540                 }
541
542                 /**
543                  * Creates a new simple field set constructor.
544                  *
545                  * @param shortLived
546                  *            {@code true} if the resulting simple field set should be
547                  *            short-lived, {@code false} otherwise
548                  * @return The created simple field set constructor
549                  */
550                 public static SimpleFieldSetConstructor create(boolean shortLived) {
551                         SimpleFieldSetConstructor simpleFieldSetConstructor = new SimpleFieldSetConstructor(shortLived);
552                         return simpleFieldSetConstructor;
553                 }
554
555         }
556
557 }