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