šŸ“„ Update year in file headers
[Sone.git] / src / main / java / net / pterodactylus / sone / core / WebOfTrustUpdaterImpl.java
1 /*
2  * Sone - WebOfTrustUpdaterImpl.java - Copyright Ā© 2013ā€“2020 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.OwnIdentity;
30 import net.pterodactylus.sone.freenet.wot.WebOfTrustConnector;
31 import net.pterodactylus.util.service.AbstractService;
32
33 import com.google.common.annotations.VisibleForTesting;
34 import com.google.inject.Inject;
35 import com.google.inject.Singleton;
36
37 /**
38  * Updates WebOfTrust identity data in a background thread because communicating
39  * with the WebOfTrust plugin can potentially last quite long.
40  */
41 @Singleton
42 public class WebOfTrustUpdaterImpl extends AbstractService implements WebOfTrustUpdater {
43
44         /** The logger. */
45         private static final Logger logger = getLogger(WebOfTrustUpdaterImpl.class.getName());
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<>();
56
57         /**
58          * Creates a new trust updater.
59          *
60          * @param webOfTrustConnector
61          *              The web of trust connector
62          */
63         @Inject
64         public WebOfTrustUpdaterImpl(WebOfTrustConnector webOfTrustConnector) {
65                 super("Trust Updater");
66                 this.webOfTrustConnector = webOfTrustConnector;
67         }
68
69         //
70         // ACTIONS
71         //
72
73         /**
74          * Adds the given context to the given own identity, waiting for completion of
75          * the operation.
76          *
77          * @param ownIdentity
78          *              The own identity to add the context to
79          * @param context
80          *              The context to add
81          * @return {@code true} if the context was added successfully, {@code false}
82          *         otherwise
83          */
84         @Override
85         public boolean addContextWait(OwnIdentity ownIdentity, String context) {
86                 AddContextJob addContextJob = new AddContextJob(ownIdentity, context);
87                 if (!updateJobs.contains(addContextJob)) {
88                         logger.log(Level.FINER, "Adding Context Job: " + addContextJob);
89                         try {
90                                 updateJobs.put(addContextJob);
91                         } catch (InterruptedException ie1) {
92                                 /* the queue is unbounded so it should never block. */
93                         }
94                         return addContextJob.waitForCompletion();
95                 } else {
96                         for (WebOfTrustUpdateJob updateJob : updateJobs) {
97                                 if (updateJob.equals(addContextJob)) {
98                                         return updateJob.waitForCompletion();
99                                 }
100                         }
101                 }
102                 return false;
103         }
104
105         /**
106          * Removes the given context from the given own identity.
107          *
108          * @param ownIdentity
109          *              The own identity to remove the context from
110          * @param context
111          *              The context to remove
112          */
113         @Override
114         public void removeContext(OwnIdentity ownIdentity, String context) {
115                 RemoveContextJob removeContextJob = new RemoveContextJob(ownIdentity, context);
116                 if (!updateJobs.contains(removeContextJob)) {
117                         logger.log(Level.FINER, "Adding Context Job: " + removeContextJob);
118                         try {
119                                 updateJobs.put(removeContextJob);
120                         } catch (InterruptedException ie1) {
121                                 /* the queue is unbounded so it should never block. */
122                         }
123                 }
124         }
125
126         /**
127          * Sets a property on the given own identity.
128          *
129          * @param ownIdentity
130          *              The own identity to set the property on
131          * @param propertyName
132          *              The name of the property to set
133          * @param propertyValue
134          *              The value of the property to set
135          */
136         @Override
137         public void setProperty(OwnIdentity ownIdentity, String propertyName, String propertyValue) {
138                 SetPropertyJob setPropertyJob = new SetPropertyJob(ownIdentity, propertyName, propertyValue);
139                 if (updateJobs.contains(setPropertyJob)) {
140                         updateJobs.remove(setPropertyJob);
141                 }
142                 logger.log(Level.FINER, "Adding Property Job: " + setPropertyJob);
143                 try {
144                         updateJobs.put(setPropertyJob);
145                 } catch (InterruptedException e) {
146                         /* the queue is unbounded so it should never block. */
147                 }
148         }
149
150         /**
151          * Removes a property from the given own identity.
152          *
153          * @param ownIdentity
154          *              The own identity to remove the property from
155          * @param propertyName
156          *              The name of the property to remove
157          */
158         @Override
159         public void removeProperty(OwnIdentity ownIdentity, String propertyName) {
160                 setProperty(ownIdentity, propertyName, null);
161         }
162
163         //
164         // SERVICE METHODS
165         //
166
167         /** {@inheritDoc} */
168         @Override
169         protected void serviceRun() {
170                 while (!shouldStop()) {
171                         try {
172                                 WebOfTrustUpdateJob updateJob = updateJobs.take();
173                                 if (shouldStop()) {
174                                         break;
175                                 }
176                                 logger.log(Level.FINE, "Running Trust Update Job: " + updateJob);
177                                 long startTime = System.currentTimeMillis();
178                                 updateJob.run();
179                                 long endTime = System.currentTimeMillis();
180                                 logger.log(Level.FINE, "Trust Update Job finished, took " + (endTime - startTime) + " ms.");
181                         } catch (InterruptedException ie1) {
182                                 /* happens, ignore, loop. */
183                         }
184                 }
185         }
186
187         /** {@inheritDoc} */
188         @Override
189         protected void serviceStop() {
190                 try {
191                         updateJobs.put(stopJob);
192                 } catch (InterruptedException ie1) {
193                         /* the queue is unbounded so it should never block. */
194                 }
195         }
196
197         /**
198          * Base class for WebOfTrust update jobs.
199          */
200         @VisibleForTesting
201         class WebOfTrustUpdateJob implements Runnable {
202
203                 /** Object for synchronization. */
204                 @SuppressWarnings("hiding")
205                 private final Object syncObject = new Object();
206
207                 /** Whether the job has finished. */
208                 private boolean finished;
209
210                 /** Whether the job was successful. */
211                 private boolean success;
212
213                 //
214                 // ACTIONS
215                 //
216
217                 /**
218                  * Performs the actual update operation.
219                  * <p/>
220                  * The implementation of this class does nothing.
221                  */
222                 @Override
223                 public void run() {
224                         /* does nothing. */
225                 }
226
227                 /**
228                  * Waits for completion of this job or stopping of the WebOfTrust updater.
229                  *
230                  * @return {@code true} if this job finished successfully, {@code false}
231                  *         otherwise
232                  * @see WebOfTrustUpdaterImpl#stop()
233                  */
234                 @SuppressWarnings("synthetic-access")
235                 public boolean waitForCompletion() {
236                         synchronized (syncObject) {
237                                 while (!finished && !shouldStop()) {
238                                         try {
239                                                 syncObject.wait();
240                                         } catch (InterruptedException ie1) {
241                                                 /* weā€™re looping, ignore. */
242                                         }
243                                 }
244                                 return success;
245                         }
246                 }
247
248                 //
249                 // PROTECTED METHODS
250                 //
251
252                 /**
253                  * Signals that this job has finished.
254                  *
255                  * @param success
256                  *              {@code true} if this job finished successfully, {@code false} otherwise
257                  */
258                 protected void finish(boolean success) {
259                         synchronized (syncObject) {
260                                 finished = true;
261                                 this.success = success;
262                                 syncObject.notifyAll();
263                         }
264                 }
265
266         }
267
268         /**
269          * Base class for context updates of an {@link OwnIdentity}.
270          */
271         @VisibleForTesting
272         class WebOfTrustContextUpdateJob extends WebOfTrustUpdateJob {
273
274                 /** The own identity whose contexts to manage. */
275                 protected final OwnIdentity ownIdentity;
276
277                 /** The context to update. */
278                 protected final String context;
279
280                 /**
281                  * Creates a new context update job.
282                  *
283                  * @param ownIdentity
284                  *              The own identity to update
285                  * @param context
286                  *              The context to update
287                  */
288                 @SuppressWarnings("synthetic-access")
289                 public WebOfTrustContextUpdateJob(OwnIdentity ownIdentity, String context) {
290                         this.ownIdentity = checkNotNull(ownIdentity, "ownIdentity must not be null");
291                         this.context = checkNotNull(context, "context must not be null");
292                 }
293
294                 //
295                 // OBJECT METHODS
296                 //
297
298                 /** {@inheritDoc} */
299                 @Override
300                 public boolean equals(Object object) {
301                         if ((object == null) || !object.getClass().equals(getClass())) {
302                                 return false;
303                         }
304                         WebOfTrustContextUpdateJob updateJob = (WebOfTrustContextUpdateJob) object;
305                         return updateJob.ownIdentity.equals(ownIdentity) && updateJob.context.equals(context);
306                 }
307
308                 /** {@inheritDoc} */
309                 @Override
310                 public int hashCode() {
311                         return getClass().hashCode() ^ ownIdentity.hashCode() ^ context.hashCode();
312                 }
313
314                 /** {@inheritDoc} */
315                 @Override
316                 public String toString() {
317                         return String.format("%s[ownIdentity=%s,context=%s]", getClass().getSimpleName(), ownIdentity, context);
318                 }
319
320         }
321
322         /**
323          * Job that adds a context to an {@link OwnIdentity}.
324          */
325         @VisibleForTesting
326         class AddContextJob extends WebOfTrustContextUpdateJob {
327
328                 /**
329                  * Creates a new add-context job.
330                  *
331                  * @param ownIdentity
332                  *              The own identity whose contexts to manage
333                  * @param context
334                  *              The context to add
335                  */
336                 public AddContextJob(OwnIdentity ownIdentity, String context) {
337                         super(ownIdentity, context);
338                 }
339
340                 /** {@inheritDoc} */
341                 @Override
342                 @SuppressWarnings("synthetic-access")
343                 public void run() {
344                         try {
345                                 webOfTrustConnector.addContext(ownIdentity, context);
346                                 ownIdentity.addContext(context);
347                                 finish(true);
348                         } catch (PluginException pe1) {
349                                 logger.log(Level.WARNING, String.format("Could not add Context ā€œ%2$sā€ to Own Identity %1$s!", ownIdentity, context), pe1);
350                                 finish(false);
351                         }
352                 }
353
354         }
355
356         /**
357          * Job that removes a context from an {@link OwnIdentity}.
358          */
359         @VisibleForTesting
360         class RemoveContextJob extends WebOfTrustContextUpdateJob {
361
362                 /**
363                  * Creates a new remove-context job.
364                  *
365                  * @param ownIdentity
366                  *              The own identity whose contexts to manage
367                  * @param context
368                  *              The context to remove
369                  */
370                 public RemoveContextJob(OwnIdentity ownIdentity, String context) {
371                         super(ownIdentity, context);
372                 }
373
374                 /** {@inheritDoc} */
375                 @Override
376                 @SuppressWarnings("synthetic-access")
377                 public void run() {
378                         try {
379                                 webOfTrustConnector.removeContext(ownIdentity, context);
380                                 ownIdentity.removeContext(context);
381                                 finish(true);
382                         } catch (PluginException pe1) {
383                                 logger.log(Level.WARNING, String.format("Could not remove Context ā€œ%2$sā€ to Own Identity %1$s!", ownIdentity, context), pe1);
384                                 finish(false);
385                         }
386                 }
387
388         }
389
390         /**
391          * WebOfTrust update job that sets a property on an {@link OwnIdentity}.
392          */
393         @VisibleForTesting
394         class SetPropertyJob extends WebOfTrustUpdateJob {
395
396                 /** The own identity to update properties on. */
397                 private final OwnIdentity ownIdentity;
398
399                 /** The name of the property to update. */
400                 private final String propertyName;
401
402                 /** The value of the property to set. */
403                 private final String propertyValue;
404
405                 /**
406                  * Creates a new set-property job.
407                  *
408                  * @param ownIdentity
409                  *              The own identity to set the property on
410                  * @param propertyName
411                  *              The name of the property to set
412                  * @param propertyValue
413                  *              The value of the property to set
414                  */
415                 public SetPropertyJob(OwnIdentity ownIdentity, String propertyName, String propertyValue) {
416                         this.ownIdentity = ownIdentity;
417                         this.propertyName = propertyName;
418                         this.propertyValue = propertyValue;
419                 }
420
421                 /** {@inheritDoc} */
422                 @Override
423                 @SuppressWarnings("synthetic-access")
424                 public void run() {
425                         try {
426                                 if (propertyValue == null) {
427                                         webOfTrustConnector.removeProperty(ownIdentity, propertyName);
428                                         ownIdentity.removeProperty(propertyName);
429                                 } else {
430                                         webOfTrustConnector.setProperty(ownIdentity, propertyName, propertyValue);
431                                         ownIdentity.setProperty(propertyName, propertyValue);
432                                 }
433                                 finish(true);
434                         } catch (PluginException pe1) {
435                                 logger.log(Level.WARNING, String.format("Could not set Property ā€œ%2$sā€ to ā€œ%3$sā€ on Own Identity %1$s!", ownIdentity, propertyName, propertyValue), pe1);
436                                 finish(false);
437                         }
438                 }
439
440                 //
441                 // OBJECT METHODS
442                 //
443
444                 /** {@inheritDoc} */
445                 @Override
446                 public boolean equals(Object object) {
447                         if ((object == null) || !object.getClass().equals(getClass())) {
448                                 return false;
449                         }
450                         SetPropertyJob updateJob = (SetPropertyJob) object;
451                         return updateJob.ownIdentity.equals(ownIdentity) && updateJob.propertyName.equals(propertyName);
452                 }
453
454                 /** {@inheritDoc} */
455                 @Override
456                 public int hashCode() {
457                         return getClass().hashCode() ^ ownIdentity.hashCode() ^ propertyName.hashCode();
458                 }
459
460                 /** {@inheritDoc} */
461                 @Override
462                 public String toString() {
463                         return String.format("%s[ownIdentity=%s,propertyName=%s]", getClass().getSimpleName(), ownIdentity, propertyName);
464                 }
465
466         }
467
468 }