Add method to remove a Sone from the database.
[Sone.git] / src / main / java / net / pterodactylus / sone / database / memory / MemoryDatabase.java
1 /*
2  * Sone - MemoryDatabase.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.database.memory;
19
20 import static com.google.common.base.Optional.fromNullable;
21 import static com.google.common.base.Preconditions.checkNotNull;
22 import static com.google.common.base.Predicates.not;
23 import static com.google.common.collect.FluentIterable.from;
24 import static java.util.Collections.unmodifiableCollection;
25 import static net.pterodactylus.sone.data.Reply.TIME_COMPARATOR;
26 import static net.pterodactylus.sone.data.Sone.LOCAL_SONE_FILTER;
27 import static net.pterodactylus.sone.data.Sone.toAllAlbums;
28 import static net.pterodactylus.sone.data.Sone.toAllImages;
29
30 import java.util.Collection;
31 import java.util.Comparator;
32 import java.util.HashMap;
33 import java.util.HashSet;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Set;
37 import java.util.concurrent.locks.ReadWriteLock;
38 import java.util.concurrent.locks.ReentrantReadWriteLock;
39
40 import net.pterodactylus.sone.data.Album;
41 import net.pterodactylus.sone.data.Image;
42 import net.pterodactylus.sone.data.Post;
43 import net.pterodactylus.sone.data.PostReply;
44 import net.pterodactylus.sone.data.Sone;
45 import net.pterodactylus.sone.data.impl.AlbumBuilderImpl;
46 import net.pterodactylus.sone.data.impl.ImageBuilderImpl;
47 import net.pterodactylus.sone.database.AlbumBuilder;
48 import net.pterodactylus.sone.database.Database;
49 import net.pterodactylus.sone.database.DatabaseException;
50 import net.pterodactylus.sone.database.ImageBuilder;
51 import net.pterodactylus.sone.database.PostBuilder;
52 import net.pterodactylus.sone.database.PostDatabase;
53 import net.pterodactylus.sone.database.PostReplyBuilder;
54 import net.pterodactylus.sone.database.SoneBuilder;
55 import net.pterodactylus.sone.database.SoneProvider;
56 import net.pterodactylus.util.config.Configuration;
57 import net.pterodactylus.util.config.ConfigurationException;
58
59 import com.google.common.base.Optional;
60 import com.google.common.base.Predicate;
61 import com.google.common.collect.HashMultimap;
62 import com.google.common.collect.Multimap;
63 import com.google.common.collect.SortedSetMultimap;
64 import com.google.common.collect.TreeMultimap;
65 import com.google.common.util.concurrent.AbstractService;
66 import com.google.inject.Inject;
67 import com.google.inject.Singleton;
68
69 /**
70  * Memory-based {@link PostDatabase} implementation.
71  *
72  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
73  */
74 @Singleton
75 public class MemoryDatabase extends AbstractService implements Database {
76
77         /** The lock. */
78         private final ReadWriteLock lock = new ReentrantReadWriteLock();
79
80         /** The Sone provider. */
81         private final SoneProvider soneProvider;
82
83         /** The configuration. */
84         private final Configuration configuration;
85
86         private final Map<String, Sone> allSones = new HashMap<String, Sone>();
87
88         /** All posts by their ID. */
89         private final Map<String, Post> allPosts = new HashMap<String, Post>();
90
91         /** All posts by their Sones. */
92         private final Multimap<String, Post> sonePosts = HashMultimap.create();
93
94         /** Whether posts are known. */
95         private final Set<String> knownPosts = new HashSet<String>();
96
97         /** All post replies by their ID. */
98         private final Map<String, PostReply> allPostReplies = new HashMap<String, PostReply>();
99
100         /** Replies sorted by Sone. */
101         private final SortedSetMultimap<String, PostReply> sonePostReplies = TreeMultimap.create(new Comparator<String>() {
102
103                 @Override
104                 public int compare(String leftString, String rightString) {
105                         return leftString.compareTo(rightString);
106                 }
107         }, TIME_COMPARATOR);
108
109         /** Whether post replies are known. */
110         private final Set<String> knownPostReplies = new HashSet<String>();
111
112         private final Map<String, Album> allAlbums = new HashMap<String, Album>();
113         private final Multimap<String, Album> soneAlbums = HashMultimap.create();
114
115         private final Map<String, Image> allImages = new HashMap<String, Image>();
116         private final Multimap<String, Image> soneImages = HashMultimap.create();
117
118         private final MemoryBookmarkDatabase memoryBookmarkDatabase =
119                         new MemoryBookmarkDatabase(this);
120
121         /**
122          * Creates a new memory database.
123          *
124          * @param soneProvider
125          *              The Sone provider
126          * @param configuration
127          *              The configuration for loading and saving elements
128          */
129         @Inject
130         public MemoryDatabase(SoneProvider soneProvider, Configuration configuration) {
131                 this.soneProvider = soneProvider;
132                 this.configuration = configuration;
133         }
134
135         //
136         // DATABASE METHODS
137         //
138
139         /**
140          * Saves the database.
141          *
142          * @throws DatabaseException
143          *              if an error occurs while saving
144          */
145         @Override
146         public void save() throws DatabaseException {
147                 saveKnownPosts();
148                 saveKnownPostReplies();
149         }
150
151         //
152         // SERVICE METHODS
153         //
154
155         /** {@inheritDocs} */
156         @Override
157         protected void doStart() {
158                 loadKnownPosts();
159                 loadKnownPostReplies();
160                 notifyStarted();
161         }
162
163         /** {@inheritDocs} */
164         @Override
165         protected void doStop() {
166                 try {
167                         save();
168                         notifyStopped();
169                 } catch (DatabaseException de1) {
170                         notifyFailed(de1);
171                 }
172         }
173
174         @Override
175         public SoneBuilder newSoneBuilder() {
176                 return new MemorySoneBuilder();
177         }
178
179         @Override
180         public void storeSone(Sone sone) {
181                 lock.writeLock().lock();
182                 try {
183                         removeSone(sone);
184
185                         allSones.put(sone.getId(), sone);
186                         sonePosts.putAll(sone.getId(), sone.getPosts());
187                         for (Post post : sone.getPosts()) {
188                                 allPosts.put(post.getId(), post);
189                         }
190                         sonePostReplies.putAll(sone.getId(), sone.getReplies());
191                         for (PostReply postReply : sone.getReplies()) {
192                                 allPostReplies.put(postReply.getId(), postReply);
193                         }
194                         soneAlbums.putAll(sone.getId(), toAllAlbums.apply(sone));
195                         for (Album album : toAllAlbums.apply(sone)) {
196                                 allAlbums.put(album.getId(), album);
197                         }
198                         soneImages.putAll(sone.getId(), toAllImages.apply(sone));
199                         for (Image image : toAllImages.apply(sone)) {
200                                 allImages.put(image.getId(), image);
201                         }
202                 } finally {
203                         lock.writeLock().unlock();
204                 }
205         }
206
207         @Override
208         public void removeSone(Sone sone) {
209                 lock.writeLock().lock();
210                 try {
211                         allSones.remove(sone.getId());
212                         Collection<Post> removedPosts = sonePosts.removeAll(sone.getId());
213                         for (Post removedPost : removedPosts) {
214                                 allPosts.remove(removedPost.getId());
215                         }
216                         Collection<PostReply> removedPostReplies =
217                                         sonePostReplies.removeAll(sone.getId());
218                         for (PostReply removedPostReply : removedPostReplies) {
219                                 allPostReplies.remove(removedPostReply.getId());
220                         }
221                         Collection<Album> removedAlbums =
222                                         soneAlbums.removeAll(sone.getId());
223                         for (Album removedAlbum : removedAlbums) {
224                                 allAlbums.remove(removedAlbum.getId());
225                         }
226                         Collection<Image> removedImages =
227                                         soneImages.removeAll(sone.getId());
228                         for (Image removedImage : removedImages) {
229                                 allImages.remove(removedImage.getId());
230                         }
231                 } finally {
232                         lock.writeLock().unlock();
233                 }
234         }
235
236         @Override
237         public Optional<Sone> getSone(String soneId) {
238                 lock.readLock().lock();
239                 try {
240                         return fromNullable(allSones.get(soneId));
241                 } finally {
242                         lock.readLock().unlock();
243                 }
244         }
245
246         @Override
247         public Collection<Sone> getSones() {
248                 lock.readLock().lock();
249                 try {
250                         return new HashSet<Sone>(allSones.values());
251                 } finally {
252                         lock.readLock().unlock();
253                 }
254         }
255
256         @Override
257         public Collection<Sone> getLocalSones() {
258                 lock.readLock().lock();
259                 try {
260                         return from(allSones.values()).filter(LOCAL_SONE_FILTER).toSet();
261                 } finally {
262                         lock.readLock().unlock();
263                 }
264         }
265
266         @Override
267         public Collection<Sone> getRemoteSones() {
268                 lock.readLock().lock();
269                 try {
270                         return from(allSones.values())
271                                         .filter(not(LOCAL_SONE_FILTER)) .toSet();
272                 } finally {
273                         lock.readLock().unlock();
274                 }
275         }
276
277         //
278         // POSTPROVIDER METHODS
279         //
280
281         /** {@inheritDocs} */
282         @Override
283         public Optional<Post> getPost(String postId) {
284                 lock.readLock().lock();
285                 try {
286                         return fromNullable(allPosts.get(postId));
287                 } finally {
288                         lock.readLock().unlock();
289                 }
290         }
291
292         /** {@inheritDocs} */
293         @Override
294         public Collection<Post> getPosts(String soneId) {
295                 return new HashSet<Post>(getPostsFrom(soneId));
296         }
297
298         /** {@inheritDocs} */
299         @Override
300         public Collection<Post> getDirectedPosts(final String recipientId) {
301                 lock.readLock().lock();
302                 try {
303                         return from(sonePosts.values()).filter(new Predicate<Post>() {
304                                 @Override
305                                 public boolean apply(Post post) {
306                                         return post.getRecipientId().asSet().contains(recipientId);
307                                 }
308                         }).toSet();
309                 } finally {
310                         lock.readLock().unlock();
311                 }
312         }
313
314         //
315         // POSTBUILDERFACTORY METHODS
316         //
317
318         /** {@inheritDocs} */
319         @Override
320         public PostBuilder newPostBuilder() {
321                 return new MemoryPostBuilder(this, soneProvider);
322         }
323
324         //
325         // POSTSTORE METHODS
326         //
327
328         /** {@inheritDocs} */
329         @Override
330         public void storePost(Post post) {
331                 checkNotNull(post, "post must not be null");
332                 lock.writeLock().lock();
333                 try {
334                         allPosts.put(post.getId(), post);
335                         getPostsFrom(post.getSone().getId()).add(post);
336                 } finally {
337                         lock.writeLock().unlock();
338                 }
339         }
340
341         /** {@inheritDocs} */
342         @Override
343         public void removePost(Post post) {
344                 checkNotNull(post, "post must not be null");
345                 lock.writeLock().lock();
346                 try {
347                         allPosts.remove(post.getId());
348                         getPostsFrom(post.getSone().getId()).remove(post);
349                         post.getSone().removePost(post);
350                 } finally {
351                         lock.writeLock().unlock();
352                 }
353         }
354
355         /** {@inheritDocs} */
356         @Override
357         public void storePosts(Sone sone, Collection<Post> posts) throws IllegalArgumentException {
358                 checkNotNull(sone, "sone must not be null");
359                 /* verify that all posts are from the same Sone. */
360                 for (Post post : posts) {
361                         if (!sone.equals(post.getSone())) {
362                                 throw new IllegalArgumentException(String.format("Post from different Sone found: %s", post));
363                         }
364                 }
365
366                 lock.writeLock().lock();
367                 try {
368                         /* remove all posts by the Sone. */
369                         Collection<Post> oldPosts = getPostsFrom(sone.getId());
370                         for (Post post : oldPosts) {
371                                 allPosts.remove(post.getId());
372                         }
373
374                         /* add new posts. */
375                         getPostsFrom(sone.getId()).addAll(posts);
376                         for (Post post : posts) {
377                                 allPosts.put(post.getId(), post);
378                         }
379                 } finally {
380                         lock.writeLock().unlock();
381                 }
382         }
383
384         /** {@inheritDocs} */
385         @Override
386         public void removePosts(Sone sone) {
387                 checkNotNull(sone, "sone must not be null");
388                 lock.writeLock().lock();
389                 try {
390                         /* remove all posts by the Sone. */
391                         getPostsFrom(sone.getId()).clear();
392                         for (Post post : sone.getPosts()) {
393                                 allPosts.remove(post.getId());
394                         }
395                 } finally {
396                         lock.writeLock().unlock();
397                 }
398         }
399
400         //
401         // POSTREPLYPROVIDER METHODS
402         //
403
404         /** {@inheritDocs} */
405         @Override
406         public Optional<PostReply> getPostReply(String id) {
407                 lock.readLock().lock();
408                 try {
409                         return fromNullable(allPostReplies.get(id));
410                 } finally {
411                         lock.readLock().unlock();
412                 }
413         }
414
415         /** {@inheritDocs} */
416         @Override
417         public List<PostReply> getReplies(final String postId) {
418                 lock.readLock().lock();
419                 try {
420                         return from(allPostReplies.values())
421                                         .filter(new Predicate<PostReply>() {
422                                                 @Override
423                                                 public boolean apply(PostReply postReply) {
424                                                         return postReply.getPostId().equals(postId);
425                                                 }
426                                         }).toSortedList(TIME_COMPARATOR);
427                 } finally {
428                         lock.readLock().unlock();
429                 }
430         }
431
432         //
433         // POSTREPLYBUILDERFACTORY METHODS
434         //
435
436         /** {@inheritDocs} */
437         @Override
438         public PostReplyBuilder newPostReplyBuilder() {
439                 return new MemoryPostReplyBuilder(this, soneProvider);
440         }
441
442         //
443         // POSTREPLYSTORE METHODS
444         //
445
446         /** {@inheritDocs} */
447         @Override
448         public void storePostReply(PostReply postReply) {
449                 lock.writeLock().lock();
450                 try {
451                         allPostReplies.put(postReply.getId(), postReply);
452                 } finally {
453                         lock.writeLock().unlock();
454                 }
455         }
456
457         /** {@inheritDocs} */
458         @Override
459         public void storePostReplies(Sone sone, Collection<PostReply> postReplies) {
460                 checkNotNull(sone, "sone must not be null");
461                 /* verify that all posts are from the same Sone. */
462                 for (PostReply postReply : postReplies) {
463                         if (!sone.equals(postReply.getSone())) {
464                                 throw new IllegalArgumentException(String.format("PostReply from different Sone found: %s", postReply));
465                         }
466                 }
467
468                 lock.writeLock().lock();
469                 try {
470                         /* remove all post replies of the Sone. */
471                         for (PostReply postReply : getRepliesFrom(sone.getId())) {
472                                 removePostReply(postReply);
473                         }
474                         for (PostReply postReply : postReplies) {
475                                 allPostReplies.put(postReply.getId(), postReply);
476                                 sonePostReplies.put(postReply.getSone().getId(), postReply);
477                         }
478                 } finally {
479                         lock.writeLock().unlock();
480                 }
481         }
482
483         /** {@inheritDocs} */
484         @Override
485         public void removePostReply(PostReply postReply) {
486                 lock.writeLock().lock();
487                 try {
488                         allPostReplies.remove(postReply.getId());
489                 } finally {
490                         lock.writeLock().unlock();
491                 }
492         }
493
494         /** {@inheritDocs} */
495         @Override
496         public void removePostReplies(Sone sone) {
497                 checkNotNull(sone, "sone must not be null");
498
499                 lock.writeLock().lock();
500                 try {
501                         for (PostReply postReply : sone.getReplies()) {
502                                 removePostReply(postReply);
503                         }
504                 } finally {
505                         lock.writeLock().unlock();
506                 }
507         }
508
509         //
510         // ALBUMPROVDER METHODS
511         //
512
513         @Override
514         public Optional<Album> getAlbum(String albumId) {
515                 lock.readLock().lock();
516                 try {
517                         return fromNullable(allAlbums.get(albumId));
518                 } finally {
519                         lock.readLock().unlock();
520                 }
521         }
522
523         //
524         // ALBUMBUILDERFACTORY METHODS
525         //
526
527         @Override
528         public AlbumBuilder newAlbumBuilder() {
529                 return new AlbumBuilderImpl();
530         }
531
532         //
533         // ALBUMSTORE METHODS
534         //
535
536         @Override
537         public void storeAlbum(Album album) {
538                 lock.writeLock().lock();
539                 try {
540                         allAlbums.put(album.getId(), album);
541                         soneAlbums.put(album.getSone().getId(), album);
542                 } finally {
543                         lock.writeLock().unlock();
544                 }
545         }
546
547         @Override
548         public void removeAlbum(Album album) {
549                 lock.writeLock().lock();
550                 try {
551                         allAlbums.remove(album.getId());
552                         soneAlbums.remove(album.getSone().getId(), album);
553                 } finally {
554                         lock.writeLock().unlock();
555                 }
556         }
557
558         //
559         // IMAGEPROVIDER METHODS
560         //
561
562         @Override
563         public Optional<Image> getImage(String imageId) {
564                 lock.readLock().lock();
565                 try {
566                         return fromNullable(allImages.get(imageId));
567                 } finally {
568                         lock.readLock().unlock();
569                 }
570         }
571
572         //
573         // IMAGEBUILDERFACTORY METHODS
574         //
575
576         @Override
577         public ImageBuilder newImageBuilder() {
578                 return new ImageBuilderImpl();
579         }
580
581         //
582         // IMAGESTORE METHODS
583         //
584
585         @Override
586         public void storeImage(Image image) {
587                 lock.writeLock().lock();
588                 try {
589                         allImages.put(image.getId(), image);
590                         soneImages.put(image.getSone().getId(), image);
591                 } finally {
592                         lock.writeLock().unlock();
593                 }
594         }
595
596         @Override
597         public void removeImage(Image image) {
598                 lock.writeLock().lock();
599                 try {
600                         allImages.remove(image.getId());
601                         soneImages.remove(image.getSone().getId(), image);
602                 } finally {
603                         lock.writeLock().unlock();
604                 }
605         }
606
607         @Override
608         public void bookmarkPost(String postId) {
609                 memoryBookmarkDatabase.bookmarkPost(postId);
610         }
611
612         @Override
613         public void bookmarkPost(Post post) {
614                 memoryBookmarkDatabase.bookmarkPost(post);
615         }
616
617         @Override
618         public void unbookmarkPost(Post post) {
619                 memoryBookmarkDatabase.unbookmarkPost(post);
620         }
621
622         @Override
623         public boolean isPostBookmarked(Post post) {
624                 return memoryBookmarkDatabase.isPostBookmarked(post);
625         }
626
627         @Override
628         public Set<Post> getBookmarkedPosts() {
629                 return memoryBookmarkDatabase.getBookmarkedPosts();
630         }
631
632         //
633         // PACKAGE-PRIVATE METHODS
634         //
635
636         /**
637          * Returns whether the given post is known.
638          *
639          * @param post
640          *              The post
641          * @return {@code true} if the post is known, {@code false} otherwise
642          */
643         boolean isPostKnown(Post post) {
644                 lock.readLock().lock();
645                 try {
646                         return knownPosts.contains(post.getId());
647                 } finally {
648                         lock.readLock().unlock();
649                 }
650         }
651
652         /**
653          * Sets whether the given post is known.
654          *
655          * @param post
656          *              The post
657          * @param known
658          *              {@code true} if the post is known, {@code false} otherwise
659          */
660         void setPostKnown(Post post, boolean known) {
661                 lock.writeLock().lock();
662                 try {
663                         if (known) {
664                                 knownPosts.add(post.getId());
665                         } else {
666                                 knownPosts.remove(post.getId());
667                         }
668                 } finally {
669                         lock.writeLock().unlock();
670                 }
671         }
672
673         /**
674          * Returns whether the given post reply is known.
675          *
676          * @param postReply
677          *              The post reply
678          * @return {@code true} if the given post reply is known, {@code false}
679          *         otherwise
680          */
681         boolean isPostReplyKnown(PostReply postReply) {
682                 lock.readLock().lock();
683                 try {
684                         return knownPostReplies.contains(postReply.getId());
685                 } finally {
686                         lock.readLock().unlock();
687                 }
688         }
689
690         /**
691          * Sets whether the given post reply is known.
692          *
693          * @param postReply
694          *              The post reply
695          * @param known
696          *              {@code true} if the post reply is known, {@code false} otherwise
697          */
698         void setPostReplyKnown(PostReply postReply, boolean known) {
699                 lock.writeLock().lock();
700                 try {
701                         if (known) {
702                                 knownPostReplies.add(postReply.getId());
703                         } else {
704                                 knownPostReplies.remove(postReply.getId());
705                         }
706                 } finally {
707                         lock.writeLock().unlock();
708                 }
709         }
710
711         //
712         // PRIVATE METHODS
713         //
714
715         /**
716          * Gets all posts for the given Sone, creating a new collection if there is
717          * none yet.
718          *
719          * @param soneId
720          *              The ID of the Sone to get the posts for
721          * @return All posts
722          */
723         private Collection<Post> getPostsFrom(String soneId) {
724                 lock.readLock().lock();
725                 try {
726                         return sonePosts.get(soneId);
727                 } finally {
728                         lock.readLock().unlock();
729                 }
730         }
731
732         /** Loads the known posts. */
733         private void loadKnownPosts() {
734                 lock.writeLock().lock();
735                 try {
736                         int postCounter = 0;
737                         while (true) {
738                                 String knownPostId = configuration.getStringValue("KnownPosts/" + postCounter++ + "/ID").getValue(null);
739                                 if (knownPostId == null) {
740                                         break;
741                                 }
742                                 knownPosts.add(knownPostId);
743                         }
744                 } finally {
745                         lock.writeLock().unlock();
746                 }
747         }
748
749         /**
750          * Saves the known posts to the configuration.
751          *
752          * @throws DatabaseException
753          *              if a configuration error occurs
754          */
755         private void saveKnownPosts() throws DatabaseException {
756                 lock.readLock().lock();
757                 try {
758                         int postCounter = 0;
759                         for (String knownPostId : knownPosts) {
760                                 configuration.getStringValue("KnownPosts/" + postCounter++ + "/ID").setValue(knownPostId);
761                         }
762                         configuration.getStringValue("KnownPosts/" + postCounter + "/ID").setValue(null);
763                 } catch (ConfigurationException ce1) {
764                         throw new DatabaseException("Could not save database.", ce1);
765                 } finally {
766                         lock.readLock().unlock();
767                 }
768         }
769
770         /**
771          * Returns all replies by the given Sone.
772          *
773          * @param id
774          *              The ID of the Sone
775          * @return The post replies of the Sone, sorted by time (newest first)
776          */
777         private Collection<PostReply> getRepliesFrom(String id) {
778                 lock.readLock().lock();
779                 try {
780                         return unmodifiableCollection(sonePostReplies.get(id));
781                 } finally {
782                         lock.readLock().unlock();
783                 }
784         }
785
786         /** Loads the known post replies. */
787         private void loadKnownPostReplies() {
788                 lock.writeLock().lock();
789                 try {
790                         int replyCounter = 0;
791                         while (true) {
792                                 String knownReplyId = configuration.getStringValue("KnownReplies/" + replyCounter++ + "/ID").getValue(null);
793                                 if (knownReplyId == null) {
794                                         break;
795                                 }
796                                 knownPostReplies.add(knownReplyId);
797                         }
798                 } finally {
799                         lock.writeLock().unlock();
800                 }
801         }
802
803         /**
804          * Saves the known post replies to the configuration.
805          *
806          * @throws DatabaseException
807          *              if a configuration error occurs
808          */
809         private void saveKnownPostReplies() throws DatabaseException {
810                 lock.readLock().lock();
811                 try {
812                         int replyCounter = 0;
813                         for (String knownReplyId : knownPostReplies) {
814                                 configuration.getStringValue("KnownReplies/" + replyCounter++ + "/ID").setValue(knownReplyId);
815                         }
816                         configuration.getStringValue("KnownReplies/" + replyCounter + "/ID").setValue(null);
817                 } catch (ConfigurationException ce1) {
818                         throw new DatabaseException("Could not save database.", ce1);
819                 } finally {
820                         lock.readLock().unlock();
821                 }
822         }
823
824 }