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