diff --git a/persistence-modules/java-mongodb/src/main/java/com/baeldung/tagging/Post.java b/persistence-modules/java-mongodb/src/main/java/com/baeldung/tagging/Post.java new file mode 100644 index 0000000000..739a7e6300 --- /dev/null +++ b/persistence-modules/java-mongodb/src/main/java/com/baeldung/tagging/Post.java @@ -0,0 +1,172 @@ +package com.baeldung.tagging; + +import java.util.List; + +/** + * Model for a blog post. + * + * @author Donato Rimenti + * + */ +public class Post { + + /** + * Title of the post. Must be unique. + */ + private String title; + + /** + * Full article. + */ + private String article; + + /** + * Author of the post. + */ + private String author; + + /** + * Tags of the post. + */ + private List tags; + + /** + * Gets the {@link #title}. + * + * @return the {@link #title} + */ + public String getTitle() { + return title; + } + + /** + * Sets the {@link #title}. + * + * @param title + * the new {@link #title} + */ + public void setTitle(String title) { + this.title = title; + } + + /** + * Gets the {@link #article}. + * + * @return the {@link #article} + */ + public String getArticle() { + return article; + } + + /** + * Sets the {@link #article}. + * + * @param article + * the new {@link #article} + */ + public void setArticle(String article) { + this.article = article; + } + + /** + * Gets the {@link #author}. + * + * @return the {@link #author} + */ + public String getAuthor() { + return author; + } + + /** + * Sets the {@link #author}. + * + * @param author + * the new {@link #author} + */ + public void setAuthor(String author) { + this.author = author; + } + + /** + * Gets the {@link #tags}. + * + * @return the {@link #tags} + */ + public List getTags() { + return tags; + } + + /** + * Sets the {@link #tags}. + * + * @param tags + * the new {@link #tags} + */ + public void setTags(List tags) { + this.tags = tags; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((article == null) ? 0 : article.hashCode()); + result = prime * result + ((author == null) ? 0 : author.hashCode()); + result = prime * result + ((tags == null) ? 0 : tags.hashCode()); + result = prime * result + ((title == null) ? 0 : title.hashCode()); + return result; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Post other = (Post) obj; + if (article == null) { + if (other.article != null) + return false; + } else if (!article.equals(other.article)) + return false; + if (author == null) { + if (other.author != null) + return false; + } else if (!author.equals(other.author)) + return false; + if (tags == null) { + if (other.tags != null) + return false; + } else if (!tags.equals(other.tags)) + return false; + if (title == null) { + if (other.title != null) + return false; + } else if (!title.equals(other.title)) + return false; + return true; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "Post [title=" + title + ", article=" + article + ", author=" + author + ", tags=" + tags + "]"; + } + +} diff --git a/persistence-modules/java-mongodb/src/main/java/com/baeldung/tagging/TagRepository.java b/persistence-modules/java-mongodb/src/main/java/com/baeldung/tagging/TagRepository.java new file mode 100644 index 0000000000..cb65e05f99 --- /dev/null +++ b/persistence-modules/java-mongodb/src/main/java/com/baeldung/tagging/TagRepository.java @@ -0,0 +1,154 @@ +package com.baeldung.tagging; + +import java.io.Closeable; +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +import org.bson.Document; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBCollection; +import com.mongodb.MongoClient; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Updates; +import com.mongodb.client.result.UpdateResult; + +/** + * Repository used to manage tags for a blog post. + * + * @author Donato Rimenti + * + */ +public class TagRepository implements Closeable { + + /** + * Document field which holds the blog tags. + */ + private static final String TAGS_FIELD = "tags"; + + /** + * The post collection. + */ + private MongoCollection collection; + + /** + * The MongoDB client, stored in a field in order to close it later. + */ + private MongoClient mongoClient; + + /** + * Instantiates a new TagRepository by opening the DB connection. + */ + public TagRepository() { + mongoClient = new MongoClient("localhost", 27018); + MongoDatabase database = mongoClient.getDatabase("blog"); + collection = database.getCollection("posts"); + } + + /** + * Returns a list of posts which contains one or more of the tags passed as + * argument. + * + * @param tags + * a list of tags + * @return a list of posts which contains at least one of the tags passed as + * argument + */ + public List postsWithAtLeastOneTag(String... tags) { + FindIterable results = collection.find(Filters.in(TAGS_FIELD, tags)); + return StreamSupport.stream(results.spliterator(), false).map(TagRepository::documentToPost) + .collect(Collectors.toList()); + } + + /** + * Returns a list of posts which contains all the tags passed as argument. + * + * @param tags + * a list of tags + * @return a list of posts which contains all the tags passed as argument + */ + public List postsWithAllTags(String... tags) { + FindIterable results = collection.find(Filters.all(TAGS_FIELD, tags)); + return StreamSupport.stream(results.spliterator(), false).map(TagRepository::documentToPost) + .collect(Collectors.toList()); + } + + /** + * Returns a list of posts which contains none of the tags passed as + * argument. + * + * @param tags + * a list of tags + * @return a list of posts which contains none of the tags passed as + * argument + */ + public List postsWithoutTags(String... tags) { + FindIterable results = collection.find(Filters.nin(TAGS_FIELD, tags)); + return StreamSupport.stream(results.spliterator(), false).map(TagRepository::documentToPost) + .collect(Collectors.toList()); + } + + /** + * Adds a list of tags to the blog post with the given title. + * + * @param title + * the title of the blog post + * @param tags + * a list of tags to add + * @return the outcome of the operation + */ + public boolean addTags(String title, List tags) { + UpdateResult result = collection.updateOne(new BasicDBObject(DBCollection.ID_FIELD_NAME, title), + Updates.addEachToSet(TAGS_FIELD, tags)); + return result.getModifiedCount() == 1; + } + + /** + * Removes a list of tags to the blog post with the given title. + * + * @param title + * the title of the blog post + * @param tags + * a list of tags to remove + * @return the outcome of the operation + */ + public boolean removeTags(String title, List tags) { + UpdateResult result = collection.updateOne(new BasicDBObject(DBCollection.ID_FIELD_NAME, title), + Updates.pullAll(TAGS_FIELD, tags)); + return result.getModifiedCount() == 1; + } + + /** + * Utility method used to map a MongoDB document into a {@link Post}. + * + * @param document + * the document to map + * @return a {@link Post} object equivalent to the document passed as + * argument + */ + @SuppressWarnings("unchecked") + private static Post documentToPost(Document document) { + Post post = new Post(); + post.setTitle(document.getString(DBCollection.ID_FIELD_NAME)); + post.setArticle(document.getString("article")); + post.setAuthor(document.getString("author")); + post.setTags((List) document.get(TAGS_FIELD)); + return post; + } + + /* + * (non-Javadoc) + * + * @see java.io.Closeable#close() + */ + @Override + public void close() throws IOException { + mongoClient.close(); + } + +} diff --git a/persistence-modules/java-mongodb/src/test/java/com/baeldung/tagging/TaggingIntegrationTest.java b/persistence-modules/java-mongodb/src/test/java/com/baeldung/tagging/TaggingIntegrationTest.java new file mode 100644 index 0000000000..2796bc60fb --- /dev/null +++ b/persistence-modules/java-mongodb/src/test/java/com/baeldung/tagging/TaggingIntegrationTest.java @@ -0,0 +1,177 @@ +package com.baeldung.tagging; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.stream.StreamSupport; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +/** + * Test for {@link TagRepository}. + * + * @author Donato Rimenti + * + */ +public class TaggingIntegrationTest { + + /** + * Object to test. + */ + private TagRepository repository; + + /** + * Sets the test up by instantiating the object to test. + */ + @Before + public void setup() { + repository = new TagRepository(); + } + + /** + * Tests {@link TagRepository#postsWithAtLeastOneTag(String...)} with 1 tag. + */ + @Test + public void givenTagRepository_whenPostsWithAtLeastOneTagMongoDB_then3Results() { + List results = repository.postsWithAtLeastOneTag("MongoDB"); + results.forEach(System.out::println); + + Assert.assertEquals(3, results.size()); + results.forEach(post -> { + Assert.assertTrue(post.getTags().contains("MongoDB")); + }); + + } + + /** + * Tests {@link TagRepository#postsWithAtLeastOneTag(String...)} with 2 + * tags. + */ + @Test + public void givenTagRepository_whenPostsWithAtLeastOneTagMongoDBJava8_then4Results() { + List results = repository.postsWithAtLeastOneTag("MongoDB", "Java 8"); + results.forEach(System.out::println); + + Assert.assertEquals(4, results.size()); + results.forEach(post -> { + Assert.assertTrue(post.getTags().contains("MongoDB") || post.getTags().contains("Java 8")); + }); + } + + /** + * Tests {@link TagRepository#postsWithAllTags(String...)} with 1 tag. + */ + @Test + public void givenTagRepository_whenPostsWithAllTagsMongoDB_then3Results() { + List results = repository.postsWithAllTags("MongoDB"); + results.forEach(System.out::println); + + Assert.assertEquals(3, results.size()); + results.forEach(post -> { + Assert.assertTrue(post.getTags().contains("MongoDB")); + }); + } + + /** + * Tests {@link TagRepository#postsWithAllTags(String...)} with 2 tags. + */ + @Test + public void givenTagRepository_whenPostsWithAllTagsMongoDBJava8_then2Results() { + List results = repository.postsWithAllTags("MongoDB", "Java 8"); + results.forEach(System.out::println); + + Assert.assertEquals(2, results.size()); + results.forEach(post -> { + Assert.assertTrue(post.getTags().contains("MongoDB")); + Assert.assertTrue(post.getTags().contains("Java 8")); + }); + } + + /** + * Tests {@link TagRepository#postsWithoutTags(String...)} with 1 tag. + */ + @Test + public void givenTagRepository_whenPostsWithoutTagsMongoDB_then1Result() { + List results = repository.postsWithoutTags("MongoDB"); + results.forEach(System.out::println); + + Assert.assertEquals(1, results.size()); + results.forEach(post -> { + Assert.assertFalse(post.getTags().contains("MongoDB")); + }); + } + + /** + * Tests {@link TagRepository#postsWithoutTags(String...)} with 2 tags. + */ + @Test + public void givenTagRepository_whenPostsWithoutTagsMongoDBJava8_then0Results() { + List results = repository.postsWithoutTags("MongoDB", "Java 8"); + results.forEach(System.out::println); + + Assert.assertEquals(0, results.size()); + results.forEach(post -> { + Assert.assertFalse(post.getTags().contains("MongoDB")); + Assert.assertFalse(post.getTags().contains("Java 8")); + }); + } + + /** + * Tests {@link TagRepository#addTags(String, List)} and + * {@link TagRepository#removeTags(String, List)}. These tests run together + * to keep the database in a consistent state. + */ + @Test + public void givenTagRepository_whenAddingRemovingElements_thenNoDuplicates() { + // Adds one element and checks the result. + boolean result = repository.addTags("Post 1", Arrays.asList("jUnit", "jUnit5")); + Assert.assertTrue(result); + + // We add the same elements again to check that there's no duplication. + result = repository.addTags("Post 1", Arrays.asList("jUnit", "jUnit5")); + Assert.assertFalse(result); + + // Fetches the element back to check if the elements have been added. + List postsAfterAddition = repository.postsWithAllTags("jUnit", "jUnit5"); + Assert.assertEquals(1, postsAfterAddition.size()); + postsAfterAddition.forEach(post -> { + Assert.assertTrue(post.getTags().contains("jUnit")); + Assert.assertTrue(post.getTags().contains("jUnit5")); + }); + + // Checks for duplication. + long countDuplicateTags = StreamSupport.stream(postsAfterAddition.get(0).getTags().spliterator(), false) + .filter(x -> x.equals("jUnit5")).count(); + Assert.assertEquals(1, countDuplicateTags); + + // Tries to remove the tags added. + result = repository.removeTags("Post 1", Arrays.asList("jUnit", "jUnit5")); + Assert.assertTrue(result); + + // We remove the same elements again to check for errors. + result = repository.removeTags("Post 1", Arrays.asList("jUnit", "jUnit5")); + Assert.assertFalse(result); + + // Fetches the element back to check if the elements have been removed. + List postsAfterDeletion = repository.postsWithAllTags("jUnit", "jUnit5"); + Assert.assertEquals(0, postsAfterDeletion.size()); + postsAfterDeletion = repository.postsWithAtLeastOneTag("jUnit"); + Assert.assertEquals(0, postsAfterDeletion.size()); + postsAfterDeletion = repository.postsWithAtLeastOneTag("jUnit5"); + Assert.assertEquals(0, postsAfterDeletion.size()); + } + + /** + * Cleans up the test by deallocating memory. + * + * @throws IOException + */ + @After + public void teardown() throws IOException { + repository.close(); + } + +}