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