From 4d62892729cbce31c4fb2171ad2c2174db1d44b4 Mon Sep 17 00:00:00 2001 From: =?utf8?q?David=20=E2=80=98Bombe=E2=80=99=20Roden?= Date: Sat, 8 Mar 2014 22:26:45 +0100 Subject: [PATCH] Extract memory-backed post database into its own class. --- .../java/net/pterodactylus/sone/core/Core.java | 5 - .../net/pterodactylus/sone/database/Database.java | 8 - .../sone/database/memory/MemoryDatabase.java | 225 ++------------- .../sone/database/memory/MemoryPostDatabase.java | 301 +++++++++++++++++++++ 4 files changed, 328 insertions(+), 211 deletions(-) create mode 100644 src/main/java/net/pterodactylus/sone/database/memory/MemoryPostDatabase.java diff --git a/src/main/java/net/pterodactylus/sone/core/Core.java b/src/main/java/net/pterodactylus/sone/core/Core.java index cb791dc..5a205d3 100644 --- a/src/main/java/net/pterodactylus/sone/core/Core.java +++ b/src/main/java/net/pterodactylus/sone/core/Core.java @@ -1480,9 +1480,6 @@ public class Core extends AbstractService implements SoneProvider { configuration.getStringValue("SoneFollowingTimes/" + soneCounter + "/Sone").setValue(null); } - /* save known posts. */ - database.save(); - /* save bookmarked posts. */ int bookmarkedPostCounter = 0; synchronized (bookmarkedPosts) { @@ -1497,8 +1494,6 @@ public class Core extends AbstractService implements SoneProvider { } catch (ConfigurationException ce1) { logger.log(Level.SEVERE, "Could not store configuration!", ce1); - } catch (DatabaseException de1) { - logger.log(Level.SEVERE, "Could not save database!", de1); } finally { synchronized (configuration) { storingConfiguration = false; diff --git a/src/main/java/net/pterodactylus/sone/database/Database.java b/src/main/java/net/pterodactylus/sone/database/Database.java index c6ebc03..02b4ae1 100644 --- a/src/main/java/net/pterodactylus/sone/database/Database.java +++ b/src/main/java/net/pterodactylus/sone/database/Database.java @@ -28,12 +28,4 @@ import com.google.common.util.concurrent.Service; */ public interface Database extends Service, IdentityDatabase, SoneDatabase, PostDatabase, PostReplyDatabase, AlbumDatabase, ImageDatabase { - /** - * Saves the database. - * - * @throws DatabaseException - * if an error occurs while saving - */ - public void save() throws DatabaseException; - } 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 4bf6643..8d60e0f 100644 --- a/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.java +++ b/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.java @@ -22,6 +22,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Predicates.not; import static com.google.common.collect.FluentIterable.from; import static java.util.Collections.emptyList; +import static java.util.logging.Logger.getLogger; import static net.pterodactylus.sone.data.Sone.LOCAL_SONE_FILTER; import java.util.ArrayList; @@ -35,6 +36,8 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.logging.Level; +import java.util.logging.Logger; import net.pterodactylus.sone.data.Album; import net.pterodactylus.sone.data.Image; @@ -44,7 +47,6 @@ import net.pterodactylus.sone.data.Sone; import net.pterodactylus.sone.data.impl.DefaultSoneBuilder; import net.pterodactylus.sone.database.Database; import net.pterodactylus.sone.database.DatabaseException; -import net.pterodactylus.sone.database.PostDatabase; import net.pterodactylus.sone.database.SoneBuilder; import net.pterodactylus.sone.freenet.wot.Identity; import net.pterodactylus.util.config.Configuration; @@ -56,7 +58,6 @@ import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.HashMultimap; import com.google.common.collect.ListMultimap; import com.google.common.collect.Maps; -import com.google.common.collect.Multimap; import com.google.common.collect.SetMultimap; import com.google.common.collect.SortedSetMultimap; import com.google.common.collect.TreeMultimap; @@ -64,12 +65,14 @@ import com.google.common.util.concurrent.AbstractService; import com.google.inject.Inject; /** - * Memory-based {@link PostDatabase} implementation. + * Memory-based {@link Database} implementation. * * @author David ‘Bombe’ Roden */ public class MemoryDatabase extends AbstractService implements Database { + private static final Logger logger = getLogger(MemoryDatabase.class.getName()); + /** The lock. */ private final ReadWriteLock lock = new ReentrantReadWriteLock(); @@ -78,20 +81,7 @@ public class MemoryDatabase extends AbstractService implements Database { private final Map identities = Maps.newHashMap(); private final Map sones = new HashMap(); - - /** All posts by their ID. */ - private final Map allPosts = new HashMap(); - - /** All posts by their Sones. */ - private final Multimap sonePosts = HashMultimap.create(); - private final SetMultimap likedPostsBySone = HashMultimap.create(); - private final SetMultimap postLikingSones = HashMultimap.create(); - - /** All posts by their recipient. */ - private final Multimap recipientPosts = HashMultimap.create(); - - /** Whether posts are known. */ - private final Set knownPosts = new HashSet(); + private final MemoryPostDatabase memoryPostDatabase; /** All post replies by their ID. */ private final Map allPostReplies = new HashMap(); @@ -134,16 +124,7 @@ public class MemoryDatabase extends AbstractService implements Database { @Inject public MemoryDatabase(Configuration configuration) { this.configuration = configuration; - } - - // - // DATABASE METHODS - // - - @Override - public void save() throws DatabaseException { - saveKnownPosts(); - saveKnownPostReplies(); + memoryPostDatabase = new MemoryPostDatabase(this, lock, configuration); } // @@ -152,7 +133,7 @@ public class MemoryDatabase extends AbstractService implements Database { @Override protected void doStart() { - loadKnownPosts(); + memoryPostDatabase.start(); loadKnownPostReplies(); notifyStarted(); } @@ -160,11 +141,11 @@ public class MemoryDatabase extends AbstractService implements Database { @Override protected void doStop() { try { - save(); - notifyStopped(); + memoryPostDatabase.stop(); } catch (DatabaseException de1) { - notifyFailed(de1); + logger.log(Level.WARNING, "Could not stop post database!", de1); } + notifyStopped(); } @Override @@ -270,43 +251,22 @@ public class MemoryDatabase extends AbstractService implements Database { @Override public Function> getPost() { - return new Function>() { - @Override - public Optional apply(String postId) { - return (postId == null) ? Optional.absent() : getPost(postId); - } - }; + return memoryPostDatabase.getPost(); } @Override public Optional getPost(String postId) { - lock.readLock().lock(); - try { - return fromNullable(allPosts.get(postId)); - } finally { - lock.readLock().unlock(); - } + return memoryPostDatabase.getPost(postId); } @Override public Collection getPosts(String soneId) { - lock.readLock().lock(); - try { - return new HashSet(sonePosts.get(soneId)); - } finally { - lock.readLock().unlock(); - } + return memoryPostDatabase.getPosts(soneId); } @Override public Collection getDirectedPosts(String recipientId) { - lock.readLock().lock(); - try { - Collection posts = recipientPosts.get(recipientId); - return (posts == null) ? Collections.emptySet() : new HashSet(posts); - } finally { - lock.readLock().unlock(); - } + return memoryPostDatabase.getDirectedPosts(recipientId); } /** @@ -318,12 +278,7 @@ public class MemoryDatabase extends AbstractService implements Database { */ @Override public boolean isPostKnown(Post post) { - lock.readLock().lock(); - try { - return knownPosts.contains(post.getId()); - } finally { - lock.readLock().unlock(); - } + return memoryPostDatabase.isPostKnown(post); } /** @@ -334,53 +289,26 @@ public class MemoryDatabase extends AbstractService implements Database { */ @Override public void setPostKnown(Post post) { - lock.writeLock().lock(); - try { - knownPosts.add(post.getId()); - } finally { - lock.writeLock().unlock(); - } + memoryPostDatabase.setPostKnown(post); } @Override public void likePost(Post post, Sone localSone) { - lock.writeLock().lock(); - try { - likedPostsBySone.put(localSone.getId(), post.getId()); - postLikingSones.put(post.getId(), localSone.getId()); - } finally { - lock.writeLock().unlock(); - } + memoryPostDatabase.likePost(post, localSone); } @Override public void unlikePost(Post post, Sone localSone) { - lock.writeLock().lock(); - try { - likedPostsBySone.remove(localSone.getId(), post.getId()); - postLikingSones.remove(post.getId(), localSone.getId()); - } finally { - lock.writeLock().unlock(); - } + memoryPostDatabase.unlikePost(post, localSone); } public boolean isLiked(Post post, Sone sone) { - lock.readLock().lock(); - try { - return likedPostsBySone.containsEntry(sone.getId(), post.getId()); - } finally { - lock.readLock().unlock(); - } + return memoryPostDatabase.isLiked(post, sone); } @Override public Set getLikes(Post post) { - lock.readLock().lock(); - try { - return from(postLikingSones.get(post.getId())).transform(getSone()).transformAndConcat(this.unwrap()).toSet(); - } finally { - lock.readLock().unlock(); - } + return memoryPostDatabase.getLikes(post); } // @@ -389,85 +317,24 @@ 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); - sonePosts.put(post.getSone().getId(), post); - if (post.getRecipientId().isPresent()) { - recipientPosts.put(post.getRecipientId().get(), post); - } - } finally { - lock.writeLock().unlock(); - } + memoryPostDatabase.storePost(post); } @Override public void removePost(Post post) { - checkNotNull(post, "post must not be null"); - lock.writeLock().lock(); - try { - allPosts.remove(post.getId()); - sonePosts.remove(post.getSone().getId(), post); - if (post.getRecipientId().isPresent()) { - recipientPosts.remove(post.getRecipientId().get(), post); - } - post.getSone().removePost(post); - } finally { - lock.writeLock().unlock(); - } + memoryPostDatabase.removePost(post); } @Override public void storePosts(Sone sone, Collection posts) throws IllegalArgumentException { - checkNotNull(sone, "sone must not be null"); /* verify that all posts are from the same Sone. */ - for (Post post : posts) { - if (!sone.equals(post.getSone())) { - throw new IllegalArgumentException(String.format("Post from different Sone found: %s", post)); - } - } - lock.writeLock().lock(); - try { - /* remove all posts by the Sone. */ - sonePosts.removeAll(sone.getId()); - for (Post post : posts) { - allPosts.remove(post.getId()); - if (post.getRecipientId().isPresent()) { - recipientPosts.remove(post.getRecipientId().get(), post); - } - } - - /* add new posts. */ - sonePosts.putAll(sone.getId(), posts); - for (Post post : posts) { - allPosts.put(post.getId(), post); - if (post.getRecipientId().isPresent()) { - recipientPosts.put(post.getRecipientId().get(), post); - } - } - } finally { - lock.writeLock().unlock(); - } + memoryPostDatabase.storePosts(sone, posts); } @Override public void removePosts(Sone sone) { - checkNotNull(sone, "sone must not be null"); - lock.writeLock().lock(); - try { - /* remove all posts by the Sone. */ - sonePosts.removeAll(sone.getId()); - for (Post post : sone.getPosts()) { - allPosts.remove(post.getId()); - if (post.getRecipientId().isPresent()) { - recipientPosts.remove(post.getRecipientId().get(), post); - } - } - } finally { - lock.writeLock().unlock(); - } + memoryPostDatabase.removePosts(sone); } // @@ -802,44 +669,6 @@ public class MemoryDatabase extends AbstractService implements Database { // PRIVATE METHODS // - /** Loads the known posts. */ - private void loadKnownPosts() { - lock.writeLock().lock(); - try { - int postCounter = 0; - while (true) { - String knownPostId = configuration.getStringValue("KnownPosts/" + postCounter++ + "/ID").getValue(null); - if (knownPostId == null) { - break; - } - knownPosts.add(knownPostId); - } - } 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(); - } - } - /** * Returns all replies by the given Sone. * @@ -915,7 +744,7 @@ public class MemoryDatabase extends AbstractService implements Database { }; } - private static Function, Iterable> unwrap() { + static Function, Iterable> unwrap() { return new Function, Iterable>() { @Override public Iterable apply(Optional input) { 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..a16a36b --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/database/memory/MemoryPostDatabase.java @@ -0,0 +1,301 @@ +/* + * Sone - MemoryPostDatabase.java - Copyright © 2014 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.database.memory; + +import static com.google.common.base.Optional.fromNullable; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.FluentIterable.from; +import static com.google.common.collect.HashMultimap.create; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.locks.ReadWriteLock; + +import net.pterodactylus.sone.data.Post; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.database.DatabaseException; +import net.pterodactylus.sone.database.PostDatabase; +import net.pterodactylus.util.config.Configuration; +import net.pterodactylus.util.config.ConfigurationException; + +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.collect.Multimap; +import com.google.common.collect.SetMultimap; + +/** + * Memory-based {@link PostDatabase} implementation. + * + * @author David ‘Bombe’ Roden + */ +public class MemoryPostDatabase implements PostDatabase { + + private final MemoryDatabase memoryDatabase; + private final ReadWriteLock readWriteLock; + private final Configuration configuration; + private final Map allPosts = new HashMap(); + private final Multimap sonePosts = create(); + private final SetMultimap likedPostsBySone = create(); + private final SetMultimap postLikingSones = create(); + private final Multimap recipientPosts = create(); + private final Set knownPosts = new HashSet(); + + public MemoryPostDatabase(MemoryDatabase memoryDatabase, ReadWriteLock readWriteLock, Configuration configuration) { + this.memoryDatabase = memoryDatabase; + this.readWriteLock = readWriteLock; + this.configuration = configuration; + } + + @Override + public Function> getPost() { + return new Function>() { + @Override + public Optional apply(String postId) { + return (postId == null) ? Optional.absent() : getPost(postId); + } + }; + } + + @Override + public Optional getPost(String postId) { + readWriteLock.readLock().lock(); + try { + return fromNullable(allPosts.get(postId)); + } finally { + readWriteLock.readLock().unlock(); + } + } + + @Override + public Collection getPosts(String soneId) { + readWriteLock.readLock().lock(); + try { + return new HashSet(sonePosts.get(soneId)); + } finally { + readWriteLock.readLock().unlock(); + } + } + + @Override + public Collection getDirectedPosts(String recipientId) { + readWriteLock.readLock().lock(); + try { + Collection posts = recipientPosts.get(recipientId); + return (posts == null) ? Collections.emptySet() : new HashSet(posts); + } finally { + readWriteLock.readLock().unlock(); + } + } + + /** + * Returns whether the given post is known. + * + * @param post + * The post + * @return {@code true} if the post is known, {@code false} otherwise + */ + @Override + public boolean isPostKnown(Post post) { + readWriteLock.readLock().lock(); + try { + return knownPosts.contains(post.getId()); + } finally { + readWriteLock.readLock().unlock(); + } + } + + /** + * Sets whether the given post is known. + * + * @param post + * The post + */ + @Override + public void setPostKnown(Post post) { + readWriteLock.writeLock().lock(); + try { + knownPosts.add(post.getId()); + } finally { + readWriteLock.writeLock().unlock(); + } + } + + @Override + public void likePost(Post post, Sone localSone) { + readWriteLock.writeLock().lock(); + try { + likedPostsBySone.put(localSone.getId(), post.getId()); + postLikingSones.put(post.getId(), localSone.getId()); + } finally { + readWriteLock.writeLock().unlock(); + } + } + + @Override + public void unlikePost(Post post, Sone localSone) { + readWriteLock.writeLock().lock(); + try { + likedPostsBySone.remove(localSone.getId(), post.getId()); + postLikingSones.remove(post.getId(), localSone.getId()); + } finally { + readWriteLock.writeLock().unlock(); + } + } + + public boolean isLiked(Post post, Sone sone) { + readWriteLock.readLock().lock(); + try { + return likedPostsBySone.containsEntry(sone.getId(), post.getId()); + } finally { + readWriteLock.readLock().unlock(); + } + } + + @Override + public Set getLikes(Post post) { + readWriteLock.readLock().lock(); + try { + return from(postLikingSones.get(post.getId())).transform(memoryDatabase.getSone()).transformAndConcat(MemoryDatabase.unwrap()).toSet(); + } finally { + readWriteLock.readLock().unlock(); + } + } + + @Override + public void storePost(Post post) { + checkNotNull(post, "post must not be null"); + readWriteLock.writeLock().lock(); + try { + allPosts.put(post.getId(), post); + sonePosts.put(post.getSone().getId(), post); + if (post.getRecipientId().isPresent()) { + recipientPosts.put(post.getRecipientId().get(), post); + } + } finally { + readWriteLock.writeLock().unlock(); + } + } + + @Override + public void removePost(Post post) { + checkNotNull(post, "post must not be null"); + readWriteLock.writeLock().lock(); + try { + allPosts.remove(post.getId()); + sonePosts.remove(post.getSone().getId(), post); + if (post.getRecipientId().isPresent()) { + recipientPosts.remove(post.getRecipientId().get(), post); + } + post.getSone().removePost(post); + } finally { + readWriteLock.writeLock().unlock(); + } + } + + @Override + public void storePosts(Sone sone, Collection posts) throws IllegalArgumentException { + checkNotNull(sone, "sone must not be null"); + /* verify that all posts are from the same Sone. */ + for (Post post : posts) { + if (!sone.equals(post.getSone())) { + throw new IllegalArgumentException(String.format("Post from different Sone found: %s", post)); + } + } + + readWriteLock.writeLock().lock(); + try { + /* remove all posts by the Sone. */ + sonePosts.removeAll(sone.getId()); + for (Post post : posts) { + allPosts.remove(post.getId()); + if (post.getRecipientId().isPresent()) { + recipientPosts.remove(post.getRecipientId().get(), post); + } + } + + /* add new posts. */ + sonePosts.putAll(sone.getId(), posts); + for (Post post : posts) { + allPosts.put(post.getId(), post); + if (post.getRecipientId().isPresent()) { + recipientPosts.put(post.getRecipientId().get(), post); + } + } + } finally { + readWriteLock.writeLock().unlock(); + } + } + + @Override + public void removePosts(Sone sone) { + checkNotNull(sone, "sone must not be null"); + readWriteLock.writeLock().lock(); + try { + /* remove all posts by the Sone. */ + sonePosts.removeAll(sone.getId()); + for (Post post : sone.getPosts()) { + allPosts.remove(post.getId()); + if (post.getRecipientId().isPresent()) { + recipientPosts.remove(post.getRecipientId().get(), post); + } + } + } finally { + readWriteLock.writeLock().unlock(); + } + } + + public void start() { + readWriteLock.writeLock().lock(); + try { + int postCounter = 0; + while (true) { + String knownPostId = configuration.getStringValue("KnownPosts/" + postCounter++ + "/ID").getValue(null); + if (knownPostId == null) { + break; + } + knownPosts.add(knownPostId); + } + } finally { + readWriteLock.writeLock().unlock(); + } + } + + public void stop() throws DatabaseException { + save(); + } + + public void save() throws DatabaseException { + readWriteLock.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 { + readWriteLock.readLock().unlock(); + } + } + +} -- 2.7.4