Remove @author tags
[Sone.git] / src / main / java / net / pterodactylus / sone / core / WebOfTrustUpdaterImpl.java
1 /*
2  * Sone - WebOfTrustUpdaterImpl.java - Copyright © 2013–2016 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 @Singleton
45 public class WebOfTrustUpdaterImpl extends AbstractService implements WebOfTrustUpdater {
46
47         /** The logger. */
48         private static final Logger logger = getLogger(WebOfTrustUpdaterImpl.class.getName());
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         @VisibleForTesting
232         class WebOfTrustUpdateJob implements Runnable {
233
234                 /** Object for synchronization. */
235                 @SuppressWarnings("hiding")
236                 private final Object syncObject = new Object();
237
238                 /** Whether the job has finished. */
239                 private boolean finished;
240
241                 /** Whether the job was successful. */
242                 private boolean success;
243
244                 //
245                 // ACTIONS
246                 //
247
248                 /**
249                  * Performs the actual update operation.
250                  * <p/>
251                  * The implementation of this class does nothing.
252                  */
253                 @Override
254                 public void run() {
255                         /* does nothing. */
256                 }
257
258                 /**
259                  * Waits for completion of this job or stopping of the WebOfTrust updater.
260                  *
261                  * @return {@code true} if this job finished successfully, {@code false}
262                  *         otherwise
263                  * @see WebOfTrustUpdaterImpl#stop()
264                  */
265                 @SuppressWarnings("synthetic-access")
266                 public boolean waitForCompletion() {
267                         synchronized (syncObject) {
268                                 while (!finished && !shouldStop()) {
269                                         try {
270                                                 syncObject.wait();
271                                         } catch (InterruptedException ie1) {
272                                                 /* we’re looping, ignore. */
273                                         }
274                                 }
275                                 return success;
276                         }
277                 }
278
279                 //
280                 // PROTECTED METHODS
281                 //
282
283                 /**
284                  * Signals that this job has finished.
285                  *
286                  * @param success
287                  *              {@code true} if this job finished successfully, {@code false} otherwise
288                  */
289                 protected void finish(boolean success) {
290                         synchronized (syncObject) {
291                                 finished = true;
292                                 this.success = success;
293                                 syncObject.notifyAll();
294                         }
295                 }
296
297         }
298
299         /**
300          * Update job that sets the trust relation between two identities.
301          */
302         @VisibleForTesting
303         class SetTrustJob extends WebOfTrustUpdateJob {
304
305                 /** The identity giving the trust. */
306                 private final OwnIdentity truster;
307
308                 /** The identity receiving the trust. */
309                 private final Identity trustee;
310
311                 /** The score of the relation. */
312                 private final Integer score;
313
314                 /** The comment of the relation. */
315                 private final String comment;
316
317                 /**
318                  * Creates a new set trust job.
319                  *
320                  * @param truster
321                  *              The identity giving the trust
322                  * @param trustee
323                  *              The identity receiving the trust
324                  * @param score
325                  *              The score of the trust (from -100 to 100, may be {@code null} to remote
326                  *              the trust relation completely)
327                  * @param comment
328                  *              The comment of the trust relation
329                  */
330                 public SetTrustJob(OwnIdentity truster, Identity trustee, Integer score, String comment) {
331                         this.truster = checkNotNull(truster, "truster must not be null");
332                         this.trustee = checkNotNull(trustee, "trustee must not be null");
333                         this.score = score;
334                         this.comment = comment;
335                 }
336
337                 /** {@inheritDoc} */
338                 @Override
339                 @SuppressWarnings("synthetic-access")
340                 public void run() {
341                         try {
342                                 if (score != null) {
343                                         webOfTrustConnector.setTrust(truster, trustee, score, comment);
344                                         trustee.setTrust(truster, new Trust(score, null, 0));
345                                 } else {
346                                         webOfTrustConnector.removeTrust(truster, trustee);
347                                         trustee.removeTrust(truster);
348                                 }
349                                 finish(true);
350                         } catch (WebOfTrustException wote1) {
351                                 logger.log(Level.WARNING, "Could not set Trust value for " + truster + " -> " + trustee + " to " + score + " (" + comment + ")!", wote1);
352                                 finish(false);
353                         }
354                 }
355
356                 //
357                 // OBJECT METHODS
358                 //
359
360                 /** {@inheritDoc} */
361                 @Override
362                 public boolean equals(Object object) {
363                         if ((object == null) || !object.getClass().equals(getClass())) {
364                                 return false;
365                         }
366                         SetTrustJob updateJob = (SetTrustJob) object;
367                         return updateJob.truster.equals(truster) && updateJob.trustee.equals(trustee);
368                 }
369
370                 /** {@inheritDoc} */
371                 @Override
372                 public int hashCode() {
373                         return getClass().hashCode() ^ truster.hashCode() ^ trustee.hashCode();
374                 }
375
376                 /** {@inheritDoc} */
377                 @Override
378                 public String toString() {
379                         return String.format("%s[truster=%s,trustee=%s]", getClass().getSimpleName(), truster.getId(), trustee.getId());
380                 }
381
382         }
383
384         /**
385          * Base class for context updates of an {@link OwnIdentity}.
386          */
387         @VisibleForTesting
388         class WebOfTrustContextUpdateJob extends WebOfTrustUpdateJob {
389
390                 /** The own identity whose contexts to manage. */
391                 protected final OwnIdentity ownIdentity;
392
393                 /** The context to update. */
394                 protected final String context;
395
396                 /**
397                  * Creates a new context update job.
398                  *
399                  * @param ownIdentity
400                  *              The own identity to update
401                  * @param context
402                  *              The context to update
403                  */
404                 @SuppressWarnings("synthetic-access")
405                 public WebOfTrustContextUpdateJob(OwnIdentity ownIdentity, String context) {
406                         this.ownIdentity = checkNotNull(ownIdentity, "ownIdentity must not be null");
407                         this.context = checkNotNull(context, "context must not be null");
408                 }
409
410                 //
411                 // OBJECT METHODS
412                 //
413
414                 /** {@inheritDoc} */
415                 @Override
416                 public boolean equals(Object object) {
417                         if ((object == null) || !object.getClass().equals(getClass())) {
418                                 return false;
419                         }
420                         WebOfTrustContextUpdateJob updateJob = (WebOfTrustContextUpdateJob) object;
421                         return updateJob.ownIdentity.equals(ownIdentity) && updateJob.context.equals(context);
422                 }
423
424                 /** {@inheritDoc} */
425                 @Override
426                 public int hashCode() {
427                         return getClass().hashCode() ^ ownIdentity.hashCode() ^ context.hashCode();
428                 }
429
430                 /** {@inheritDoc} */
431                 @Override
432                 public String toString() {
433                         return String.format("%s[ownIdentity=%s,context=%s]", getClass().getSimpleName(), ownIdentity, context);
434                 }
435
436         }
437
438         /**
439          * Job that adds a context to an {@link OwnIdentity}.
440          */
441         @VisibleForTesting
442         class AddContextJob extends WebOfTrustContextUpdateJob {
443
444                 /**
445                  * Creates a new add-context job.
446                  *
447                  * @param ownIdentity
448                  *              The own identity whose contexts to manage
449                  * @param context
450                  *              The context to add
451                  */
452                 public AddContextJob(OwnIdentity ownIdentity, String context) {
453                         super(ownIdentity, context);
454                 }
455
456                 /** {@inheritDoc} */
457                 @Override
458                 @SuppressWarnings("synthetic-access")
459                 public void run() {
460                         try {
461                                 webOfTrustConnector.addContext(ownIdentity, context);
462                                 ownIdentity.addContext(context);
463                                 finish(true);
464                         } catch (PluginException pe1) {
465                                 logger.log(Level.WARNING, String.format("Could not add Context “%2$s” to Own Identity %1$s!", ownIdentity, context), pe1);
466                                 finish(false);
467                         }
468                 }
469
470         }
471
472         /**
473          * Job that removes a context from an {@link OwnIdentity}.
474          */
475         @VisibleForTesting
476         class RemoveContextJob extends WebOfTrustContextUpdateJob {
477
478                 /**
479                  * Creates a new remove-context job.
480                  *
481                  * @param ownIdentity
482                  *              The own identity whose contexts to manage
483                  * @param context
484                  *              The context to remove
485                  */
486                 public RemoveContextJob(OwnIdentity ownIdentity, String context) {
487                         super(ownIdentity, context);
488                 }
489
490                 /** {@inheritDoc} */
491                 @Override
492                 @SuppressWarnings("synthetic-access")
493                 public void run() {
494                         try {
495                                 webOfTrustConnector.removeContext(ownIdentity, context);
496                                 ownIdentity.removeContext(context);
497                                 finish(true);
498                         } catch (PluginException pe1) {
499                                 logger.log(Level.WARNING, String.format("Could not remove Context “%2$s” to Own Identity %1$s!", ownIdentity, context), pe1);
500                                 finish(false);
501                         }
502                 }
503
504         }
505
506         /**
507          * WebOfTrust update job that sets a property on an {@link OwnIdentity}.
508          */
509         @VisibleForTesting
510         class SetPropertyJob extends WebOfTrustUpdateJob {
511
512                 /** The own identity to update properties on. */
513                 private final OwnIdentity ownIdentity;
514
515                 /** The name of the property to update. */
516                 private final String propertyName;
517
518                 /** The value of the property to set. */
519                 private final String propertyValue;
520
521                 /**
522                  * Creates a new set-property job.
523                  *
524                  * @param ownIdentity
525                  *              The own identity to set the property on
526                  * @param propertyName
527                  *              The name of the property to set
528                  * @param propertyValue
529                  *              The value of the property to set
530                  */
531                 public SetPropertyJob(OwnIdentity ownIdentity, String propertyName, String propertyValue) {
532                         this.ownIdentity = ownIdentity;
533                         this.propertyName = propertyName;
534                         this.propertyValue = propertyValue;
535                 }
536
537                 /** {@inheritDoc} */
538                 @Override
539                 @SuppressWarnings("synthetic-access")
540                 public void run() {
541                         try {
542                                 if (propertyValue == null) {
543                                         webOfTrustConnector.removeProperty(ownIdentity, propertyName);
544                                         ownIdentity.removeProperty(propertyName);
545                                 } else {
546                                         webOfTrustConnector.setProperty(ownIdentity, propertyName, propertyValue);
547                                         ownIdentity.setProperty(propertyName, propertyValue);
548                                 }
549                                 finish(true);
550                         } catch (PluginException pe1) {
551                                 logger.log(Level.WARNING, String.format("Could not set Property “%2$s” to “%3$s” on Own Identity %1$s!", ownIdentity, propertyName, propertyValue), pe1);
552                                 finish(false);
553                         }
554                 }
555
556                 //
557                 // OBJECT METHODS
558                 //
559
560                 /** {@inheritDoc} */
561                 @Override
562                 public boolean equals(Object object) {
563                         if ((object == null) || !object.getClass().equals(getClass())) {
564                                 return false;
565                         }
566                         SetPropertyJob updateJob = (SetPropertyJob) object;
567                         return updateJob.ownIdentity.equals(ownIdentity) && updateJob.propertyName.equals(propertyName);
568                 }
569
570                 /** {@inheritDoc} */
571                 @Override
572                 public int hashCode() {
573                         return getClass().hashCode() ^ ownIdentity.hashCode() ^ propertyName.hashCode();
574                 }
575
576                 /** {@inheritDoc} */
577                 @Override
578                 public String toString() {
579                         return String.format("%s[ownIdentity=%s,propertyName=%s]", getClass().getSimpleName(), ownIdentity, propertyName);
580                 }
581
582         }
583
584 }