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