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