From: David ‘Bombe’ Roden Date: Wed, 26 Nov 2014 21:09:38 +0000 (+0100) Subject: Move post-related database functionality into its own class. X-Git-Url: https://git.pterodactylus.net/?a=commitdiff_plain;h=b282ae4881da1f8329fded3369bc08d6aaee6e7b;p=Sone.git Move post-related database functionality into its own class. --- diff --git a/src/main/java/net/pterodactylus/sone/database/memory/ConfigurationLoader.java b/src/main/java/net/pterodactylus/sone/database/memory/ConfigurationLoader.java index 8974d10..e65cf48 100644 --- a/src/main/java/net/pterodactylus/sone/database/memory/ConfigurationLoader.java +++ b/src/main/java/net/pterodactylus/sone/database/memory/ConfigurationLoader.java @@ -39,6 +39,10 @@ public class ConfigurationLoader { return loadIds("KnownPosts"); } + public synchronized void saveKnownPosts(Set knownPosts) { + saveIds("KnownPosts", knownPosts); + } + public synchronized Set loadKnownPostReplies() { return loadIds("KnownReplies"); } diff --git a/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.java b/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.java index 83a886f..813dc42 100644 --- a/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.java +++ b/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.java @@ -110,15 +110,6 @@ public class MemoryDatabase extends AbstractService implements Database { private final Map allSones = new HashMap(); private final Map lastInsertFingerprints = new HashMap(); - /** All posts by their ID. */ - private final Map allPosts = new HashMap(); - - /** All posts by their Sones. */ - private final Multimap sonePosts = HashMultimap.create(); - - /** Whether posts are known. */ - private final Set knownPosts = new HashSet(); - /** All post replies by their ID. */ private final Map allPostReplies = new HashMap(); @@ -140,6 +131,7 @@ public class MemoryDatabase extends AbstractService implements Database { private final Map allImages = new HashMap(); private final Multimap soneImages = HashMultimap.create(); + private final MemoryPostDatabase postDatabase; private final MemoryBookmarkDatabase memoryBookmarkDatabase; private final MemoryFriendDatabase memoryFriendDatabase; @@ -156,6 +148,7 @@ public class MemoryDatabase extends AbstractService implements Database { this.soneProvider = soneProvider; this.configuration = configuration; this.configurationLoader = new ConfigurationLoader(configuration); + postDatabase = new MemoryPostDatabase(this, configurationLoader); memoryBookmarkDatabase = new MemoryBookmarkDatabase(this, configurationLoader); memoryFriendDatabase = new MemoryFriendDatabase(configurationLoader); @@ -326,7 +319,6 @@ public class MemoryDatabase extends AbstractService implements Database { public void save() throws DatabaseException { lock.writeLock().lock(); try { - saveKnownPosts(); saveKnownPostReplies(); for (Sone localSone : from(localSones).transform(soneLoader()).transform(Optionals.get())) { saveSone(localSone); @@ -456,8 +448,8 @@ public class MemoryDatabase extends AbstractService implements Database { /** {@inheritDocs} */ @Override protected void doStart() { + postDatabase.start(); memoryBookmarkDatabase.start(); - loadKnownPosts(); loadKnownPostReplies(); notifyStarted(); } @@ -466,6 +458,7 @@ public class MemoryDatabase extends AbstractService implements Database { @Override protected void doStop() { try { + postDatabase.stop(); memoryBookmarkDatabase.stop(); save(); notifyStopped(); @@ -496,10 +489,7 @@ public class MemoryDatabase extends AbstractService implements Database { } private void storePosts(String soneId, Collection posts) { - sonePosts.putAll(soneId, posts); - for (Post post : posts) { - allPosts.put(post.getId(), post); - } + postDatabase.storePosts(soneId, posts); } private void storePostReplies(String soneId, Collection postReplies) { @@ -528,10 +518,7 @@ public class MemoryDatabase extends AbstractService implements Database { lock.writeLock().lock(); try { allSones.remove(sone.getId()); - Collection removedPosts = sonePosts.removeAll(sone.getId()); - for (Post removedPost : removedPosts) { - allPosts.remove(removedPost.getId()); - } + postDatabase.removePostsFor(sone.getId()); Collection removedPostReplies = sonePostReplies.removeAll(sone.getId()); for (PostReply removedPostReply : removedPostReplies) { @@ -642,12 +629,7 @@ public class MemoryDatabase extends AbstractService implements Database { /** {@inheritDocs} */ @Override public Optional getPost(String postId) { - lock.readLock().lock(); - try { - return fromNullable(allPosts.get(postId)); - } finally { - lock.readLock().unlock(); - } + return postDatabase.getPost(postId); } /** {@inheritDocs} */ @@ -659,17 +641,7 @@ public class MemoryDatabase extends AbstractService implements Database { /** {@inheritDocs} */ @Override public Collection getDirectedPosts(final String recipientId) { - lock.readLock().lock(); - try { - return from(sonePosts.values()).filter(new Predicate() { - @Override - public boolean apply(Post post) { - return post.getRecipientId().asSet().contains(recipientId); - } - }).toSet(); - } finally { - lock.readLock().unlock(); - } + return postDatabase.getDirectedPosts(recipientId); } // @@ -690,27 +662,14 @@ public class MemoryDatabase extends AbstractService implements Database { @Override public void storePost(Post post) { checkNotNull(post, "post must not be null"); - lock.writeLock().lock(); - try { - allPosts.put(post.getId(), post); - getPostsFrom(post.getSone().getId()).add(post); - } finally { - lock.writeLock().unlock(); - } + postDatabase.storePost(post); } /** {@inheritDocs} */ @Override public void removePost(Post post) { checkNotNull(post, "post must not be null"); - lock.writeLock().lock(); - try { - allPosts.remove(post.getId()); - getPostsFrom(post.getSone().getId()).remove(post); - post.getSone().removePost(post); - } finally { - lock.writeLock().unlock(); - } + postDatabase.removePost(post.getId()); } // @@ -911,12 +870,7 @@ public class MemoryDatabase extends AbstractService implements Database { * @return {@code true} if the post is known, {@code false} otherwise */ boolean isPostKnown(Post post) { - lock.readLock().lock(); - try { - return knownPosts.contains(post.getId()); - } finally { - lock.readLock().unlock(); - } + return postDatabase.isPostKnown(post.getId()); } /** @@ -928,16 +882,7 @@ public class MemoryDatabase extends AbstractService implements Database { * {@code true} if the post is known, {@code false} otherwise */ void setPostKnown(Post post, boolean known) { - lock.writeLock().lock(); - try { - if (known) { - knownPosts.add(post.getId()); - } else { - knownPosts.remove(post.getId()); - } - } finally { - lock.writeLock().unlock(); - } + postDatabase.setPostKnown(post.getId(), known); } /** @@ -991,46 +936,7 @@ public class MemoryDatabase extends AbstractService implements Database { * @return All posts */ private Collection getPostsFrom(String soneId) { - lock.readLock().lock(); - try { - return sonePosts.get(soneId); - } finally { - lock.readLock().unlock(); - } - } - - /** Loads the known posts. */ - private void loadKnownPosts() { - Set knownPosts = configurationLoader.loadKnownPosts(); - lock.writeLock().lock(); - try { - this.knownPosts.clear(); - this.knownPosts.addAll(knownPosts); - } finally { - lock.writeLock().unlock(); - } - } - - /** - * Saves the known posts to the configuration. - * - * @throws DatabaseException - * if a configuration error occurs - */ - private void saveKnownPosts() throws DatabaseException { - lock.readLock().lock(); - try { - int postCounter = 0; - for (String knownPostId : knownPosts) { - configuration.getStringValue("KnownPosts/" + postCounter++ + "/ID").setValue( - knownPostId); - } - configuration.getStringValue("KnownPosts/" + postCounter + "/ID").setValue(null); - } catch (ConfigurationException ce1) { - throw new DatabaseException("Could not save database.", ce1); - } finally { - lock.readLock().unlock(); - } + return postDatabase.getPostsFrom(soneId); } /** Loads the known post replies. */ diff --git a/src/main/java/net/pterodactylus/sone/database/memory/MemoryPostDatabase.java b/src/main/java/net/pterodactylus/sone/database/memory/MemoryPostDatabase.java new file mode 100644 index 0000000..fb66162 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/database/memory/MemoryPostDatabase.java @@ -0,0 +1,194 @@ +package net.pterodactylus.sone.database.memory; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import net.pterodactylus.sone.data.Post; +import net.pterodactylus.sone.database.DatabaseException; +import net.pterodactylus.sone.utils.Optionals; + +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; + +/** + * Groups {@link Post}-related database functions. + * + * @author David ‘Bombe’ Roden + */ +class MemoryPostDatabase { + + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + private final MemoryDatabase database; + private final ConfigurationLoader configurationLoader; + private final Multimap sonePosts = HashMultimap.create(); + private final Map postSones = new HashMap(); + private final Map postTimes = new HashMap(); + private final Map postRecipients = new HashMap(); + private final Multimap recipientPosts = HashMultimap.create(); + private final Map postTexts = new HashMap(); + private final Set knownPosts = new HashSet(); + + MemoryPostDatabase(MemoryDatabase database, ConfigurationLoader configurationLoader) { + this.database = database; + this.configurationLoader = configurationLoader; + } + + void start() { + loadKnownPosts(); + } + + void stop() { + saveKnownPosts(); + } + + Optional getPost(String postId) { + lock.readLock().lock(); + try { + if (!postSones.containsKey(postId)) { + return Optional.absent(); + } + String sender = postSones.get(postId); + String recipientId = postRecipients.get(postId); + long time = postTimes.get(postId); + String text = postTexts.get(postId); + return Optional.of( + new MemoryPost(database, database, postId, sender, recipientId, time, text)); + } finally { + lock.readLock().unlock(); + } + } + + Collection getPostsFrom(String soneId) { + lock.readLock().lock(); + try { + return FluentIterable.from(sonePosts.get(soneId)) + .transform(postLoader) + .transform(Optionals.get()) + .toList(); + } finally { + lock.readLock().unlock(); + } + } + + Collection getDirectedPosts(String recipientId) { + lock.readLock().lock(); + try { + return FluentIterable.from(recipientPosts.get(recipientId)) + .transform(postLoader) + .transform(Optionals.get()) + .toList(); + } finally { + lock.readLock().unlock(); + } + } + + void storePosts(String soneId, Collection posts) { + lock.writeLock().lock(); + try { + removePostsFor(soneId); + for (Post post : posts) { + storePost(post); + } + } finally { + lock.writeLock().unlock(); + } + } + + void storePost(Post post) { + String soneId = post.getSone().getId(); + final String postId = post.getId(); + sonePosts.put(soneId, postId); + postSones.put(postId, soneId); + postRecipients.put(postId, post.getRecipientId().orNull()); + if (post.getRecipientId().isPresent()) { + recipientPosts.put(post.getRecipientId().get(), postId); + } + postTimes.put(postId, post.getTime()); + postTexts.put(postId, post.getText()); + } + + void removePostsFor(String soneId) { + lock.writeLock().lock(); + try { + for (String postId : sonePosts.removeAll(soneId)) { + removePost(postId); + } + } finally { + lock.writeLock().unlock(); + } + } + + void removePost(String postId) { + lock.writeLock().lock(); + try { + postSones.remove(postId); + String recipient = postRecipients.remove(postId); + if (recipient != null) { + recipientPosts.remove(recipient, postId); + } + postTimes.remove(postId); + postTexts.remove(postId); + } finally { + lock.writeLock().unlock(); + } + } + + boolean isPostKnown(String postId) { + lock.readLock().lock(); + try { + return knownPosts.contains(postId); + } finally { + lock.readLock().unlock(); + } + } + + void setPostKnown(String postId, boolean known) { + lock.writeLock().lock(); + try { + if (known) { + knownPosts.add(postId); + } else { + knownPosts.remove(postId); + } + saveKnownPosts(); + } finally { + lock.writeLock().unlock(); + } + } + + private void loadKnownPosts() { + Set knownPosts = configurationLoader.loadKnownPosts(); + lock.writeLock().lock(); + try { + this.knownPosts.clear(); + this.knownPosts.addAll(knownPosts); + } finally { + lock.writeLock().unlock(); + } + } + + private void saveKnownPosts() { + lock.readLock().lock(); + try { + configurationLoader.saveKnownPosts(knownPosts); + } finally { + lock.readLock().unlock(); + } + } + + private Function> postLoader = new Function>() { + @Override + public Optional apply(String input) { + return getPost(input); + } + }; + +} diff --git a/src/test/java/net/pterodactylus/sone/database/memory/ConfigurationLoaderTest.java b/src/test/java/net/pterodactylus/sone/database/memory/ConfigurationLoaderTest.java index 47e26b7..41bc593 100644 --- a/src/test/java/net/pterodactylus/sone/database/memory/ConfigurationLoaderTest.java +++ b/src/test/java/net/pterodactylus/sone/database/memory/ConfigurationLoaderTest.java @@ -3,11 +3,16 @@ package net.pterodactylus.sone.database.memory; import static java.util.Arrays.asList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; +import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Map; import java.util.Set; import net.pterodactylus.sone.TestValue; @@ -16,6 +21,8 @@ import net.pterodactylus.util.config.ConfigurationException; import net.pterodactylus.util.config.Value; import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; /** * Unit test for {@link ConfigurationLoader}. @@ -41,6 +48,29 @@ public class ConfigurationLoaderTest { } @Test + public void loaderCanSaveKnownPosts() throws ConfigurationException { + Map> configurationValues = prepareConfigurationValues(); + HashSet originalPosts = new LinkedHashSet(asList("Post1", "Post2")); + configurationLoader.saveKnownPosts(originalPosts); + assertThat(configurationValues.get("KnownPosts/0/ID").getValue(), is("Post1")); + assertThat(configurationValues.get("KnownPosts/1/ID").getValue(), is("Post2")); + assertThat(configurationValues.get("KnownPosts/2/ID").getValue(), nullValue()); + } + + private Map> prepareConfigurationValues() { + final Map> configurationValues = new HashMap>(); + when(configuration.getStringValue(anyString())).thenAnswer(new Answer>() { + @Override + public Value answer(InvocationOnMock invocation) throws Throwable { + Value stringValue = TestValue.from(null); + configurationValues.put((String) invocation.getArguments()[0], stringValue); + return stringValue; + } + }); + return configurationValues; + } + + @Test public void loaderCanLoadKnownPostReplies() { when(configuration.getStringValue("KnownReplies/0/ID")) .thenReturn(TestValue.from("PostReply2"));