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