From e6d83975506adffb2bc358aa6dce0fc580b4a183 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 8 Dec 2014 08:07:20 +0100 Subject: [PATCH] optimize opml export (fix #687) --- .../commafeed/backend/opml/OPMLExporter.java | 27 ++-- .../backend/opml/OPMLExporterTest.java | 141 ++++++++++++++++++ 2 files changed, 158 insertions(+), 10 deletions(-) create mode 100644 src/test/java/com/commafeed/backend/opml/OPMLExporterTest.java diff --git a/src/main/java/com/commafeed/backend/opml/OPMLExporter.java b/src/main/java/com/commafeed/backend/opml/OPMLExporter.java index 49e7011f..7d6a22d1 100644 --- a/src/main/java/com/commafeed/backend/opml/OPMLExporter.java +++ b/src/main/java/com/commafeed/backend/opml/OPMLExporter.java @@ -13,6 +13,9 @@ import com.commafeed.backend.dao.FeedSubscriptionDAO; import com.commafeed.backend.model.FeedCategory; import com.commafeed.backend.model.FeedSubscription; import com.commafeed.backend.model.User; +import com.google.common.base.Function; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; import com.rometools.opml.feed.opml.Attribute; import com.rometools.opml.feed.opml.Opml; import com.rometools.opml.feed.opml.Outline; @@ -21,6 +24,14 @@ import com.rometools.opml.feed.opml.Outline; @Singleton public class OPMLExporter { + private static Long ROOT_CATEGORY_ID = new Long(-1); + private static final Function SUBSCRIPTION_TO_CATEGORYID = new Function() { + @Override + public Long apply(FeedSubscription sub) { + return sub.getCategory() == null ? ROOT_CATEGORY_ID : sub.getCategory().getId(); + } + }; + private final FeedCategoryDAO feedCategoryDAO; private final FeedSubscriptionDAO feedSubscriptionDAO; @@ -31,7 +42,7 @@ public class OPMLExporter { opml.setCreated(new Date()); List categories = feedCategoryDAO.findAll(user); - List subscriptions = feedSubscriptionDAO.findAll(user); + Multimap subscriptions = Multimaps.index(feedSubscriptionDAO.findAll(user), SUBSCRIPTION_TO_CATEGORYID); // export root categories for (FeedCategory cat : categories) { @@ -41,17 +52,15 @@ public class OPMLExporter { } // export root subscriptions - for (FeedSubscription sub : subscriptions) { - if (sub.getCategory() == null) { - opml.getOutlines().add(buildSubscriptionOutline(sub)); - } + for (FeedSubscription sub : subscriptions.get(ROOT_CATEGORY_ID)) { + opml.getOutlines().add(buildSubscriptionOutline(sub)); } return opml; } - private Outline buildCategoryOutline(FeedCategory cat, List subscriptions) { + private Outline buildCategoryOutline(FeedCategory cat, Multimap subscriptions) { Outline outline = new Outline(); outline.setText(cat.getName()); outline.setTitle(cat.getName()); @@ -60,10 +69,8 @@ public class OPMLExporter { outline.getChildren().add(buildCategoryOutline(child, subscriptions)); } - for (FeedSubscription sub : subscriptions) { - if (sub.getCategory() != null && sub.getCategory().getId().equals(cat.getId())) { - outline.getChildren().add(buildSubscriptionOutline(sub)); - } + for (FeedSubscription sub : subscriptions.get(cat.getId())) { + outline.getChildren().add(buildSubscriptionOutline(sub)); } return outline; } diff --git a/src/test/java/com/commafeed/backend/opml/OPMLExporterTest.java b/src/test/java/com/commafeed/backend/opml/OPMLExporterTest.java new file mode 100644 index 00000000..873e5d1e --- /dev/null +++ b/src/test/java/com/commafeed/backend/opml/OPMLExporterTest.java @@ -0,0 +1,141 @@ +package com.commafeed.backend.opml; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import com.commafeed.backend.dao.FeedCategoryDAO; +import com.commafeed.backend.dao.FeedSubscriptionDAO; +import com.commafeed.backend.model.Feed; +import com.commafeed.backend.model.FeedCategory; +import com.commafeed.backend.model.FeedSubscription; +import com.commafeed.backend.model.User; +import com.rometools.opml.feed.opml.Opml; +import com.rometools.opml.feed.opml.Outline; + +public class OPMLExporterTest { + + @Mock + private FeedCategoryDAO feedCategoryDAO; + @Mock + private FeedSubscriptionDAO feedSubscriptionDAO; + + private User user = new User(); + + private FeedCategory cat1 = new FeedCategory(); + private FeedCategory cat2 = new FeedCategory(); + + private FeedSubscription rootFeed = newFeedSubscription("rootFeed", "rootFeed.com"); + private FeedSubscription cat1Feed = newFeedSubscription("cat1Feed", "cat1Feed.com"); + private FeedSubscription cat2Feed = newFeedSubscription("cat2Feed", "cat2Feed.com"); + + private List categories = new ArrayList<>(); + private List subscriptions = new ArrayList<>(); + + @Before + public void before_each_test() { + MockitoAnnotations.initMocks(this); + + user.setName("John Doe"); + + cat1.setId(1l); + cat1.setName("cat1"); + cat1.setParent(null); + cat1.setChildren(new HashSet()); + cat1.setSubscriptions(new HashSet()); + + cat2.setId(2l); + cat2.setName("cat2"); + cat2.setParent(cat1); + cat2.setChildren(new HashSet()); + cat2.setSubscriptions(new HashSet()); + + cat1.getChildren().add(cat2); + + rootFeed.setCategory(null); + cat1Feed.setCategory(cat1); + cat2Feed.setCategory(cat2); + + cat1.getSubscriptions().add(cat1Feed); + cat2.getSubscriptions().add(cat2Feed); + + categories.add(cat1); + categories.add(cat2); + + subscriptions.add(rootFeed); + subscriptions.add(cat1Feed); + subscriptions.add(cat2Feed); + } + + private Feed newFeed(String url) { + Feed feed = new Feed(); + feed.setUrl(url); + return feed; + } + + private FeedSubscription newFeedSubscription(String title, String url) { + FeedSubscription feedSubscription = new FeedSubscription(); + feedSubscription.setTitle(title); + feedSubscription.setFeed(newFeed(url)); + return feedSubscription; + } + + @Test + public void generates_OPML_correctly() { + when(feedCategoryDAO.findAll(user)).thenReturn(categories); + when(feedSubscriptionDAO.findAll(user)).thenReturn(subscriptions); + + Opml opml = new OPMLExporter(feedCategoryDAO, feedSubscriptionDAO).export(user); + + List rootOutlines = opml.getOutlines(); + assertEquals(2, rootOutlines.size()); + assertTrue(containsCategory(rootOutlines, "cat1")); + assertTrue(containsFeed(rootOutlines, "rootFeed", "rootFeed.com")); + + Outline cat1Outline = getCategoryOutline(rootOutlines, "cat1"); + List cat1Children = cat1Outline.getChildren(); + assertEquals(2, cat1Children.size()); + assertTrue(containsCategory(cat1Children, "cat2")); + assertTrue(containsFeed(cat1Children, "cat1Feed", "cat1Feed.com")); + + Outline cat2Outline = getCategoryOutline(cat1Children, "cat2"); + List cat2Children = cat2Outline.getChildren(); + assertEquals(1, cat2Children.size()); + assertTrue(containsFeed(cat2Children, "cat2Feed", "cat2Feed.com")); + } + + private boolean containsCategory(List outlines, String category) { + for (Outline o : outlines) + if (!"rss".equals(o.getType())) + if (category.equals(o.getTitle())) + return true; + + return false; + } + + private boolean containsFeed(List outlines, String title, String url) { + for (Outline o : outlines) + if ("rss".equals(o.getType())) + if (title.equals(o.getTitle()) && o.getAttributeValue("xmlUrl").equals(url)) + return true; + + return false; + } + + private Outline getCategoryOutline(List outlines, String title) { + for (Outline o : outlines) + if (o.getTitle().equals(title)) + return o; + + return null; + } +} \ No newline at end of file