Merge branch 'release-0.8.3'
[Sone.git] / src / main / java / net / pterodactylus / sone / core / WebOfTrustUpdater.java
1 /*
2  * Sone - WebOfTrustUpdater.java - Copyright © 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.core;
19
20 import java.util.concurrent.BlockingQueue;
21 import java.util.concurrent.LinkedBlockingQueue;
22 import java.util.logging.Level;
23 import java.util.logging.Logger;
24
25 import net.pterodactylus.sone.freenet.plugin.PluginException;
26 import net.pterodactylus.sone.freenet.wot.DefaultIdentity;
27 import net.pterodactylus.sone.freenet.wot.Identity;
28 import net.pterodactylus.sone.freenet.wot.OwnIdentity;
29 import net.pterodactylus.sone.freenet.wot.Trust;
30 import net.pterodactylus.sone.freenet.wot.WebOfTrustConnector;
31 import net.pterodactylus.sone.freenet.wot.WebOfTrustException;
32 import net.pterodactylus.util.logging.Logging;
33 import net.pterodactylus.util.service.AbstractService;
34 import net.pterodactylus.util.validation.Validation;
35
36 /**
37  * Updates WebOfTrust identity data in a background thread because communicating
38  * with the WebOfTrust plugin can potentially last quite long.
39  *
40  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
41  */
42 public class WebOfTrustUpdater extends AbstractService {
43
44         /** The logger. */
45         private static final Logger logger = Logging.getLogger(WebOfTrustUpdater.class);
46
47         /** Stop job. */
48         @SuppressWarnings("synthetic-access")
49         private final WebOfTrustUpdateJob stopJob = new WebOfTrustUpdateJob();
50
51         /** The web of trust connector. */
52         private final WebOfTrustConnector webOfTrustConnector;
53
54         /** The queue for jobs. */
55         private final BlockingQueue<WebOfTrustUpdateJob> updateJobs = new LinkedBlockingQueue<WebOfTrustUpdateJob>();
56
57         /**
58          * Creates a new trust updater.
59          *
60          * @param webOfTrustConnector
61          *            The web of trust connector
62          */
63         public WebOfTrustUpdater(WebOfTrustConnector webOfTrustConnector) {
64                 super("Trust Updater");
65                 this.webOfTrustConnector = webOfTrustConnector;
66         }
67
68         //
69         // ACTIONS
70         //
71
72         /**
73          * Updates the trust relation between the truster and the trustee. This
74          * method will return immediately and perform a trust update in the
75          * background.
76          *
77          * @param truster
78          *            The identity giving the trust
79          * @param trustee
80          *            The identity receiving the trust
81          * @param score
82          *            The new level of trust (from -100 to 100, may be {@code null}
83          *            to remove the trust completely)
84          * @param comment
85          *            The comment of the trust relation
86          */
87         public void setTrust(OwnIdentity truster, Identity trustee, Integer score, String comment) {
88                 SetTrustJob setTrustJob = new SetTrustJob(truster, trustee, score, comment);
89                 if (updateJobs.contains(setTrustJob)) {
90                         updateJobs.remove(setTrustJob);
91                 }
92                 logger.log(Level.FINER, "Adding Trust Update Job: " + setTrustJob);
93                 try {
94                         updateJobs.put(setTrustJob);
95                 } catch (InterruptedException e) {
96                         /* the queue is unbounded so it should never block. */
97                 }
98         }
99
100         /**
101          * Adds the given context to the given own identity.
102          *
103          * @param ownIdentity
104          *            The own identity to add the context to
105          * @param context
106          *            The context to add
107          */
108         public void addContext(OwnIdentity ownIdentity, String context) {
109                 addContextWait(ownIdentity, context, false);
110         }
111
112         /**
113          * Adds the given context to the given own identity, waiting for completion
114          * of the operation.
115          *
116          * @param ownIdentity
117          *            The own identity to add the context to
118          * @param context
119          *            The context to add
120          * @return {@code true} if the context was added successfully, {@code false}
121          *         otherwise
122          */
123         public boolean addContextWait(OwnIdentity ownIdentity, String context) {
124                 return addContextWait(ownIdentity, context, true);
125         }
126
127         /**
128          * Adds the given context to the given own identity, waiting for completion
129          * of the operation.
130          *
131          * @param ownIdentity
132          *            The own identity to add the context to
133          * @param context
134          *            The context to add
135          * @param wait
136          *            {@code true} to wait for the end of the operation,
137          *            {@code false} to return immediately
138          * @return {@code true} if the context was added successfully, {@code false}
139          *         if the context was not added successfully, or if the job should
140          *         not wait for completion
141          */
142         private boolean addContextWait(OwnIdentity ownIdentity, String context, boolean wait) {
143                 AddContextJob addContextJob = new AddContextJob(ownIdentity, context);
144                 if (!updateJobs.contains(addContextJob)) {
145                         logger.log(Level.FINER, "Adding Context Job: " + addContextJob);
146                         try {
147                                 updateJobs.put(addContextJob);
148                         } catch (InterruptedException ie1) {
149                                 /* the queue is unbounded so it should never block. */
150                         }
151                         if (wait) {
152                                 return addContextJob.waitForCompletion();
153                         }
154                 } else if (wait) {
155                         for (WebOfTrustUpdateJob updateJob : updateJobs) {
156                                 if (updateJob.equals(addContextJob)) {
157                                         return updateJob.waitForCompletion();
158                                 }
159                         }
160                 }
161                 return false;
162         }
163
164         /**
165          * Removes the given context from the given own identity.
166          *
167          * @param ownIdentity
168          *            The own identity to remove the context from
169          * @param context
170          *            The context to remove
171          */
172         public void removeContext(OwnIdentity ownIdentity, String context) {
173                 RemoveContextJob removeContextJob = new RemoveContextJob(ownIdentity, context);
174                 if (!updateJobs.contains(removeContextJob)) {
175                         logger.log(Level.FINER, "Adding Context Job: " + removeContextJob);
176                         try {
177                                 updateJobs.put(removeContextJob);
178                         } catch (InterruptedException ie1) {
179                                 /* the queue is unbounded so it should never block. */
180                         }
181                 }
182         }
183
184         /**
185          * Sets a property on the given own identity.
186          *
187          * @param ownIdentity
188          *            The own identity to set the property on
189          * @param propertyName
190          *            The name of the property to set
191          * @param propertyValue
192          *            The value of the property to set
193          */
194         public void setProperty(OwnIdentity ownIdentity, String propertyName, String propertyValue) {
195                 SetPropertyJob setPropertyJob = new SetPropertyJob(ownIdentity, propertyName, propertyValue);
196                 if (updateJobs.contains(setPropertyJob)) {
197                         updateJobs.remove(setPropertyJob);
198                 }
199                 logger.log(Level.FINER, "Adding Property Job: " + setPropertyJob);
200                 try {
201                         updateJobs.put(setPropertyJob);
202                 } catch (InterruptedException e) {
203                         /* the queue is unbounded so it should never block. */
204                 }
205         }
206
207         /**
208          * Removes a property from the given own identity.
209          *
210          * @param ownIdentity
211          *            The own identity to remove the property from
212          * @param propertyName
213          *            The name of the property to remove
214          */
215         public void removeProperty(OwnIdentity ownIdentity, String propertyName) {
216                 setProperty(ownIdentity, propertyName, null);
217         }
218
219         //
220         // SERVICE METHODS
221         //
222
223         /**
224          * {@inheritDoc}
225          */
226         @Override
227         protected void serviceRun() {
228                 while (!shouldStop()) {
229                         try {
230                                 WebOfTrustUpdateJob updateJob = updateJobs.take();
231                                 if (shouldStop() || (updateJob == stopJob)) {
232                                         break;
233                                 }
234                                 logger.log(Level.FINE, "Running Trust Update Job: " + updateJob);
235                                 long startTime = System.currentTimeMillis();
236                                 updateJob.run();
237                                 long endTime = System.currentTimeMillis();
238                                 logger.log(Level.FINE, "Trust Update Job finished, took " + (endTime - startTime) + " ms.");
239                         } catch (InterruptedException ie1) {
240                                 /* happens, ignore, loop. */
241                         }
242                 }
243         }
244
245         /**
246          * {@inheritDoc}
247          */
248         @Override
249         protected void serviceStop() {
250                 try {
251                         updateJobs.put(stopJob);
252                 } catch (InterruptedException ie1) {
253                         /* the queue is unbounded so it should never block. */
254                 }
255         }
256
257         /**
258          * Base class for WebOfTrust update jobs.
259          *
260          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
261          */
262         private class WebOfTrustUpdateJob {
263
264                 /** Object for synchronization. */
265                 @SuppressWarnings("hiding")
266                 private final Object syncObject = new Object();
267
268                 /** Whether the job has finished. */
269                 private boolean finished;
270
271                 /** Whether the job was successful. */
272                 private boolean success;
273
274                 //
275                 // ACTIONS
276                 //
277
278                 /**
279                  * Performs the actual update operation.
280                  * <p>
281                  * The implementation of this class does nothing.
282                  */
283                 public void run() {
284                         /* does nothing. */
285                 }
286
287                 /**
288                  * Waits for completion of this job or stopping of the WebOfTrust
289                  * updater.
290                  *
291                  * @return {@code true} if this job finished successfully, {@code false}
292                  *         otherwise
293                  *
294                  * @see WebOfTrustUpdater#stop()
295                  */
296                 @SuppressWarnings("synthetic-access")
297                 public boolean waitForCompletion() {
298                         synchronized (syncObject) {
299                                 while (!finished && !shouldStop()) {
300                                         try {
301                                                 syncObject.wait();
302                                         } catch (InterruptedException ie1) {
303                                                 /* we’re looping, ignore. */
304                                         }
305                                 }
306                                 return success;
307                         }
308                 }
309
310                 //
311                 // PROTECTED METHODS
312                 //
313
314                 /**
315                  * Signals that this job has finished.
316                  *
317                  * @param success
318                  *            {@code true} if this job finished successfully,
319                  *            {@code false} otherwise
320                  */
321                 protected void finish(boolean success) {
322                         synchronized (syncObject) {
323                                 finished = true;
324                                 this.success = success;
325                                 syncObject.notifyAll();
326                         }
327                 }
328
329         }
330
331         /**
332          * Base class for WebOfTrust trust update jobs.
333          *
334          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
335          */
336         private class WebOfTrustTrustUpdateJob extends WebOfTrustUpdateJob {
337
338                 /** The identity giving the trust. */
339                 protected final OwnIdentity truster;
340
341                 /** The identity receiving the trust. */
342                 protected final Identity trustee;
343
344                 /**
345                  * Creates a new trust update job.
346                  *
347                  * @param truster
348                  *            The identity giving the trust
349                  * @param trustee
350                  *            The identity receiving the trust
351                  */
352                 @SuppressWarnings("synthetic-access")
353                 public WebOfTrustTrustUpdateJob(OwnIdentity truster, Identity trustee) {
354                         this.truster = truster;
355                         this.trustee = trustee;
356                 }
357
358                 //
359                 // OBJECT METHODS
360                 //
361
362                 /**
363                  * {@inheritDoc}
364                  */
365                 @Override
366                 public boolean equals(Object object) {
367                         if ((object == null) || !object.getClass().equals(getClass())) {
368                                 return false;
369                         }
370                         WebOfTrustTrustUpdateJob updateJob = (WebOfTrustTrustUpdateJob) object;
371                         return ((truster == null) ? (updateJob.truster == null) : updateJob.truster.equals(truster)) && ((trustee == null) ? (updateJob.trustee == null) : updateJob.trustee.equals(trustee));
372                 }
373
374                 /**
375                  * {@inheritDoc}
376                  */
377                 @Override
378                 public int hashCode() {
379                         return getClass().hashCode() ^ ((truster == null) ? 0 : truster.hashCode()) ^ ((trustee == null) ? 0 : trustee.hashCode());
380                 }
381
382                 /**
383                  * {@inheritDoc}
384                  */
385                 @Override
386                 public String toString() {
387                         return String.format("%s[truster=%s,trustee=%s]", getClass().getSimpleName(), (truster == null) ? null : truster.getId(), (trustee == null) ? null : trustee.getId());
388                 }
389
390         }
391
392         /**
393          * Update job that sets the trust relation between two identities.
394          *
395          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
396          */
397         private class SetTrustJob extends WebOfTrustTrustUpdateJob {
398
399                 /** The score of the relation. */
400                 private final Integer score;
401
402                 /** The comment of the relation. */
403                 private final String comment;
404
405                 /**
406                  * Creates a new set trust job.
407                  *
408                  * @param truster
409                  *            The identity giving the trust
410                  * @param trustee
411                  *            The identity receiving the trust
412                  * @param score
413                  *            The score of the trust (from -100 to 100, may be
414                  *            {@code null} to remote the trust relation completely)
415                  * @param comment
416                  *            The comment of the trust relation
417                  */
418                 public SetTrustJob(OwnIdentity truster, Identity trustee, Integer score, String comment) {
419                         super(truster, trustee);
420                         this.score = score;
421                         this.comment = comment;
422                 }
423
424                 /**
425                  * {@inheritDoc}
426                  */
427                 @Override
428                 @SuppressWarnings("synthetic-access")
429                 public void run() {
430                         try {
431                                 if (score != null) {
432                                         if (trustee instanceof DefaultIdentity) {
433                                                 ((DefaultIdentity) trustee).setTrust(truster, new Trust(score, null, 0));
434                                         }
435                                         webOfTrustConnector.setTrust(truster, trustee, score, comment);
436                                 } else {
437                                         if (trustee instanceof DefaultIdentity) {
438                                                 ((DefaultIdentity) trustee).setTrust(truster, null);
439                                         }
440                                         webOfTrustConnector.removeTrust(truster, trustee);
441                                 }
442                                 finish(true);
443                         } catch (WebOfTrustException wote1) {
444                                 logger.log(Level.WARNING, "Could not set Trust value for " + truster + " -> " + trustee + " to " + score + " (" + comment + ")!", wote1);
445                                 finish(false);
446                         }
447                 }
448
449         }
450
451         /**
452          * Base class for context updates of an {@link OwnIdentity}.
453          *
454          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
455          */
456         private class WebOfTrustContextUpdateJob extends WebOfTrustUpdateJob {
457
458                 /** The own identity whose contexts to manage. */
459                 protected final OwnIdentity ownIdentity;
460
461                 /** The context to update. */
462                 protected final String context;
463
464                 /**
465                  * Creates a new context update job.
466                  *
467                  * @param ownIdentity
468                  *            The own identity to update
469                  * @param context
470                  *            The context to update
471                  */
472                 @SuppressWarnings("synthetic-access")
473                 public WebOfTrustContextUpdateJob(OwnIdentity ownIdentity, String context) {
474                         Validation.begin().isNotNull("OwnIdentity", ownIdentity).isNotNull("Context", context).check();
475                         this.ownIdentity = ownIdentity;
476                         this.context = context;
477                 }
478
479                 //
480                 // OBJECT METHODS
481                 //
482
483                 /**
484                  * {@inheritDoc}
485                  */
486                 @Override
487                 public boolean equals(Object object) {
488                         if ((object == null) || !object.getClass().equals(getClass())) {
489                                 return false;
490                         }
491                         WebOfTrustContextUpdateJob updateJob = (WebOfTrustContextUpdateJob) object;
492                         return updateJob.ownIdentity.equals(ownIdentity) && updateJob.context.equals(context);
493                 }
494
495                 /**
496                  * {@inheritDoc}
497                  */
498                 @Override
499                 public int hashCode() {
500                         return getClass().hashCode() ^ ownIdentity.hashCode() ^ context.hashCode();
501                 }
502
503                 /**
504                  * {@inheritDoc}
505                  */
506                 @Override
507                 public String toString() {
508                         return String.format("%s[ownIdentity=%s,context=%s]", getClass().getSimpleName(), ownIdentity, context);
509                 }
510
511         }
512
513         /**
514          * Job that adds a context to an {@link OwnIdentity}.
515          *
516          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
517          */
518         private class AddContextJob extends WebOfTrustContextUpdateJob {
519
520                 /**
521                  * Creates a new add-context job.
522                  *
523                  * @param ownIdentity
524                  *            The own identity whose contexts to manage
525                  * @param context
526                  *            The context to add
527                  */
528                 public AddContextJob(OwnIdentity ownIdentity, String context) {
529                         super(ownIdentity, context);
530                 }
531
532                 /**
533                  * {@inheritDoc}
534                  */
535                 @Override
536                 @SuppressWarnings("synthetic-access")
537                 public void run() {
538                         try {
539                                 webOfTrustConnector.addContext(ownIdentity, context);
540                                 ownIdentity.addContext(context);
541                                 finish(true);
542                         } catch (PluginException pe1) {
543                                 logger.log(Level.WARNING, String.format("Could not add Context “%2$s” to Own Identity %1$s!", ownIdentity, context), pe1);
544                                 finish(false);
545                         }
546                 }
547
548         }
549
550         /**
551          * Job that removes a context from an {@link OwnIdentity}.
552          *
553          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
554          */
555         private class RemoveContextJob extends WebOfTrustContextUpdateJob {
556
557                 /**
558                  * Creates a new remove-context job.
559                  *
560                  * @param ownIdentity
561                  *            The own identity whose contexts to manage
562                  * @param context
563                  *            The context to remove
564                  */
565                 public RemoveContextJob(OwnIdentity ownIdentity, String context) {
566                         super(ownIdentity, context);
567                 }
568
569                 /**
570                  * {@inheritDoc}
571                  */
572                 @Override
573                 @SuppressWarnings("synthetic-access")
574                 public void run() {
575                         try {
576                                 webOfTrustConnector.removeContext(ownIdentity, context);
577                                 ownIdentity.removeContext(context);
578                                 finish(true);
579                         } catch (PluginException pe1) {
580                                 logger.log(Level.WARNING, String.format("Could not remove Context “%2$s” to Own Identity %1$s!", ownIdentity, context), pe1);
581                                 finish(false);
582                         }
583                 }
584
585         }
586
587         /**
588          * Base class for update jobs that deal with properties.
589          *
590          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
591          */
592         private class WebOfTrustPropertyUpdateJob extends WebOfTrustUpdateJob {
593
594                 /** The own identity to update properties on. */
595                 protected final OwnIdentity ownIdentity;
596
597                 /** The name of the property to update. */
598                 protected final String propertyName;
599
600                 /**
601                  * Creates a new property update job.
602                  *
603                  * @param ownIdentity
604                  *            The own identity to update the property on
605                  * @param propertyName
606                  *            The name of the property to update
607                  */
608                 @SuppressWarnings("synthetic-access")
609                 public WebOfTrustPropertyUpdateJob(OwnIdentity ownIdentity, String propertyName) {
610                         this.ownIdentity = ownIdentity;
611                         this.propertyName = propertyName;
612                 }
613
614                 //
615                 // OBJECT METHODS
616                 //
617
618                 /**
619                  * {@inheritDoc}
620                  */
621                 @Override
622                 public boolean equals(Object object) {
623                         if ((object == null) || !object.getClass().equals(getClass())) {
624                                 return false;
625                         }
626                         WebOfTrustPropertyUpdateJob updateJob = (WebOfTrustPropertyUpdateJob) object;
627                         return updateJob.ownIdentity.equals(ownIdentity) && updateJob.propertyName.equals(propertyName);
628                 }
629
630                 /**
631                  * {@inheritDoc}
632                  */
633                 @Override
634                 public int hashCode() {
635                         return getClass().hashCode() ^ ownIdentity.hashCode() ^ propertyName.hashCode();
636                 }
637
638                 /**
639                  * {@inheritDoc}
640                  */
641                 @Override
642                 public String toString() {
643                         return String.format("%s[ownIdentity=%s,propertyName=%s]", getClass().getSimpleName(), ownIdentity, propertyName);
644                 }
645
646         }
647
648         /**
649          * WebOfTrust update job that sets a property on an {@link OwnIdentity}.
650          *
651          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
652          */
653         private class SetPropertyJob extends WebOfTrustPropertyUpdateJob {
654
655                 /** The value of the property to set. */
656                 private final String propertyValue;
657
658                 /**
659                  * Creates a new set-property job.
660                  *
661                  * @param ownIdentity
662                  *            The own identity to set the property on
663                  * @param propertyName
664                  *            The name of the property to set
665                  * @param propertyValue
666                  *            The value of the property to set
667                  */
668                 public SetPropertyJob(OwnIdentity ownIdentity, String propertyName, String propertyValue) {
669                         super(ownIdentity, propertyName);
670                         this.propertyValue = propertyValue;
671                 }
672
673                 /**
674                  * {@inheritDoc}
675                  */
676                 @Override
677                 @SuppressWarnings("synthetic-access")
678                 public void run() {
679                         try {
680                                 if (propertyValue == null) {
681                                         webOfTrustConnector.removeProperty(ownIdentity, propertyName);
682                                         ownIdentity.removeProperty(propertyName);
683                                 } else {
684                                         webOfTrustConnector.setProperty(ownIdentity, propertyName, propertyValue);
685                                         ownIdentity.setProperty(propertyName, propertyValue);
686                                 }
687                                 finish(true);
688                         } catch (PluginException pe1) {
689                                 logger.log(Level.WARNING, String.format("Could not set Property “%2$s” to “%3$s” on Own Identity %1$s!", ownIdentity, propertyName, propertyValue), pe1);
690                                 finish(false);
691                         }
692                 }
693
694         }
695
696 }