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