Add method to save bookmarked posts to configuration loader.
[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                         memoryBookmarkDatabase.stop();
172                         save();
173                         notifyStopped();
174                 } catch (DatabaseException de1) {
175                         notifyFailed(de1);
176                 }
177         }
178
179         @Override
180         public SoneBuilder newSoneBuilder() {
181                 return new MemorySoneBuilder();
182         }
183
184         @Override
185         public void storeSone(Sone sone) {
186                 lock.writeLock().lock();
187                 try {
188                         removeSone(sone);
189
190                         allSones.put(sone.getId(), sone);
191                         sonePosts.putAll(sone.getId(), sone.getPosts());
192                         for (Post post : sone.getPosts()) {
193                                 allPosts.put(post.getId(), post);
194                         }
195                         sonePostReplies.putAll(sone.getId(), sone.getReplies());
196                         for (PostReply postReply : sone.getReplies()) {
197                                 allPostReplies.put(postReply.getId(), postReply);
198                         }
199                         soneAlbums.putAll(sone.getId(), toAllAlbums.apply(sone));
200                         for (Album album : toAllAlbums.apply(sone)) {
201                                 allAlbums.put(album.getId(), album);
202                         }
203                         soneImages.putAll(sone.getId(), toAllImages.apply(sone));
204                         for (Image image : toAllImages.apply(sone)) {
205                                 allImages.put(image.getId(), image);
206                         }
207                 } finally {
208                         lock.writeLock().unlock();
209                 }
210         }
211
212         @Override
213         public void removeSone(Sone sone) {
214                 lock.writeLock().lock();
215                 try {
216                         allSones.remove(sone.getId());
217                         Collection<Post> removedPosts = sonePosts.removeAll(sone.getId());
218                         for (Post removedPost : removedPosts) {
219                                 allPosts.remove(removedPost.getId());
220                         }
221                         Collection<PostReply> removedPostReplies =
222                                         sonePostReplies.removeAll(sone.getId());
223                         for (PostReply removedPostReply : removedPostReplies) {
224                                 allPostReplies.remove(removedPostReply.getId());
225                         }
226                         Collection<Album> removedAlbums =
227                                         soneAlbums.removeAll(sone.getId());
228                         for (Album removedAlbum : removedAlbums) {
229                                 allAlbums.remove(removedAlbum.getId());
230                         }
231                         Collection<Image> removedImages =
232                                         soneImages.removeAll(sone.getId());
233                         for (Image removedImage : removedImages) {
234                                 allImages.remove(removedImage.getId());
235                         }
236                 } finally {
237                         lock.writeLock().unlock();
238                 }
239         }
240
241         @Override
242         public Optional<Sone> getSone(String soneId) {
243                 lock.readLock().lock();
244                 try {
245                         return fromNullable(allSones.get(soneId));
246                 } finally {
247                         lock.readLock().unlock();
248                 }
249         }
250
251         @Override
252         public Collection<Sone> getSones() {
253                 lock.readLock().lock();
254                 try {
255                         return new HashSet<Sone>(allSones.values());
256                 } finally {
257                         lock.readLock().unlock();
258                 }
259         }
260
261         @Override
262         public Collection<Sone> getLocalSones() {
263                 lock.readLock().lock();
264                 try {
265                         return from(allSones.values()).filter(LOCAL_SONE_FILTER).toSet();
266                 } finally {
267                         lock.readLock().unlock();
268                 }
269         }
270
271         @Override
272         public Collection<Sone> getRemoteSones() {
273                 lock.readLock().lock();
274                 try {
275                         return from(allSones.values())
276                                         .filter(not(LOCAL_SONE_FILTER)) .toSet();
277                 } finally {
278                         lock.readLock().unlock();
279                 }
280         }
281
282         //
283         // POSTPROVIDER METHODS
284         //
285
286         /** {@inheritDocs} */
287         @Override
288         public Optional<Post> getPost(String postId) {
289                 lock.readLock().lock();
290                 try {
291                         return fromNullable(allPosts.get(postId));
292                 } finally {
293                         lock.readLock().unlock();
294                 }
295         }
296
297         /** {@inheritDocs} */
298         @Override
299         public Collection<Post> getPosts(String soneId) {
300                 return new HashSet<Post>(getPostsFrom(soneId));
301         }
302
303         /** {@inheritDocs} */
304         @Override
305         public Collection<Post> getDirectedPosts(final String recipientId) {
306                 lock.readLock().lock();
307                 try {
308                         return from(sonePosts.values()).filter(new Predicate<Post>() {
309                                 @Override
310                                 public boolean apply(Post post) {
311                                         return post.getRecipientId().asSet().contains(recipientId);
312                                 }
313                         }).toSet();
314                 } finally {
315                         lock.readLock().unlock();
316                 }
317         }
318
319         //
320         // POSTBUILDERFACTORY METHODS
321         //
322
323         /** {@inheritDocs} */
324         @Override
325         public PostBuilder newPostBuilder() {
326                 return new MemoryPostBuilder(this, soneProvider);
327         }
328
329         //
330         // POSTSTORE METHODS
331         //
332
333         /** {@inheritDocs} */
334         @Override
335         public void storePost(Post post) {
336                 checkNotNull(post, "post must not be null");
337                 lock.writeLock().lock();
338                 try {
339                         allPosts.put(post.getId(), post);
340                         getPostsFrom(post.getSone().getId()).add(post);
341                 } finally {
342                         lock.writeLock().unlock();
343                 }
344         }
345
346         /** {@inheritDocs} */
347         @Override
348         public void removePost(Post post) {
349                 checkNotNull(post, "post must not be null");
350                 lock.writeLock().lock();
351                 try {
352                         allPosts.remove(post.getId());
353                         getPostsFrom(post.getSone().getId()).remove(post);
354                         post.getSone().removePost(post);
355                 } finally {
356                         lock.writeLock().unlock();
357                 }
358         }
359
360         /** {@inheritDocs} */
361         @Override
362         public void storePosts(Sone sone, Collection<Post> posts) throws IllegalArgumentException {
363                 checkNotNull(sone, "sone must not be null");
364                 /* verify that all posts are from the same Sone. */
365                 for (Post post : posts) {
366                         if (!sone.equals(post.getSone())) {
367                                 throw new IllegalArgumentException(String.format("Post from different Sone found: %s", post));
368                         }
369                 }
370
371                 lock.writeLock().lock();
372                 try {
373                         /* remove all posts by the Sone. */
374                         Collection<Post> oldPosts = getPostsFrom(sone.getId());
375                         for (Post post : oldPosts) {
376                                 allPosts.remove(post.getId());
377                         }
378
379                         /* add new posts. */
380                         getPostsFrom(sone.getId()).addAll(posts);
381                         for (Post post : posts) {
382                                 allPosts.put(post.getId(), post);
383                         }
384                 } finally {
385                         lock.writeLock().unlock();
386                 }
387         }
388
389         /** {@inheritDocs} */
390         @Override
391         public void removePosts(Sone sone) {
392                 checkNotNull(sone, "sone must not be null");
393                 lock.writeLock().lock();
394                 try {
395                         /* remove all posts by the Sone. */
396                         getPostsFrom(sone.getId()).clear();
397                         for (Post post : sone.getPosts()) {
398                                 allPosts.remove(post.getId());
399                         }
400                 } finally {
401                         lock.writeLock().unlock();
402                 }
403         }
404
405         //
406         // POSTREPLYPROVIDER METHODS
407         //
408
409         /** {@inheritDocs} */
410         @Override
411         public Optional<PostReply> getPostReply(String id) {
412                 lock.readLock().lock();
413                 try {
414                         return fromNullable(allPostReplies.get(id));
415                 } finally {
416                         lock.readLock().unlock();
417                 }
418         }
419
420         /** {@inheritDocs} */
421         @Override
422         public List<PostReply> getReplies(final String postId) {
423                 lock.readLock().lock();
424                 try {
425                         return from(allPostReplies.values())
426                                         .filter(new Predicate<PostReply>() {
427                                                 @Override
428                                                 public boolean apply(PostReply postReply) {
429                                                         return postReply.getPostId().equals(postId);
430                                                 }
431                                         }).toSortedList(TIME_COMPARATOR);
432                 } finally {
433                         lock.readLock().unlock();
434                 }
435         }
436
437         //
438         // POSTREPLYBUILDERFACTORY METHODS
439         //
440
441         /** {@inheritDocs} */
442         @Override
443         public PostReplyBuilder newPostReplyBuilder() {
444                 return new MemoryPostReplyBuilder(this, soneProvider);
445         }
446
447         //
448         // POSTREPLYSTORE METHODS
449         //
450
451         /** {@inheritDocs} */
452         @Override
453         public void storePostReply(PostReply postReply) {
454                 lock.writeLock().lock();
455                 try {
456                         allPostReplies.put(postReply.getId(), postReply);
457                 } finally {
458                         lock.writeLock().unlock();
459                 }
460         }
461
462         /** {@inheritDocs} */
463         @Override
464         public void storePostReplies(Sone sone, Collection<PostReply> postReplies) {
465                 checkNotNull(sone, "sone must not be null");
466                 /* verify that all posts are from the same Sone. */
467                 for (PostReply postReply : postReplies) {
468                         if (!sone.equals(postReply.getSone())) {
469                                 throw new IllegalArgumentException(String.format("PostReply from different Sone found: %s", postReply));
470                         }
471                 }
472
473                 lock.writeLock().lock();
474                 try {
475                         /* remove all post replies of the Sone. */
476                         for (PostReply postReply : getRepliesFrom(sone.getId())) {
477                                 removePostReply(postReply);
478                         }
479                         for (PostReply postReply : postReplies) {
480                                 allPostReplies.put(postReply.getId(), postReply);
481                                 sonePostReplies.put(postReply.getSone().getId(), postReply);
482                         }
483                 } finally {
484                         lock.writeLock().unlock();
485                 }
486         }
487
488         /** {@inheritDocs} */
489         @Override
490         public void removePostReply(PostReply postReply) {
491                 lock.writeLock().lock();
492                 try {
493                         allPostReplies.remove(postReply.getId());
494                 } finally {
495                         lock.writeLock().unlock();
496                 }
497         }
498
499         /** {@inheritDocs} */
500         @Override
501         public void removePostReplies(Sone sone) {
502                 checkNotNull(sone, "sone must not be null");
503
504                 lock.writeLock().lock();
505                 try {
506                         for (PostReply postReply : sone.getReplies()) {
507                                 removePostReply(postReply);
508                         }
509                 } finally {
510                         lock.writeLock().unlock();
511                 }
512         }
513
514         //
515         // ALBUMPROVDER METHODS
516         //
517
518         @Override
519         public Optional<Album> getAlbum(String albumId) {
520                 lock.readLock().lock();
521                 try {
522                         return fromNullable(allAlbums.get(albumId));
523                 } finally {
524                         lock.readLock().unlock();
525                 }
526         }
527
528         //
529         // ALBUMBUILDERFACTORY METHODS
530         //
531
532         @Override
533         public AlbumBuilder newAlbumBuilder() {
534                 return new AlbumBuilderImpl();
535         }
536
537         //
538         // ALBUMSTORE METHODS
539         //
540
541         @Override
542         public void storeAlbum(Album album) {
543                 lock.writeLock().lock();
544                 try {
545                         allAlbums.put(album.getId(), album);
546                         soneAlbums.put(album.getSone().getId(), album);
547                 } finally {
548                         lock.writeLock().unlock();
549                 }
550         }
551
552         @Override
553         public void removeAlbum(Album album) {
554                 lock.writeLock().lock();
555                 try {
556                         allAlbums.remove(album.getId());
557                         soneAlbums.remove(album.getSone().getId(), album);
558                 } finally {
559                         lock.writeLock().unlock();
560                 }
561         }
562
563         //
564         // IMAGEPROVIDER METHODS
565         //
566
567         @Override
568         public Optional<Image> getImage(String imageId) {
569                 lock.readLock().lock();
570                 try {
571                         return fromNullable(allImages.get(imageId));
572                 } finally {
573                         lock.readLock().unlock();
574                 }
575         }
576
577         //
578         // IMAGEBUILDERFACTORY METHODS
579         //
580
581         @Override
582         public ImageBuilder newImageBuilder() {
583                 return new ImageBuilderImpl();
584         }
585
586         //
587         // IMAGESTORE METHODS
588         //
589
590         @Override
591         public void storeImage(Image image) {
592                 lock.writeLock().lock();
593                 try {
594                         allImages.put(image.getId(), image);
595                         soneImages.put(image.getSone().getId(), image);
596                 } finally {
597                         lock.writeLock().unlock();
598                 }
599         }
600
601         @Override
602         public void removeImage(Image image) {
603                 lock.writeLock().lock();
604                 try {
605                         allImages.remove(image.getId());
606                         soneImages.remove(image.getSone().getId(), image);
607                 } finally {
608                         lock.writeLock().unlock();
609                 }
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                 Set<String> knownPosts = configurationLoader.loadKnownPosts();
735                 lock.writeLock().lock();
736                 try {
737                         this.knownPosts.clear();
738                         this.knownPosts.addAll(knownPosts);
739                 } finally {
740                         lock.writeLock().unlock();
741                 }
742         }
743
744         /**
745          * Saves the known posts to the configuration.
746          *
747          * @throws DatabaseException
748          *              if a configuration error occurs
749          */
750         private void saveKnownPosts() throws DatabaseException {
751                 lock.readLock().lock();
752                 try {
753                         int postCounter = 0;
754                         for (String knownPostId : knownPosts) {
755                                 configuration.getStringValue("KnownPosts/" + postCounter++ + "/ID").setValue(knownPostId);
756                         }
757                         configuration.getStringValue("KnownPosts/" + postCounter + "/ID").setValue(null);
758                 } catch (ConfigurationException ce1) {
759                         throw new DatabaseException("Could not save database.", ce1);
760                 } finally {
761                         lock.readLock().unlock();
762                 }
763         }
764
765         /**
766          * Returns all replies by the given Sone.
767          *
768          * @param id
769          *              The ID of the Sone
770          * @return The post replies of the Sone, sorted by time (newest first)
771          */
772         private Collection<PostReply> getRepliesFrom(String id) {
773                 lock.readLock().lock();
774                 try {
775                         return unmodifiableCollection(sonePostReplies.get(id));
776                 } finally {
777                         lock.readLock().unlock();
778                 }
779         }
780
781         /** Loads the known post replies. */
782         private void loadKnownPostReplies() {
783                 Set<String> knownPostReplies = configurationLoader.loadKnownPostReplies();
784                 lock.writeLock().lock();
785                 try {
786                         this.knownPostReplies.clear();
787                         this.knownPostReplies.addAll(knownPostReplies);
788                 } finally {
789                         lock.writeLock().unlock();
790                 }
791         }
792
793         /**
794          * Saves the known post replies to the configuration.
795          *
796          * @throws DatabaseException
797          *              if a configuration error occurs
798          */
799         private void saveKnownPostReplies() throws DatabaseException {
800                 lock.readLock().lock();
801                 try {
802                         int replyCounter = 0;
803                         for (String knownReplyId : knownPostReplies) {
804                                 configuration.getStringValue("KnownReplies/" + replyCounter++ + "/ID").setValue(knownReplyId);
805                         }
806                         configuration.getStringValue("KnownReplies/" + replyCounter + "/ID").setValue(null);
807                 } catch (ConfigurationException ce1) {
808                         throw new DatabaseException("Could not save database.", ce1);
809                 } finally {
810                         lock.readLock().unlock();
811                 }
812         }
813
814 }