Add test for “set property” job.
[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.common.annotations.VisibleForTesting;
38 import com.google.inject.Inject;
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 public class WebOfTrustUpdater extends AbstractService {
47
48         /** The logger. */
49         private static final Logger logger = Logging.getLogger(WebOfTrustUpdater.class);
50
51         /** Stop job. */
52         @SuppressWarnings("synthetic-access")
53         private final WebOfTrustUpdateJob stopJob = new WebOfTrustUpdateJob();
54
55         /** The web of trust connector. */
56         private final WebOfTrustConnector webOfTrustConnector;
57
58         /** The queue for jobs. */
59         private final BlockingQueue<WebOfTrustUpdateJob> updateJobs = new LinkedBlockingQueue<WebOfTrustUpdateJob>();
60
61         /**
62          * Creates a new trust updater.
63          *
64          * @param webOfTrustConnector
65          *              The web of trust connector
66          */
67         @Inject
68         public WebOfTrustUpdater(WebOfTrustConnector webOfTrustConnector) {
69                 super("Trust Updater");
70                 this.webOfTrustConnector = webOfTrustConnector;
71         }
72
73         //
74         // ACTIONS
75         //
76
77         /**
78          * Updates the trust relation between the truster and the trustee. This method
79          * will return immediately and perform a trust update in the 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} to remove
87          *              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 of
118          * 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 of
133          * 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, {@code false} to return
141          *              immediately
142          * @return {@code true} if the context was added successfully, {@code false} if
143          *         the context was not added successfully, or if the job should not
144          *         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         /** {@inheritDoc} */
228         @Override
229         protected void serviceRun() {
230                 while (!shouldStop()) {
231                         try {
232                                 WebOfTrustUpdateJob updateJob = updateJobs.take();
233                                 if (shouldStop() || (updateJob == stopJob)) {
234                                         break;
235                                 }
236                                 logger.log(Level.FINE, "Running Trust Update Job: " + updateJob);
237                                 long startTime = System.currentTimeMillis();
238                                 updateJob.run();
239                                 long endTime = System.currentTimeMillis();
240                                 logger.log(Level.FINE, "Trust Update Job finished, took " + (endTime - startTime) + " ms.");
241                         } catch (InterruptedException ie1) {
242                                 /* happens, ignore, loop. */
243                         }
244                 }
245         }
246
247         /** {@inheritDoc} */
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         @VisibleForTesting
263         class WebOfTrustUpdateJob implements Runnable {
264
265                 /** Object for synchronization. */
266                 @SuppressWarnings("hiding")
267                 private final Object syncObject = new Object();
268
269                 /** Whether the job has finished. */
270                 private boolean finished;
271
272                 /** Whether the job was successful. */
273                 private boolean success;
274
275                 //
276                 // ACTIONS
277                 //
278
279                 /**
280                  * Performs the actual update operation.
281                  * <p/>
282                  * The implementation of this class does nothing.
283                  */
284                 @Override
285                 public void run() {
286                         /* does nothing. */
287                 }
288
289                 /**
290                  * Waits for completion of this job or stopping of the WebOfTrust updater.
291                  *
292                  * @return {@code true} if this job finished successfully, {@code false}
293                  *         otherwise
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, {@code false} otherwise
319                  */
320                 protected void finish(boolean success) {
321                         synchronized (syncObject) {
322                                 finished = true;
323                                 this.success = success;
324                                 syncObject.notifyAll();
325                         }
326                 }
327
328         }
329
330         /**
331          * Update job that sets the trust relation between two identities.
332          *
333          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
334          */
335         private class SetTrustJob extends WebOfTrustUpdateJob {
336
337                 /** The identity giving the trust. */
338                 private final OwnIdentity truster;
339
340                 /** The identity receiving the trust. */
341                 private final Identity trustee;
342
343                 /** The score of the relation. */
344                 private final Integer score;
345
346                 /** The comment of the relation. */
347                 private final String comment;
348
349                 /**
350                  * Creates a new set trust job.
351                  *
352                  * @param truster
353                  *              The identity giving the trust
354                  * @param trustee
355                  *              The identity receiving the trust
356                  * @param score
357                  *              The score of the trust (from -100 to 100, may be {@code null} to remote
358                  *              the trust relation completely)
359                  * @param comment
360                  *              The comment of the trust relation
361                  */
362                 public SetTrustJob(OwnIdentity truster, Identity trustee, Integer score, String comment) {
363                         this.truster = truster;
364                         this.trustee = trustee;
365                         this.score = score;
366                         this.comment = comment;
367                 }
368
369                 /** {@inheritDoc} */
370                 @Override
371                 @SuppressWarnings("synthetic-access")
372                 public void run() {
373                         try {
374                                 if (score != null) {
375                                         if (trustee instanceof DefaultIdentity) {
376                                                 ((DefaultIdentity) trustee).setTrust(truster, new Trust(score, null, 0));
377                                         }
378                                         webOfTrustConnector.setTrust(truster, trustee, score, comment);
379                                 } else {
380                                         if (trustee instanceof DefaultIdentity) {
381                                                 ((DefaultIdentity) trustee).setTrust(truster, null);
382                                         }
383                                         webOfTrustConnector.removeTrust(truster, trustee);
384                                 }
385                                 finish(true);
386                         } catch (WebOfTrustException wote1) {
387                                 logger.log(Level.WARNING, "Could not set Trust value for " + truster + " -> " + trustee + " to " + score + " (" + comment + ")!", wote1);
388                                 finish(false);
389                         }
390                 }
391
392                 //
393                 // OBJECT METHODS
394                 //
395
396                 /** {@inheritDoc} */
397                 @Override
398                 public boolean equals(Object object) {
399                         if ((object == null) || !object.getClass().equals(getClass())) {
400                                 return false;
401                         }
402                         SetTrustJob updateJob = (SetTrustJob) object;
403                         return ((truster == null) ? (updateJob.truster == null) : updateJob.truster.equals(truster)) && ((trustee == null) ? (updateJob.trustee == null) : updateJob.trustee.equals(trustee));
404                 }
405
406                 /** {@inheritDoc} */
407                 @Override
408                 public int hashCode() {
409                         return getClass().hashCode() ^ ((truster == null) ? 0 : truster.hashCode()) ^ ((trustee == null) ? 0 : trustee.hashCode());
410                 }
411
412                 /** {@inheritDoc} */
413                 @Override
414                 public String toString() {
415                         return String.format("%s[truster=%s,trustee=%s]", getClass().getSimpleName(), (truster == null) ? null : truster.getId(), (trustee == null) ? null : trustee.getId());
416                 }
417
418         }
419
420         /**
421          * Base class for context updates of an {@link OwnIdentity}.
422          *
423          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
424          */
425         @VisibleForTesting
426         class WebOfTrustContextUpdateJob extends WebOfTrustUpdateJob {
427
428                 /** The own identity whose contexts to manage. */
429                 protected final OwnIdentity ownIdentity;
430
431                 /** The context to update. */
432                 protected final String context;
433
434                 /**
435                  * Creates a new context update job.
436                  *
437                  * @param ownIdentity
438                  *              The own identity to update
439                  * @param context
440                  *              The context to update
441                  */
442                 @SuppressWarnings("synthetic-access")
443                 public WebOfTrustContextUpdateJob(OwnIdentity ownIdentity, String context) {
444                         this.ownIdentity = checkNotNull(ownIdentity, "ownIdentity must not be null");
445                         this.context = checkNotNull(context, "context must not be null");
446                 }
447
448                 //
449                 // OBJECT METHODS
450                 //
451
452                 /** {@inheritDoc} */
453                 @Override
454                 public boolean equals(Object object) {
455                         if ((object == null) || !object.getClass().equals(getClass())) {
456                                 return false;
457                         }
458                         WebOfTrustContextUpdateJob updateJob = (WebOfTrustContextUpdateJob) object;
459                         return updateJob.ownIdentity.equals(ownIdentity) && updateJob.context.equals(context);
460                 }
461
462                 /** {@inheritDoc} */
463                 @Override
464                 public int hashCode() {
465                         return getClass().hashCode() ^ ownIdentity.hashCode() ^ context.hashCode();
466                 }
467
468                 /** {@inheritDoc} */
469                 @Override
470                 public String toString() {
471                         return String.format("%s[ownIdentity=%s,context=%s]", getClass().getSimpleName(), ownIdentity, context);
472                 }
473
474         }
475
476         /**
477          * Job that adds a context to an {@link OwnIdentity}.
478          *
479          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
480          */
481         @VisibleForTesting
482         class AddContextJob extends WebOfTrustContextUpdateJob {
483
484                 /**
485                  * Creates a new add-context job.
486                  *
487                  * @param ownIdentity
488                  *              The own identity whose contexts to manage
489                  * @param context
490                  *              The context to add
491                  */
492                 public AddContextJob(OwnIdentity ownIdentity, String context) {
493                         super(ownIdentity, context);
494                 }
495
496                 /** {@inheritDoc} */
497                 @Override
498                 @SuppressWarnings("synthetic-access")
499                 public void run() {
500                         try {
501                                 webOfTrustConnector.addContext(ownIdentity, context);
502                                 ownIdentity.addContext(context);
503                                 finish(true);
504                         } catch (PluginException pe1) {
505                                 logger.log(Level.WARNING, String.format("Could not add Context “%2$s” to Own Identity %1$s!", ownIdentity, context), pe1);
506                                 finish(false);
507                         }
508                 }
509
510         }
511
512         /**
513          * Job that removes a context from an {@link OwnIdentity}.
514          *
515          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
516          */
517         @VisibleForTesting
518         class RemoveContextJob extends WebOfTrustContextUpdateJob {
519
520                 /**
521                  * Creates a new remove-context job.
522                  *
523                  * @param ownIdentity
524                  *              The own identity whose contexts to manage
525                  * @param context
526                  *              The context to remove
527                  */
528                 public RemoveContextJob(OwnIdentity ownIdentity, String context) {
529                         super(ownIdentity, context);
530                 }
531
532                 /** {@inheritDoc} */
533                 @Override
534                 @SuppressWarnings("synthetic-access")
535                 public void run() {
536                         try {
537                                 webOfTrustConnector.removeContext(ownIdentity, context);
538                                 ownIdentity.removeContext(context);
539                                 finish(true);
540                         } catch (PluginException pe1) {
541                                 logger.log(Level.WARNING, String.format("Could not remove Context “%2$s” to Own Identity %1$s!", ownIdentity, context), pe1);
542                                 finish(false);
543                         }
544                 }
545
546         }
547
548         /**
549          * WebOfTrust update job that sets a property on an {@link OwnIdentity}.
550          *
551          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
552          */
553         @VisibleForTesting
554         class SetPropertyJob extends WebOfTrustUpdateJob {
555
556                 /** The own identity to update properties on. */
557                 private final OwnIdentity ownIdentity;
558
559                 /** The name of the property to update. */
560                 private final String propertyName;
561
562                 /** The value of the property to set. */
563                 private final String propertyValue;
564
565                 /**
566                  * Creates a new set-property job.
567                  *
568                  * @param ownIdentity
569                  *              The own identity to set the property on
570                  * @param propertyName
571                  *              The name of the property to set
572                  * @param propertyValue
573                  *              The value of the property to set
574                  */
575                 public SetPropertyJob(OwnIdentity ownIdentity, String propertyName, String propertyValue) {
576                         this.ownIdentity = ownIdentity;
577                         this.propertyName = propertyName;
578                         this.propertyValue = propertyValue;
579                 }
580
581                 /** {@inheritDoc} */
582                 @Override
583                 @SuppressWarnings("synthetic-access")
584                 public void run() {
585                         try {
586                                 if (propertyValue == null) {
587                                         webOfTrustConnector.removeProperty(ownIdentity, propertyName);
588                                         ownIdentity.removeProperty(propertyName);
589                                 } else {
590                                         webOfTrustConnector.setProperty(ownIdentity, propertyName, propertyValue);
591                                         ownIdentity.setProperty(propertyName, propertyValue);
592                                 }
593                                 finish(true);
594                         } catch (PluginException pe1) {
595                                 logger.log(Level.WARNING, String.format("Could not set Property “%2$s” to “%3$s” on Own Identity %1$s!", ownIdentity, propertyName, propertyValue), pe1);
596                                 finish(false);
597                         }
598                 }
599
600                 //
601                 // OBJECT METHODS
602                 //
603
604                 /** {@inheritDoc} */
605                 @Override
606                 public boolean equals(Object object) {
607                         if ((object == null) || !object.getClass().equals(getClass())) {
608                                 return false;
609                         }
610                         SetPropertyJob updateJob = (SetPropertyJob) object;
611                         return updateJob.ownIdentity.equals(ownIdentity) && updateJob.propertyName.equals(propertyName);
612                 }
613
614                 /** {@inheritDoc} */
615                 @Override
616                 public int hashCode() {
617                         return getClass().hashCode() ^ ownIdentity.hashCode() ^ propertyName.hashCode();
618                 }
619
620                 /** {@inheritDoc} */
621                 @Override
622                 public String toString() {
623                         return String.format("%s[ownIdentity=%s,propertyName=%s]", getClass().getSimpleName(), ownIdentity, propertyName);
624                 }
625
626         }
627
628 }