05deb299c36d0a0e2ac8056a46d41143d65c42c8
[Sone.git] / src / main / java / net / pterodactylus / sone / core / WebOfTrustUpdaterImpl.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.Identity;
29 import net.pterodactylus.sone.freenet.wot.OwnIdentity;
30 import net.pterodactylus.sone.freenet.wot.Trust;
31 import net.pterodactylus.sone.freenet.wot.WebOfTrustConnector;
32 import net.pterodactylus.sone.freenet.wot.WebOfTrustException;
33 import net.pterodactylus.util.logging.Logging;
34 import net.pterodactylus.util.service.AbstractService;
35
36 import com.google.common.annotations.VisibleForTesting;
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 WebOfTrustUpdaterImpl extends AbstractService implements WebOfTrustUpdater {
46
47         /** The logger. */
48         private static final Logger logger = Logging.getLogger(WebOfTrustUpdaterImpl.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 WebOfTrustUpdaterImpl(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 method
78          * will return immediately and perform a trust update in the background.
79          *
80          * @param truster
81          *              The identity giving the trust
82          * @param trustee
83          *              The identity receiving the trust
84          * @param score
85          *              The new level of trust (from -100 to 100, may be {@code null} to remove
86          *              the trust completely)
87          * @param comment
88          *              The comment of the trust relation
89          */
90         @Override
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, waiting for completion of
106          * the operation.
107          *
108          * @param ownIdentity
109          *              The own identity to add the context to
110          * @param context
111          *              The context to add
112          * @return {@code true} if the context was added successfully, {@code false}
113          *         otherwise
114          */
115         @Override
116         public boolean addContextWait(OwnIdentity ownIdentity, String context) {
117                 AddContextJob addContextJob = new AddContextJob(ownIdentity, context);
118                 if (!updateJobs.contains(addContextJob)) {
119                         logger.log(Level.FINER, "Adding Context Job: " + addContextJob);
120                         try {
121                                 updateJobs.put(addContextJob);
122                         } catch (InterruptedException ie1) {
123                                 /* the queue is unbounded so it should never block. */
124                         }
125                         return addContextJob.waitForCompletion();
126                 } else {
127                         for (WebOfTrustUpdateJob updateJob : updateJobs) {
128                                 if (updateJob.equals(addContextJob)) {
129                                         return updateJob.waitForCompletion();
130                                 }
131                         }
132                 }
133                 return false;
134         }
135
136         /**
137          * Removes the given context from the given own identity.
138          *
139          * @param ownIdentity
140          *              The own identity to remove the context from
141          * @param context
142          *              The context to remove
143          */
144         @Override
145         public void removeContext(OwnIdentity ownIdentity, String context) {
146                 RemoveContextJob removeContextJob = new RemoveContextJob(ownIdentity, context);
147                 if (!updateJobs.contains(removeContextJob)) {
148                         logger.log(Level.FINER, "Adding Context Job: " + removeContextJob);
149                         try {
150                                 updateJobs.put(removeContextJob);
151                         } catch (InterruptedException ie1) {
152                                 /* the queue is unbounded so it should never block. */
153                         }
154                 }
155         }
156
157         /**
158          * Sets a property on the given own identity.
159          *
160          * @param ownIdentity
161          *              The own identity to set the property on
162          * @param propertyName
163          *              The name of the property to set
164          * @param propertyValue
165          *              The value of the property to set
166          */
167         @Override
168         public void setProperty(OwnIdentity ownIdentity, String propertyName, String propertyValue) {
169                 SetPropertyJob setPropertyJob = new SetPropertyJob(ownIdentity, propertyName, propertyValue);
170                 if (updateJobs.contains(setPropertyJob)) {
171                         updateJobs.remove(setPropertyJob);
172                 }
173                 logger.log(Level.FINER, "Adding Property Job: " + setPropertyJob);
174                 try {
175                         updateJobs.put(setPropertyJob);
176                 } catch (InterruptedException e) {
177                         /* the queue is unbounded so it should never block. */
178                 }
179         }
180
181         /**
182          * Removes a property from the given own identity.
183          *
184          * @param ownIdentity
185          *              The own identity to remove the property from
186          * @param propertyName
187          *              The name of the property to remove
188          */
189         @Override
190         public void removeProperty(OwnIdentity ownIdentity, String propertyName) {
191                 setProperty(ownIdentity, propertyName, null);
192         }
193
194         //
195         // SERVICE METHODS
196         //
197
198         /** {@inheritDoc} */
199         @Override
200         protected void serviceRun() {
201                 while (!shouldStop()) {
202                         try {
203                                 WebOfTrustUpdateJob updateJob = updateJobs.take();
204                                 if (shouldStop()) {
205                                         break;
206                                 }
207                                 logger.log(Level.FINE, "Running Trust Update Job: " + updateJob);
208                                 long startTime = System.currentTimeMillis();
209                                 updateJob.run();
210                                 long endTime = System.currentTimeMillis();
211                                 logger.log(Level.FINE, "Trust Update Job finished, took " + (endTime - startTime) + " ms.");
212                         } catch (InterruptedException ie1) {
213                                 /* happens, ignore, loop. */
214                         }
215                 }
216         }
217
218         /** {@inheritDoc} */
219         @Override
220         protected void serviceStop() {
221                 try {
222                         updateJobs.put(stopJob);
223                 } catch (InterruptedException ie1) {
224                         /* the queue is unbounded so it should never block. */
225                 }
226         }
227
228         /**
229          * Base class for WebOfTrust update jobs.
230          *
231          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
232          */
233         @VisibleForTesting
234         class WebOfTrustUpdateJob implements Runnable {
235
236                 /** Object for synchronization. */
237                 @SuppressWarnings("hiding")
238                 private final Object syncObject = new Object();
239
240                 /** Whether the job has finished. */
241                 private boolean finished;
242
243                 /** Whether the job was successful. */
244                 private boolean success;
245
246                 //
247                 // ACTIONS
248                 //
249
250                 /**
251                  * Performs the actual update operation.
252                  * <p/>
253                  * The implementation of this class does nothing.
254                  */
255                 @Override
256                 public void run() {
257                         /* does nothing. */
258                 }
259
260                 /**
261                  * Waits for completion of this job or stopping of the WebOfTrust updater.
262                  *
263                  * @return {@code true} if this job finished successfully, {@code false}
264                  *         otherwise
265                  * @see WebOfTrustUpdaterImpl#stop()
266                  */
267                 @SuppressWarnings("synthetic-access")
268                 public boolean waitForCompletion() {
269                         synchronized (syncObject) {
270                                 while (!finished && !shouldStop()) {
271                                         try {
272                                                 syncObject.wait();
273                                         } catch (InterruptedException ie1) {
274                                                 /* we’re looping, ignore. */
275                                         }
276                                 }
277                                 return success;
278                         }
279                 }
280
281                 //
282                 // PROTECTED METHODS
283                 //
284
285                 /**
286                  * Signals that this job has finished.
287                  *
288                  * @param success
289                  *              {@code true} if this job finished successfully, {@code false} otherwise
290                  */
291                 protected void finish(boolean success) {
292                         synchronized (syncObject) {
293                                 finished = true;
294                                 this.success = success;
295                                 syncObject.notifyAll();
296                         }
297                 }
298
299         }
300
301         /**
302          * Update job that sets the trust relation between two identities.
303          *
304          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
305          */
306         @VisibleForTesting
307         class SetTrustJob extends WebOfTrustUpdateJob {
308
309                 /** The identity giving the trust. */
310                 private final OwnIdentity truster;
311
312                 /** The identity receiving the trust. */
313                 private final Identity trustee;
314
315                 /** The score of the relation. */
316                 private final Integer score;
317
318                 /** The comment of the relation. */
319                 private final String comment;
320
321                 /**
322                  * Creates a new set trust job.
323                  *
324                  * @param truster
325                  *              The identity giving the trust
326                  * @param trustee
327                  *              The identity receiving the trust
328                  * @param score
329                  *              The score of the trust (from -100 to 100, may be {@code null} to remote
330                  *              the trust relation completely)
331                  * @param comment
332                  *              The comment of the trust relation
333                  */
334                 public SetTrustJob(OwnIdentity truster, Identity trustee, Integer score, String comment) {
335                         this.truster = checkNotNull(truster, "truster must not be null");
336                         this.trustee = checkNotNull(trustee, "trustee must not be null");
337                         this.score = score;
338                         this.comment = comment;
339                 }
340
341                 /** {@inheritDoc} */
342                 @Override
343                 @SuppressWarnings("synthetic-access")
344                 public void run() {
345                         try {
346                                 if (score != null) {
347                                         webOfTrustConnector.setTrust(truster, trustee, score, comment);
348                                         trustee.setTrust(truster, new Trust(score, null, 0));
349                                 } else {
350                                         webOfTrustConnector.removeTrust(truster, trustee);
351                                         trustee.removeTrust(truster);
352                                 }
353                                 finish(true);
354                         } catch (WebOfTrustException wote1) {
355                                 logger.log(Level.WARNING, "Could not set Trust value for " + truster + " -> " + trustee + " to " + score + " (" + comment + ")!", wote1);
356                                 finish(false);
357                         }
358                 }
359
360                 //
361                 // OBJECT METHODS
362                 //
363
364                 /** {@inheritDoc} */
365                 @Override
366                 public boolean equals(Object object) {
367                         if ((object == null) || !object.getClass().equals(getClass())) {
368                                 return false;
369                         }
370                         SetTrustJob updateJob = (SetTrustJob) object;
371                         return updateJob.truster.equals(truster) && updateJob.trustee.equals(trustee);
372                 }
373
374                 /** {@inheritDoc} */
375                 @Override
376                 public int hashCode() {
377                         return getClass().hashCode() ^ truster.hashCode() ^ trustee.hashCode();
378                 }
379
380                 /** {@inheritDoc} */
381                 @Override
382                 public String toString() {
383                         return String.format("%s[truster=%s,trustee=%s]", getClass().getSimpleName(), truster.getId(), trustee.getId());
384                 }
385
386         }
387
388         /**
389          * Base class for context updates of an {@link OwnIdentity}.
390          *
391          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
392          */
393         @VisibleForTesting
394         class WebOfTrustContextUpdateJob extends WebOfTrustUpdateJob {
395
396                 /** The own identity whose contexts to manage. */
397                 protected final OwnIdentity ownIdentity;
398
399                 /** The context to update. */
400                 protected final String context;
401
402                 /**
403                  * Creates a new context update job.
404                  *
405                  * @param ownIdentity
406                  *              The own identity to update
407                  * @param context
408                  *              The context to update
409                  */
410                 @SuppressWarnings("synthetic-access")
411                 public WebOfTrustContextUpdateJob(OwnIdentity ownIdentity, String context) {
412                         this.ownIdentity = checkNotNull(ownIdentity, "ownIdentity must not be null");
413                         this.context = checkNotNull(context, "context must not be null");
414                 }
415
416                 //
417                 // OBJECT METHODS
418                 //
419
420                 /** {@inheritDoc} */
421                 @Override
422                 public boolean equals(Object object) {
423                         if ((object == null) || !object.getClass().equals(getClass())) {
424                                 return false;
425                         }
426                         WebOfTrustContextUpdateJob updateJob = (WebOfTrustContextUpdateJob) object;
427                         return updateJob.ownIdentity.equals(ownIdentity) && updateJob.context.equals(context);
428                 }
429
430                 /** {@inheritDoc} */
431                 @Override
432                 public int hashCode() {
433                         return getClass().hashCode() ^ ownIdentity.hashCode() ^ context.hashCode();
434                 }
435
436                 /** {@inheritDoc} */
437                 @Override
438                 public String toString() {
439                         return String.format("%s[ownIdentity=%s,context=%s]", getClass().getSimpleName(), ownIdentity, context);
440                 }
441
442         }
443
444         /**
445          * Job that adds a context to an {@link OwnIdentity}.
446          *
447          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
448          */
449         @VisibleForTesting
450         class AddContextJob extends WebOfTrustContextUpdateJob {
451
452                 /**
453                  * Creates a new add-context job.
454                  *
455                  * @param ownIdentity
456                  *              The own identity whose contexts to manage
457                  * @param context
458                  *              The context to add
459                  */
460                 public AddContextJob(OwnIdentity ownIdentity, String context) {
461                         super(ownIdentity, context);
462                 }
463
464                 /** {@inheritDoc} */
465                 @Override
466                 @SuppressWarnings("synthetic-access")
467                 public void run() {
468                         try {
469                                 webOfTrustConnector.addContext(ownIdentity, context);
470                                 ownIdentity.addContext(context);
471                                 finish(true);
472                         } catch (PluginException pe1) {
473                                 logger.log(Level.WARNING, String.format("Could not add Context “%2$s” to Own Identity %1$s!", ownIdentity, context), pe1);
474                                 finish(false);
475                         }
476                 }
477
478         }
479
480         /**
481          * Job that removes a context from an {@link OwnIdentity}.
482          *
483          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
484          */
485         @VisibleForTesting
486         class RemoveContextJob extends WebOfTrustContextUpdateJob {
487
488                 /**
489                  * Creates a new remove-context job.
490                  *
491                  * @param ownIdentity
492                  *              The own identity whose contexts to manage
493                  * @param context
494                  *              The context to remove
495                  */
496                 public RemoveContextJob(OwnIdentity ownIdentity, String context) {
497                         super(ownIdentity, context);
498                 }
499
500                 /** {@inheritDoc} */
501                 @Override
502                 @SuppressWarnings("synthetic-access")
503                 public void run() {
504                         try {
505                                 webOfTrustConnector.removeContext(ownIdentity, context);
506                                 ownIdentity.removeContext(context);
507                                 finish(true);
508                         } catch (PluginException pe1) {
509                                 logger.log(Level.WARNING, String.format("Could not remove Context “%2$s” to Own Identity %1$s!", ownIdentity, context), pe1);
510                                 finish(false);
511                         }
512                 }
513
514         }
515
516         /**
517          * WebOfTrust update job that sets a property on an {@link OwnIdentity}.
518          *
519          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
520          */
521         @VisibleForTesting
522         class SetPropertyJob extends WebOfTrustUpdateJob {
523
524                 /** The own identity to update properties on. */
525                 private final OwnIdentity ownIdentity;
526
527                 /** The name of the property to update. */
528                 private final String propertyName;
529
530                 /** The value of the property to set. */
531                 private final String propertyValue;
532
533                 /**
534                  * Creates a new set-property job.
535                  *
536                  * @param ownIdentity
537                  *              The own identity to set the property on
538                  * @param propertyName
539                  *              The name of the property to set
540                  * @param propertyValue
541                  *              The value of the property to set
542                  */
543                 public SetPropertyJob(OwnIdentity ownIdentity, String propertyName, String propertyValue) {
544                         this.ownIdentity = ownIdentity;
545                         this.propertyName = propertyName;
546                         this.propertyValue = propertyValue;
547                 }
548
549                 /** {@inheritDoc} */
550                 @Override
551                 @SuppressWarnings("synthetic-access")
552                 public void run() {
553                         try {
554                                 if (propertyValue == null) {
555                                         webOfTrustConnector.removeProperty(ownIdentity, propertyName);
556                                         ownIdentity.removeProperty(propertyName);
557                                 } else {
558                                         webOfTrustConnector.setProperty(ownIdentity, propertyName, propertyValue);
559                                         ownIdentity.setProperty(propertyName, propertyValue);
560                                 }
561                                 finish(true);
562                         } catch (PluginException pe1) {
563                                 logger.log(Level.WARNING, String.format("Could not set Property “%2$s” to “%3$s” on Own Identity %1$s!", ownIdentity, propertyName, propertyValue), pe1);
564                                 finish(false);
565                         }
566                 }
567
568                 //
569                 // OBJECT METHODS
570                 //
571
572                 /** {@inheritDoc} */
573                 @Override
574                 public boolean equals(Object object) {
575                         if ((object == null) || !object.getClass().equals(getClass())) {
576                                 return false;
577                         }
578                         SetPropertyJob updateJob = (SetPropertyJob) object;
579                         return updateJob.ownIdentity.equals(ownIdentity) && updateJob.propertyName.equals(propertyName);
580                 }
581
582                 /** {@inheritDoc} */
583                 @Override
584                 public int hashCode() {
585                         return getClass().hashCode() ^ ownIdentity.hashCode() ^ propertyName.hashCode();
586                 }
587
588                 /** {@inheritDoc} */
589                 @Override
590                 public String toString() {
591                         return String.format("%s[ownIdentity=%s,propertyName=%s]", getClass().getSimpleName(), ownIdentity, propertyName);
592                 }
593
594         }
595
596 }