Commits

Julien Hoarau committed a85ead7

Reuse PopularContentQueries content to retrieve popular content instead of retrieving all pages.

  • Participants
  • Parent commits 08261db

Comments (0)

Files changed (10)

             <scope>provided</scope>
         </dependency>
         <dependency>
+            <groupId>com.atlassian.confluence.plugins</groupId>
+            <artifactId>confluence-edge-index</artifactId>
+            <version>${confluence.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>net.sf.trove4j</groupId>
             <artifactId>trove4j</artifactId>
             <version>3.0.3</version>

src/main/java/com/atlassian/confluence/plugins/whotofollow/PluginInitializer.java

+package com.atlassian.confluence.plugins.whotofollow;
+
+import com.atlassian.confluence.plugins.whotofollow.engine.AggregateRecommendationEngine;
+import com.atlassian.confluence.plugins.whotofollow.engine.InitializeEngineJob;
+import com.atlassian.hibernate.PluginHibernateSessionFactory;
+import com.atlassian.sal.api.lifecycle.LifecycleAware;
+import com.atlassian.sal.api.scheduling.PluginScheduler;
+import com.atlassian.sal.api.transaction.TransactionTemplate;
+import com.google.common.collect.ImmutableMap;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Date;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class PluginInitializer implements LifecycleAware
+{
+    private static final Logger logger = LoggerFactory.getLogger(PluginInitializer.class);
+
+    public static String SCHEDULER_KEY = "SCHEDULER_KEY";
+    public static String ENGINE_KEY = "ENGINE_KEY";
+    public static String SESSION_FACTORY_KEY = "SESSION_FACTORY_KEY";
+    public static String TRANSACTION_TEMPLATE_KEY = "TRANSACTION_TEMPLATE_KEY";
+    public static final String JOB_SUFFIX = ":job";
+
+    private final PluginScheduler pluginScheduler;
+    private final PluginHibernateSessionFactory sessionFactory;
+    private final TransactionTemplate transactionTemplate;
+
+    private final AggregateRecommendationEngine aggregateRecommendationEngine;
+
+    private AtomicBoolean initialized = new AtomicBoolean(false);
+
+    public PluginInitializer(final PluginScheduler pluginScheduler, final PluginHibernateSessionFactory sessionFactory, final TransactionTemplate transactionTemplate, final AggregateRecommendationEngine aggregateRecommendationEngine)
+    {
+        this.pluginScheduler = pluginScheduler;
+        this.sessionFactory = sessionFactory;
+        this.transactionTemplate = transactionTemplate;
+        this.aggregateRecommendationEngine = aggregateRecommendationEngine;
+    }
+
+    @Override
+    public void onStart()
+    {
+        if (initialized.compareAndSet(false, true))
+        {
+            Class<InitializeEngineJob> job = InitializeEngineJob.class;
+            pluginScheduler.scheduleJob(job.getName() + JOB_SUFFIX,
+                                        job,
+                                        ImmutableMap.of(ENGINE_KEY, aggregateRecommendationEngine,
+                                                        SCHEDULER_KEY, pluginScheduler,
+                                                        SESSION_FACTORY_KEY, sessionFactory,
+                                                        TRANSACTION_TEMPLATE_KEY, transactionTemplate),
+                                        new Date(),
+                                        Long.MAX_VALUE);
+        }
+    }
+}

src/main/java/com/atlassian/confluence/plugins/whotofollow/engine/AbstractRecommendationEngine.java

 
         return recommendFollowings(user);
     }
+
+    @Override
+    public void init()
+    {
+        //
+    }
 }

src/main/java/com/atlassian/confluence/plugins/whotofollow/engine/AggregateRecommendationEngine.java

 
         return users;
     }
+
+    @Override
+    public void init()
+    {
+        popularityRecommendationEngine.init();
+        likeRecommendationEngine.init();
+    }
 }

src/main/java/com/atlassian/confluence/plugins/whotofollow/engine/IRecommendationEngine.java

     Collection<User> recommendFollowings(String username);
 
     Collection<User> recommendFollowings(User currentUser);
+
+    void init();
 }

src/main/java/com/atlassian/confluence/plugins/whotofollow/engine/InitializeEngineJob.java

+package com.atlassian.confluence.plugins.whotofollow.engine;
+
+import com.atlassian.confluence.plugins.whotofollow.PluginInitializer;
+import com.atlassian.sal.api.scheduling.PluginJob;
+import com.atlassian.sal.api.scheduling.PluginScheduler;
+import com.atlassian.sal.api.transaction.TransactionCallback;
+import com.atlassian.sal.api.transaction.TransactionTemplate;
+import net.sf.hibernate.SessionFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+
+public class InitializeEngineJob implements PluginJob
+{
+    private static final Logger logger = LoggerFactory.getLogger(InitializeEngineJob.class);
+    private static final String JOB_NAME = InitializeEngineJob.class.getName() + PluginInitializer.JOB_SUFFIX;
+
+    @Override
+    public void execute(final Map<String, Object> params)
+    {
+        logger.info("Execute job <{}>.", JOB_NAME);
+        final IRecommendationEngine engine = (IRecommendationEngine) params.get(PluginInitializer.ENGINE_KEY);
+
+        TransactionTemplate transactionTemplate = (TransactionTemplate) params.get(PluginInitializer.TRANSACTION_TEMPLATE_KEY);
+        transactionTemplate.execute(new TransactionCallback<Object>()
+        {
+            @Override
+            public Object doInTransaction()
+            {
+                engine.init();
+                return null;  //To change body of implemented methods use File | Settings | File Templates.
+            }
+        });
+
+        logger.info("Job <{}> executed.", JOB_NAME);
+        unscheduleJob(params);
+    }
+
+    private void unscheduleJob(final Map<String, Object> params)
+    {
+        PluginScheduler pluginScheduler = (PluginScheduler) params.get(PluginInitializer.SCHEDULER_KEY);
+        pluginScheduler.unscheduleJob(JOB_NAME);
+
+        logger.info("Job <{}> unscheduled.", JOB_NAME);
+    }
+}

src/main/java/com/atlassian/confluence/plugins/whotofollow/engine/LikeRecommendationEngine.java

 package com.atlassian.confluence.plugins.whotofollow.engine;
 
+import com.atlassian.confluence.core.ContentEntityObject;
 import com.atlassian.confluence.event.events.like.LikeEvent;
 import com.atlassian.confluence.event.events.like.LikeRemovedEvent;
 import com.atlassian.confluence.follow.FollowManager;
 import com.atlassian.confluence.like.Like;
 import com.atlassian.confluence.like.LikeManager;
 import com.atlassian.confluence.pages.Page;
-import com.atlassian.confluence.pages.PageManager;
+import com.atlassian.confluence.plugins.edgeindex.PopularContentQueries;
+import com.atlassian.confluence.plugins.edgeindex.ScoreConfig;
 import com.atlassian.confluence.plugins.whotofollow.data.UserLike;
 import com.atlassian.confluence.plugins.whotofollow.data.services.IUserLikeDaoService;
 import com.atlassian.confluence.user.UserAccessor;
 import com.atlassian.crowd.embedded.api.User;
 import com.atlassian.event.api.EventListener;
 import com.atlassian.event.api.EventPublisher;
-import com.atlassian.sal.api.lifecycle.LifecycleAware;
 import com.google.common.base.Function;
 import com.google.common.collect.Lists;
 import gnu.trove.iterator.TObjectIntIterator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.TimeUnit;
 
-public class LikeRecommendationEngine extends AbstractRecommendationEngine implements InitializingBean, DisposableBean, LifecycleAware
+public class LikeRecommendationEngine extends AbstractRecommendationEngine implements InitializingBean, DisposableBean
 {
     public static final int SCORE_MIN_THRESHOLD = 0;
 
-    private final PageManager pageManager;
+    private final PopularContentQueries popularContentQueries;
     private final LikeManager likeManager;
     private final EventPublisher eventPublisher;
 
     private final IUserLikeDaoService userLikeDaoService;
 
-    public LikeRecommendationEngine(final UserAccessor userAccessor, final FollowManager followManager, final CrowdService crowdService, final PageManager pageManager, final LikeManager likeManager, final EventPublisher eventPublisher, final IUserLikeDaoService userLikeDaoService)
+    public LikeRecommendationEngine(final UserAccessor userAccessor, final FollowManager followManager, final CrowdService crowdService, final PopularContentQueries popularContentQueries, final LikeManager likeManager, final EventPublisher eventPublisher, final IUserLikeDaoService userLikeDaoService)
     {
         super(userAccessor, followManager, crowdService);
-        this.pageManager = pageManager;
+        this.popularContentQueries = popularContentQueries;
         this.likeManager = likeManager;
         this.eventPublisher = eventPublisher;
         this.userLikeDaoService = userLikeDaoService;
 
         Calendar cal = Calendar.getInstance();
         cal.add(Calendar.MONTH, -6);
-        List<Page> recentPages = pageManager.getPagesCreatedOrUpdatedSinceDate(cal.getTime());
-        for (Page page : recentPages)
+        ScoreConfig scoreConfig = new ScoreConfig();
+        scoreConfig.setTimeDecayBase(1f);
+        scoreConfig.setFolloweeEdge(1f);
+        scoreConfig.setCommentCreateEdge(0f);
+        List<ContentEntityObject> mostPopular = popularContentQueries.getMostPopular(200, 30, TimeUnit.DAYS, scoreConfig);
+        for (ContentEntityObject popularContent : mostPopular)
         {
-            String creatorName = page.getCreatorName();
-            List<Like> likes = likeManager.getLikes(page);
+            String creatorName = popularContent.getCreatorName();
+
+            List<Like> likes = likeManager.getLikes(popularContent);
             for (Like like : likes)
             {
                 if (!like.getUsername().equals(creatorName))
     }
 
     @Override
-    public void onStart()
+    public void init()
     {
         construct();
     }

src/main/java/com/atlassian/confluence/plugins/whotofollow/engine/PopularityRecommendationEngine.java

 import com.atlassian.crowd.search.query.entity.EntityQuery;
 import com.atlassian.crowd.search.query.entity.restriction.PropertyRestriction;
 import com.atlassian.crowd.search.query.entity.restriction.constants.UserTermKeys;
-import com.atlassian.sal.api.lifecycle.LifecycleAware;
 import com.google.common.base.Function;
 import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
 import java.util.Collection;
 import java.util.List;
 
-public class PopularityRecommendationEngine extends AbstractRecommendationEngine implements LifecycleAware
+public class PopularityRecommendationEngine extends AbstractRecommendationEngine
 {
 	private final UserDetailsManager userDetailsManager;
 
     }
 
     @Override
-    public void onStart()
+    public void init()
     {
         construct();
     }

src/main/resources/atlassian-plugin.xml

     <component-import key="web-resource-manager" interface="com.atlassian.plugin.webresource.WebResourceManager"/>
     <component-import key="user-details-manager" interface="com.atlassian.confluence.user.UserDetailsManager"/>
     <component-import key="event-publisher" interface="com.atlassian.event.api.EventPublisher"/>
+    <component-import key="popular-content-queries" interface="com.atlassian.confluence.plugins.edgeindex.PopularContentQueries"/>
+    <component-import key="plugin-scheduler" interface="com.atlassian.sal.api.scheduling.PluginScheduler"/>
+    <component-import key="session-factory" interface="com.atlassian.hibernate.PluginHibernateSessionFactory"/>
+    <component-import key="transaction-template" interface="com.atlassian.sal.api.transaction.TransactionTemplate"/>
 
     <component key="tx-processor" name="Transactional Annotation Processor"
                class="com.atlassian.activeobjects.external.TransactionalAnnotationProcessor">
                class="com.atlassian.confluence.plugins.whotofollow.data.services.impl.UserLikeDaoService"/>
 
     <component key="aggregate-recommendation-engine" class="com.atlassian.confluence.plugins.whotofollow.engine.AggregateRecommendationEngine"/>
-    <component key="recommendation-engine" class="com.atlassian.confluence.plugins.whotofollow.engine.PopularityRecommendationEngine">
-        <interface>com.atlassian.sal.api.lifecycle.LifecycleAware</interface>
-    </component>
-    <component key="like-recommendation-engine" class="com.atlassian.confluence.plugins.whotofollow.engine.LikeRecommendationEngine">
+    <component key="recommendation-engine" class="com.atlassian.confluence.plugins.whotofollow.engine.PopularityRecommendationEngine"/>
+    <component key="like-recommendation-engine" class="com.atlassian.confluence.plugins.whotofollow.engine.LikeRecommendationEngine" />
+
+    <component key="plugin-initializer" class="com.atlassian.confluence.plugins.whotofollow.PluginInitializer" public="true">
         <interface>com.atlassian.sal.api.lifecycle.LifecycleAware</interface>
     </component>
 
-    <!--<listener key="like-event-listener" class="com.atlassian.confluence.plugins.whotofollow.engine.LikeRecommendationEngine">-->
-        <!--<description>Listens for like events and updates the likes recommendations.</description>-->
-    <!--</listener>-->
-
     <ao key="ao-module">
         <entity>com.atlassian.confluence.plugins.whotofollow.data.UserNode</entity>
         <entity>com.atlassian.confluence.plugins.whotofollow.data.UserLike</entity>

src/main/resources/includes/js/recommendations-macro.js

-AJS.toInit(function($) {
-
-    function setUp() {
-        var contextPath = Confluence.getContextPath();
-
-        if (typeof AJS.followCallbacks == "undefined") AJS.followCallbacks = [];
-        AJS.followCallbacks.push(function(user) {
-            var $userLink = $("[data-username=" + user + "]");
-            if ($userLink.length) {
-                updateRecommendations($userLink);
-            }
-
-            var $userBlock = $("#recommendation-" + user);
-            if ($userBlock.length) {
-                replaceFollowedByUnfollowedRecommendation($userBlock);
-            }
-        });
-
-        function setupLink() {
-            if ($(".recommendations-macro.big").length) {
-                var $followLinks = $(".follow");
-                $followLinks.click(function(e) {
-                    var $this = $(this);
-
-                    var username = $this.attr("data-username");
-                    var url = $this.hasClass("unfollow") ? "/unfollowuser.action" : "/followuser.action";
-
-                    $this.removeClass("follow").addClass("unfollow").text("Unfollow");
-                    AJS.safe.post(contextPath + url + "?username=" + encodeURIComponent(username) + "&mode=blank", {}, function() {
-
-                        updateRecommendations($this);
-                    });
-
-                });
-            }
-        }
-
-        setupLink();
-
-        function updateRecommendations($recommendationLink) {
-            var $recommendation = $recommendationLink.parents(".recommendation");
-
-            replaceFollowedByUnfollowedRecommendation($recommendation);
-        }
-
-        function replaceFollowedByUnfollowedRecommendation($recommendation) {
-            $recommendation.fadeOut(1000, function () {
-                var $listElement = $recommendation.parent();
-                var $listElements = $listElement.parent();
-
-                var $hiddenElements = $listElements.children(".hidden:first");
-                $listElement.remove();
-                $hiddenElements.removeClass("hidden");
-            });
-        }
-
-        $(window).bind("hover-user.open", function() {
-            setupLink();
-        });
-    }
-    setUp();
-
-    $("#network-pane").bind("recommendations-ready", function(event) {
-        setUp();
-    });
-});
+var WTF = WTF || {};
+
+(function() {
+
+    WTF.setUp = function() {
+        var contextPath = Confluence.getContextPath();
+
+        if (typeof AJS.followCallbacks == "undefined") AJS.followCallbacks = [];
+        AJS.followCallbacks.push(function(user) {
+            var $userLink = $("[data-username=" + user + "]");
+            if ($userLink.length) {
+                updateRecommendations($userLink);
+            }
+
+            var $userBlock = $("#recommendation-" + user);
+            if ($userBlock.length) {
+                replaceFollowedByUnfollowedRecommendation($userBlock);
+            }
+        });
+
+        function setupLink() {
+            if ($(".recommendations-macro.big").length) {
+                var $followLinks = $(".follow");
+                $followLinks.click(function(e) {
+                    var $this = $(this);
+
+                    var username = $this.attr("data-username");
+                    var url = $this.hasClass("unfollow") ? "/unfollowuser.action" : "/followuser.action";
+
+                    $this.removeClass("follow").addClass("unfollow").text("Unfollow");
+                    AJS.safe.post(contextPath + url + "?username=" + encodeURIComponent(username) + "&mode=blank", {}, function() {
+
+                        updateRecommendations($this);
+                    });
+
+                });
+            }
+        }
+
+        setupLink();
+
+        function updateRecommendations($recommendationLink) {
+            var $recommendation = $recommendationLink.parents(".recommendation");
+
+            replaceFollowedByUnfollowedRecommendation($recommendation);
+        }
+
+        function replaceFollowedByUnfollowedRecommendation($recommendation) {
+            $recommendation.fadeOut(1000, function () {
+                var $listElement = $recommendation.parent();
+                var $listElements = $listElement.parent();
+
+                var $hiddenElements = $listElements.children(".hidden:first");
+                $listElement.remove();
+                $hiddenElements.removeClass("hidden");
+            });
+        }
+
+        $(window).bind("hover-user.open", function() {
+            setupLink();
+        });
+    }
+
+}());
+
+AJS.toInit(function($) {
+
+    var $networkPane = $("#network-pane");
+    if ($networkPane.length) {
+        $networkPane.bind("recommendations-ready", function(event) {
+            WTF.setUp();
+        });
+    } else {
+        WTF.setUp();
+    }
+
+});