Commits

Julien Hoarau committed f6707dc

Init project

Comments (0)

Files changed (19)

+To avoid future confusion, we recommend that you include a license with your plugin.
+This file is simply a reminder.
+
+For a template license you can have a look at: http://www.opensource.org/licenses/
+
+Atlassian releases most of its modules under a BSD license: http://www.opensource.org/licenses/bsd-license.php
+You have successfully created a plugin using the Confluence plugin archetype!
+
+Here are the SDK commands you'll use immediately:
+
+* atlas-run   -- installs this plugin into Confluence and starts it on http://<machinename>:1990/confluence
+* atlas-debug -- same as atlas-run, but allows a debugger to attach at port 5005
+* atlas-cli   -- after atlas-run or atlas-debug, opens a Maven command line window:
+                 - 'pi' reinstalls the plugin into the running Confluence instance
+* atlas-help  -- prints description for all commands in the SDK
+
+Full documentation is always available at:
+
+https://developer.atlassian.com/display/DOCS/Developing+with+the+Atlassian+Plugin+SDK
+<?xml version="1.0" encoding="UTF-8"?>
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.atlassian.confluence.plugins</groupId>
+    <artifactId>whotofollow</artifactId>
+    <version>1.0-SNAPSHOT</version>
+
+    <organization>
+        <name>Example Company</name>
+        <url>http://www.example.com/</url>
+    </organization>
+
+    <name>whotofollow</name>
+    <description>This is the com.atlassian.confluence.plugins:whotofollow plugin for Atlassian Confluence.</description>
+    <packaging>atlassian-plugin</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.atlassian.confluence</groupId>
+            <artifactId>confluence</artifactId>
+            <version>${confluence.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.atlassian.activeobjects</groupId>
+            <artifactId>activeobjects-plugin</artifactId>
+            <version>${ao.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.atlassian.sal</groupId>
+            <artifactId>sal-api</artifactId>
+            <version>2.4.1</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>10.0.1</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.atlassian.plugins.rest</groupId>
+            <artifactId>atlassian-rest-module</artifactId>
+            <version>2.6.7</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.atlassian.confluence.rest</groupId>
+            <artifactId>confluence-rest-plugin</artifactId>
+            <version>${confluence.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.10</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-core</artifactId>
+            <version>1.3</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.atlassian.confluence.plugin</groupId>
+            <artifactId>func-test</artifactId>
+            <version>2.3</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>net.sourceforge.jwebunit</groupId>
+            <artifactId>jwebunit-htmlunit-plugin</artifactId>
+            <version>2.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>net.sourceforge.nekohtml</groupId>
+            <artifactId>nekohtml</artifactId>
+            <version>1.9.12</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <version>1.9.0</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.atlassian.activeobjects</groupId>
+            <artifactId>activeobjects-test</artifactId>
+            <version>${ao.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>hsqldb</groupId>
+            <artifactId>hsqldb</artifactId>
+            <version>1.8.0.10</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>com.atlassian.maven.plugins</groupId>
+                <artifactId>maven-confluence-plugin</artifactId>
+                <version>${amps.version}</version>
+                <extensions>true</extensions>
+                <configuration>
+                    <productVersion>${confluence.version}</productVersion>
+                    <productDataVersion>${confluence.data.version}</productDataVersion>
+                </configuration>
+            </plugin>
+            <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>1.6</source>
+                    <target>1.6</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <properties>
+        <confluence.version>4.3</confluence.version>
+        <confluence.data.version>4.3</confluence.data.version>
+        <ao.version>0.19.12</ao.version>
+        <amps.version>4.0-rc1</amps.version>
+    </properties>
+
+</project>

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

+package com.atlassian.confluence.plugins.whotofollow;
+
+import java.util.Map;
+import java.util.List;
+
+import com.atlassian.confluence.content.render.xhtml.ConversionContext;
+import com.atlassian.confluence.macro.Macro;
+import com.atlassian.confluence.macro.MacroExecutionException;
+import com.atlassian.confluence.pages.PageManager;
+import com.atlassian.confluence.pages.Page;
+import com.atlassian.confluence.spaces.SpaceManager;
+import com.atlassian.confluence.user.AuthenticatedUserThreadLocal;
+import com.atlassian.user.User;
+import com.opensymphony.util.TextUtils;
+
+/**
+ * This very simple macro shows you the very basic use-case of displaying *something* on the Confluence page where it is used.
+ * Use this example macro to toy around, and then quickly move on to the next example - this macro doesn't
+ * really show you all the fun stuff you can do with Confluence.
+ */
+public class ExampleMacro implements Macro
+{
+
+    // We just have to define the variables and the setters, then Spring injects the correct objects for us to use. Simple and efficient.
+    // You just need to know *what* you want to inject and use.
+
+    private final PageManager pageManager;
+    private final SpaceManager spaceManager;
+
+    public ExampleMacro(PageManager pageManager, SpaceManager spaceManager)
+    {
+        this.pageManager = pageManager;
+        this.spaceManager = spaceManager;
+    }
+
+    /**
+     * This method returns XHTML to be displayed on the page that uses this macro
+     * we just do random stuff here, trying to show how you can access the most basic
+     * managers and model objects. No emphasis is put on beauty of code nor on
+     * doing actually useful things :-)
+     */
+    @Override
+    public String execute(Map<String, String> parameters, String body, ConversionContext context) throws MacroExecutionException
+    {
+        // in this most simple example, we build the result in memory, appending HTML code to it at will.
+        // this is something you absolutely don't want to do once you start writing plugins for real. Refer
+        // to the next example for better ways to render content.
+        StringBuffer result = new StringBuffer();
+
+        // get the currently logged in user and display his name
+        User user = AuthenticatedUserThreadLocal.getUser();
+        if (user != null)
+        {
+            String greeting = "<p>Hello <b>" + TextUtils.htmlEncode(user.getFullName()) + "</b>.</p>";
+            result.append(greeting);
+        }
+
+        //get the pages added in the last 55 days to the DS space ("Demo Space"), and display them
+        List<Page> list = pageManager.getRecentlyAddedPages(55, "DS");
+        result.append("<p>");
+        result.append("Some stats for <b>Demonstration Space</b>:");
+        result.append("<table class=\"confluenceTable\">");
+        result.append("<thead><tr><th class=\"confluenceTh\">Page</th><th class=\"confluenceTh\">Number of children</th></tr></thead>");
+        result.append("<tbody>");
+        for (Page page : list)
+        {
+            int numberOfChildren = page.getChildren().size();
+            String pageWithChildren = "<tr><td class=\"confluenceTd\">" + TextUtils.htmlEncode(page.getTitle()) + "</td><td class=\"confluenceTd\" style=\"text-align:right\">" + numberOfChildren + "</td></tr>";
+            result.append(pageWithChildren);
+        }
+        result.append("</tbody>");
+        result.append("</table>");
+        result.append("</p>");
+
+        // and show the number of all spaces in this installation.
+        String spaces = "<p>Altogether, this installation has <b>" + spaceManager.getAllSpaces().size() + "</b> spaces.</p>";
+        result.append(spaces);
+
+        // this concludes our little demo. Now you should understand the basics of code injection use in Confluence, and how
+        // to get a really simple macro running.
+
+        return result.toString();
+    }
+
+    @Override
+    public BodyType getBodyType()
+    {
+        return BodyType.NONE;
+    }
+
+    @Override
+    public OutputType getOutputType()
+    {
+        return OutputType.BLOCK;
+    }
+
+}

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

+package com.atlassian.confluence.plugins.whotofollow;
+
+import com.atlassian.confluence.follow.FollowManager;
+import com.atlassian.confluence.plugins.whotofollow.data.UserNode;
+import com.atlassian.confluence.plugins.whotofollow.data.services.IUserNodeDaoService;
+import com.atlassian.confluence.user.UserAccessor;
+import com.atlassian.crowd.embedded.api.CrowdService;
+import com.atlassian.crowd.embedded.api.User;
+import com.atlassian.crowd.search.EntityDescriptor;
+import com.atlassian.crowd.search.builder.QueryBuilder;
+import com.atlassian.crowd.search.builder.Restriction;
+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.google.common.base.Function;
+import com.google.common.collect.Lists;
+
+import javax.annotation.Nullable;
+import java.util.List;
+
+public class RecommendationEngine
+{
+    private final CrowdService crowdService;
+    private final UserAccessor userAccessor;
+    private final FollowManager followManager;
+
+    private final IUserNodeDaoService userNodeDaoService;
+
+    public RecommendationEngine(CrowdService crowdService, UserAccessor userAccessor, FollowManager followManager, IUserNodeDaoService userNodeDaoService)
+    {
+        this.crowdService = crowdService;
+        this.userAccessor = userAccessor;
+        this.followManager = followManager;
+        this.userNodeDaoService = userNodeDaoService;
+    }
+
+    public List<User> recommendFollowings(String username)
+    {
+        return recommendFollowings(crowdService.getUser(username));
+    }
+
+    public List<User> recommendFollowings(User currentUser)
+    {
+        com.atlassian.user.User user = userAccessor.getUser(currentUser.getName());
+        List<String> following = followManager.getFollowing(user);
+        List<UserNode> mostPopulars = userNodeDaoService.findMostPopulars(currentUser, following.toArray(new String[following.size()]));
+
+        return Lists.transform(mostPopulars, new Function<UserNode, User>()
+        {
+            @Override
+            public User apply(@Nullable UserNode userNode)
+            {
+                return crowdService.getUser(userNode.getUserName());
+            }
+        });
+    }
+
+    public final void construct()
+    {
+        PropertyRestriction<Boolean> restriction = Restriction.on(UserTermKeys.ACTIVE).containing(true);
+        // TODO: Fix and optimize
+        EntityQuery<String> query = QueryBuilder.queryFor(String.class, EntityDescriptor.user())
+                                              .with(restriction)
+                                              .returningAtMost(Integer.MAX_VALUE);
+
+        Iterable<String> userNames = crowdService.search(query);
+        for (String username : userNames)
+        {
+            com.atlassian.user.User user = userAccessor.getUser(username);
+            List<String> followers = followManager.getFollowers(user);
+            int globalPopularity = followers.size();
+
+            // TODO: Retrieve department and set department popularity?
+            userNodeDaoService.createOrUpdate(username, "", globalPopularity, 0);
+        }
+    }
+}

src/main/java/com/atlassian/confluence/plugins/whotofollow/data/UserNode.java

+package com.atlassian.confluence.plugins.whotofollow.data;
+
+import net.java.ao.Entity;
+import net.java.ao.schema.Indexed;
+import net.java.ao.schema.Unique;
+
+public interface UserNode extends Entity
+{
+    @Indexed
+    @Unique
+    String getUserName();
+    void setUserName(String name);
+
+    String getDepartment();
+    void setDepartment(String department);
+
+    int getGlobalPopularityScore();
+    void setGlobalPopularityScore(int popularityScore);
+
+    int getDepartmentPopularityScore();
+    void setDepartmentPopularityScore(int popularityScore);
+}

src/main/java/com/atlassian/confluence/plugins/whotofollow/data/services/IDaoService.java

+package com.atlassian.confluence.plugins.whotofollow.data.services;
+
+import com.atlassian.activeobjects.tx.Transactional;
+import net.java.ao.Entity;
+
+import java.util.List;
+
+@Transactional
+public interface IDaoService<T extends Entity>
+{
+    T get(int id);
+    List<T> all();
+    void deleteAll();
+}
+

src/main/java/com/atlassian/confluence/plugins/whotofollow/data/services/IUserNodeDaoService.java

+package com.atlassian.confluence.plugins.whotofollow.data.services;
+
+import com.atlassian.confluence.plugins.whotofollow.data.UserNode;
+import com.atlassian.crowd.embedded.api.User;
+
+import java.util.List;
+
+public interface IUserNodeDaoService extends IDaoService<UserNode>
+{
+    UserNode createOrUpdate(String username, String department, int globalPopularityScore, int departmentPopularityScore);
+    List<UserNode> findMostPopulars(User currentUser, String... ignoredUsers);
+}

src/main/java/com/atlassian/confluence/plugins/whotofollow/data/services/impl/BaseDaoService.java

+package com.atlassian.confluence.plugins.whotofollow.data.services.impl;
+
+import com.atlassian.activeobjects.external.ActiveObjects;
+import com.atlassian.confluence.plugins.whotofollow.data.services.IDaoService;
+import net.java.ao.Entity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Arrays;
+import java.util.List;
+
+public abstract class BaseDaoService<T extends Entity> implements IDaoService<T>
+{
+    protected Class<T> clazz = getClazz();
+    protected abstract Class<T> getClazz();
+
+    protected static final Logger logger = LoggerFactory.getLogger(BaseDaoService.class);
+    protected final ActiveObjects ao;
+
+    public BaseDaoService(ActiveObjects ao)
+    {
+        this.ao = ao;
+    }
+
+    public T get(int id)
+    {
+        T[] result = ao.find(clazz, "ID = ?", id);
+        return result.length > 0 ? result[0] : null;
+    }
+
+    public List<T> all()
+    {
+        return Arrays.asList(ao.find(clazz));
+    }
+
+    public void deleteAll()
+    {
+        ao.delete(ao.find(clazz));
+    }
+}

src/main/java/com/atlassian/confluence/plugins/whotofollow/data/services/impl/UserNodeDaoService.java

+package com.atlassian.confluence.plugins.whotofollow.data.services.impl;
+
+import com.atlassian.activeobjects.external.ActiveObjects;
+import com.atlassian.confluence.plugins.whotofollow.data.UserNode;
+import com.atlassian.confluence.plugins.whotofollow.data.services.IUserNodeDaoService;
+import com.atlassian.crowd.embedded.api.User;
+import com.google.common.collect.ImmutableMap;
+import net.java.ao.Query;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class UserNodeDaoService extends BaseDaoService<UserNode> implements IUserNodeDaoService
+{
+    public UserNodeDaoService(ActiveObjects ao)
+    {
+
+        super(ao);
+    }
+
+    @Override
+    protected Class<UserNode> getClazz()
+    {
+        return UserNode.class;
+    }
+
+    @Override
+    public UserNode createOrUpdate(String username, String department, int globalPopularityScore, int departmentPopularityScore)
+    {
+        UserNode[] userNodes = ao.find(getClazz(), "USER_NAME = ?", username);
+        if (userNodes.length == 0)
+        {
+            return create(username, department, globalPopularityScore, departmentPopularityScore);
+        }
+        else if (userNodes.length == 1)
+        {
+            updateUser(userNodes[0], department, globalPopularityScore, departmentPopularityScore);
+            return userNodes[0];
+        }
+        else
+        {
+            throw new IllegalStateException("Found multiple users for username: " + username);
+        }
+
+    }
+
+    private void updateUser(UserNode userNode, String department, int globalPopularityScore, int departmentPopularityScore)
+    {
+        userNode.setDepartment(department);
+        userNode.setGlobalPopularityScore(globalPopularityScore);
+        userNode.setDepartmentPopularityScore(departmentPopularityScore);
+        userNode.save();
+    }
+
+    @Override
+    public List<UserNode> findMostPopulars(User currentUser, String... ignoredUsers)
+    {
+        String[] ignored = new String[ignoredUsers.length + 1];
+        int i = 0;
+        ignored[i++] = currentUser.getName();
+        StringBuilder ignoredUserClause = new StringBuilder("USER_NAME != ?");
+        for (String ignoredUser : ignoredUsers)
+        {
+            ignoredUserClause.append(" AND USER_NAME != ?");
+            ignored[i++] = ignoredUser;
+        }
+
+        UserNode[] popularUsers = ao.find(getClazz(),
+                Query.select()
+                        .where(ignoredUserClause.toString(), ignored)
+                        .order("GLOBAL_POPULARITY_SCORE DESC"));
+        return Arrays.asList(popularUsers);
+    }
+
+    private UserNode create(String username, String department, int globalPopularityScore, int departmentPopularityScore)
+    {
+        UserNode userNode = ao.create(getClazz(),
+                                      ImmutableMap.<String, Object>of("USER_NAME", username,
+                                                                      "DEPARTMENT", department,
+                                                                      "GLOBAL_POPULARITY_SCORE", globalPopularityScore,
+                                                                      "DEPARTMENT_POPULARITY_SCORE", departmentPopularityScore));
+
+        return userNode;
+    }
+}

src/main/java/com/atlassian/confluence/plugins/whotofollow/rest/RecommendationResource.java

+package com.atlassian.confluence.plugins.whotofollow.rest;
+
+import com.atlassian.confluence.plugins.rest.entities.UserEntity;
+import com.atlassian.confluence.plugins.rest.entities.builders.UserEntityBuilder;
+import com.atlassian.confluence.plugins.rest.resources.AbstractResource;
+import com.atlassian.confluence.plugins.whotofollow.RecommendationEngine;
+import com.atlassian.confluence.security.SpacePermissionManager;
+import com.atlassian.confluence.setup.settings.SettingsManager;
+import com.atlassian.confluence.user.UserAccessor;
+import com.atlassian.confluence.util.i18n.I18NBeanFactory;
+import com.atlassian.crowd.embedded.api.User;
+import com.atlassian.plugin.webresource.WebResourceUrlProvider;
+import com.google.common.base.Function;
+import com.google.common.collect.Lists;
+
+import javax.annotation.Nullable;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.util.List;
+
+@Path("recommendations")
+@Produces({MediaType.APPLICATION_JSON})
+public class RecommendationResource extends AbstractResource
+{
+    private final UserEntityBuilder userEntityBuilder;
+
+    private final RecommendationEngine recommendationEngine;
+
+    public RecommendationResource(UserAccessor userAccessor, WebResourceUrlProvider webResourceUrlProvider, SettingsManager settingsManager, I18NBeanFactory i18NBeanFactory, SpacePermissionManager spm, RecommendationEngine recommendationEngine)
+    {
+        super(userAccessor, spm);
+
+        this.recommendationEngine = recommendationEngine;
+
+        this.userEntityBuilder = new UserEntityBuilder(userAccessor, settingsManager, webResourceUrlProvider, i18NBeanFactory);
+
+        recommendationEngine.construct();
+    }
+
+    @GET
+    @Path("{username}")
+    public Response getRecommendation(@PathParam("username") String username)
+    {
+        createRequestContext();
+        List<User> users = recommendationEngine.recommendFollowings(username);
+
+        List<UserEntity> userEntities = Lists.transform(users, new Function<User, UserEntity>()
+        {
+            @Override
+            public UserEntity apply(@Nullable User user)
+            {
+                return userEntityBuilder.build(userAccessor.getUser(user.getName()));
+            }
+        });
+
+        return Response.ok(userEntities).build();
+    }
+
+    @GET
+    @Path("reconstruct")
+    public Response reconstruct()
+    {
+        recommendationEngine.construct();
+
+        return Response.ok().build();
+    }
+}

src/main/java/com/atlassian/confluence/plugins/whotofollow/rest/bean/UserBean.java

+package com.atlassian.confluence.plugins.whotofollow.rest.bean;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+
+@XmlAccessorType(XmlAccessType.FIELD)
+public class UserBean
+{
+//    private final String username;
+
+
+}

src/main/resources/atlassian-plugin.xml

+<atlassian-plugin key="${project.groupId}.${project.artifactId}" name="${project.name}" plugins-version="2">
+    <plugin-info>
+        <description>${project.description}</description>
+        <version>${project.version}</version>
+        <vendor name="${project.organization.name}" url="${project.organization.url}" />
+    </plugin-info>
+
+    <component-import key="activeObjects" interface="com.atlassian.activeobjects.external.ActiveObjects"/>
+    <component-import key="crowd-service" interface="com.atlassian.crowd.embedded.api.CrowdService"/>
+    <component-import key="user-accessor" interface="com.atlassian.confluence.user.UserAccessor"/>
+    <component-import key="follow-manager" interface="com.atlassian.confluence.follow.FollowManager"/>
+    <component-import key="webresource-urlprovider" interface="com.atlassian.plugin.webresource.WebResourceUrlProvider"/>
+    <component-import key="settings-manager" interface="com.atlassian.confluence.setup.settings.SettingsManager"/>
+    <component-import key="i18nbean-factory" interface="com.atlassian.confluence.util.i18n.I18NBeanFactory"/>
+    <component-import key="space-permission-manager" interface="com.atlassian.confluence.security.SpacePermissionManager"/>
+
+
+    <component key="tx-processor" name="Transactional Annotation Processor"
+               class="com.atlassian.activeobjects.external.TransactionalAnnotationProcessor">
+        <decription>Processes @Transactional annotations.</decription>
+    </component>
+
+    <component key="usernode-daoservice" interface="com.atlassian.confluence.plugins.whotofollow.data.services.IUserNodeDaoService"
+               class="com.atlassian.confluence.plugins.whotofollow.data.services.impl.UserNodeDaoService"/>
+
+    <component key="recommendation-engine" class="com.atlassian.confluence.plugins.whotofollow.RecommendationEngine"/>
+
+    <ao key="ao-module">
+        <entity>com.atlassian.confluence.plugins.whotofollow.data.UserNode</entity>
+    </ao>
+
+    <!-- REST -->
+    <rest key="rest-service-resources" path="/whotofollow" version="1.0">
+        <description>Provides the REST resources for WhoToFollow plugin.</description>
+    </rest>
+
+</atlassian-plugin>

src/test/java/com/atlassian/confluence/plugins/whotofollow/ExampleMacroTest.java

+package com.atlassian.confluence.plugins.whotofollow;
+
+import org.junit.Test;
+
+/**
+ * Testing {@link com.atlassian.confluence.plugins.whotofollow.ExampleMacro}
+ */
+public class ExampleMacroTest
+{
+    @Test
+    public void basic()
+    {
+        // add test here...
+    }
+}

src/test/java/com/atlassian/confluence/plugins/whotofollow/RecommendationEngineTest.java

+package com.atlassian.confluence.plugins.whotofollow;
+
+import com.atlassian.confluence.follow.FollowManager;
+import com.atlassian.confluence.plugins.whotofollow.data.UserNode;
+import com.atlassian.confluence.plugins.whotofollow.data.services.IUserNodeDaoService;
+import com.atlassian.confluence.user.UserAccessor;
+import com.atlassian.crowd.embedded.api.CrowdService;
+import com.atlassian.crowd.embedded.api.Query;
+import com.atlassian.crowd.embedded.api.User;
+import com.atlassian.crowd.embedded.impl.ImmutableUser;
+import com.atlassian.user.impl.DefaultUser;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+import java.util.List;
+
+import static junit.framework.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class RecommendationEngineTest
+{
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void constructShouldCreateUserNodes() throws Exception
+    {
+        CrowdService crowdService = mock(CrowdService.class);
+        when(crowdService.search(any(Query.class))).thenReturn(ImmutableList.of("currentuser"));
+
+        UserAccessor userAccessor = mock(UserAccessor.class);
+        FollowManager followManager = mock(FollowManager.class);
+        IUserNodeDaoService userNodeDaoService = mock(IUserNodeDaoService.class);
+
+        createUser(crowdService, userAccessor, "currentuser");
+
+        RecommendationEngine engine = new RecommendationEngine(crowdService, userAccessor, followManager, userNodeDaoService);
+        engine.construct();
+
+        verify(userNodeDaoService).createOrUpdate("currentuser", "", 0, 0);
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void constructShouldUseNumberOfFollowerAsGlobalPopularityScore() throws Exception
+    {
+        CrowdService crowdService = mock(CrowdService.class);
+        when(crowdService.search(any(Query.class))).thenReturn(ImmutableList.of("currentuser"));
+
+        UserAccessor userAccessor = mock(UserAccessor.class);
+        FollowManager followManager = mock(FollowManager.class);
+        when(followManager.getFollowers(any(com.atlassian.user.User.class))).thenReturn(ImmutableList.of("follower1", "follower2", "follower3"));
+
+        IUserNodeDaoService userNodeDaoService = mock(IUserNodeDaoService.class);
+
+        createUser(crowdService, userAccessor, "currentuser");
+
+        RecommendationEngine engine = new RecommendationEngine(crowdService, userAccessor, followManager, userNodeDaoService);
+        engine.construct();
+
+        verify(userNodeDaoService).createOrUpdate("currentuser", "", 3, 0);
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void recommendShouldRecommendNothing() throws Exception
+    {
+        CrowdService crowdService = mock(CrowdService.class);
+        when(crowdService.search(any(Query.class))).thenReturn(ImmutableList.of());
+
+        UserAccessor userAccessor = mock(UserAccessor.class);
+        FollowManager followManager = mock(FollowManager.class);
+        IUserNodeDaoService userNodeDaoService = mock(IUserNodeDaoService.class);
+
+        RecommendationEngine engine = new RecommendationEngine(crowdService, userAccessor, followManager, userNodeDaoService);
+
+        User currentUser = new ImmutableUser(0, "currentuser", "Current User", "current@user.com", true);
+        List<User> recommendedUsers = engine.recommendFollowings(currentUser);
+
+        assertEquals(0, recommendedUsers.size());
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void recommendShouldRecommendMostPopular() throws Exception
+    {
+        CrowdService crowdService = mock(CrowdService.class);
+        UserAccessor userAccessor = mock(UserAccessor.class);
+        FollowManager followManager = mock(FollowManager.class);
+
+        User popularUser = createUser(crowdService, userAccessor, "popularuser");
+        User currentUser = createUser(crowdService, userAccessor, "currentuser");
+
+        IUserNodeDaoService userNodeDaoService = mock(IUserNodeDaoService.class);
+        ImmutableList<UserNode> popularUsers = ImmutableList.of(createMockUserNode("popularuser"));
+        when(userNodeDaoService.findMostPopulars(currentUser)).thenReturn(popularUsers);
+
+        RecommendationEngine engine = new RecommendationEngine(crowdService, userAccessor, followManager, userNodeDaoService);
+
+        List<User> recommendedUsers = engine.recommendFollowings(currentUser);
+
+        assertEquals(popularUser, recommendedUsers.get(0));
+    }
+
+    private User createUser(CrowdService crowdService, UserAccessor userAccessor, String username)
+    {
+        User user = new ImmutableUser(0, username, "", "", true);
+
+        when(crowdService.getUser(username)).thenReturn(user);
+        when(userAccessor.getUser(username)).thenReturn(new DefaultUser(username, "", ""));
+
+        return user;
+    }
+
+    private UserNode createMockUserNode(String username)
+    {
+        UserNode userNode = mock(UserNode.class);
+        when(userNode.getUserName()).thenReturn(username);
+
+        return userNode;
+    }
+}

src/test/java/com/atlassian/confluence/plugins/whotofollow/data/services/impl/AbstractServiceTest.java

+package com.atlassian.confluence.plugins.whotofollow.data.services.impl;
+
+import com.atlassian.activeobjects.external.ActiveObjects;
+import com.atlassian.activeobjects.test.TestActiveObjects;
+import net.java.ao.EntityManager;
+import net.java.ao.test.jdbc.DynamicJdbcConfiguration;
+import net.java.ao.test.jdbc.Jdbc;
+import net.java.ao.test.junit.ActiveObjectsJUnitRunner;
+import org.junit.runner.RunWith;
+
+@RunWith(ActiveObjectsJUnitRunner.class)
+@Jdbc(DynamicJdbcConfiguration.class)
+public abstract class AbstractServiceTest
+{
+    protected EntityManager entityManager;
+
+    protected ActiveObjects createActiveObjects()
+    {
+        return new TestActiveObjects(entityManager);
+    }
+}

src/test/java/com/atlassian/confluence/plugins/whotofollow/data/services/impl/UserNodeDaoServiceTest.java

+package com.atlassian.confluence.plugins.whotofollow.data.services.impl;
+
+import com.atlassian.activeobjects.external.ActiveObjects;
+import com.atlassian.confluence.plugins.whotofollow.data.UserNode;
+import com.atlassian.confluence.plugins.whotofollow.data.services.IUserNodeDaoService;
+import com.atlassian.crowd.embedded.api.User;
+import com.atlassian.crowd.embedded.impl.ImmutableUser;
+import org.junit.Test;
+
+import java.util.List;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+public class UserNodeDaoServiceTest extends AbstractServiceTest
+{
+    @Test
+    public void createOrUpdateShouldCreateIfNotExisting() throws Exception
+    {
+        ActiveObjects ao = createActiveObjects();
+        IUserNodeDaoService userNodeDaoService = new UserNodeDaoService(ao);
+        ao.migrate(UserNode.class);
+
+        UserNode createdUser = userNodeDaoService.createOrUpdate("testuser", "test", 0, 0);
+        entityManager.flushAll();
+
+        UserNode returnedUser = userNodeDaoService.all().get(0);
+        assertThat(returnedUser.getUserName(), is(createdUser.getUserName()));
+    }
+
+    @Test
+    public void findMostPopularShouldReturnMostPopularUsersOrderByGlobalPopularity() throws Exception
+    {
+        ActiveObjects ao = createActiveObjects();
+        IUserNodeDaoService userNodeDaoService = new UserNodeDaoService(ao);
+        ao.migrate(UserNode.class);
+
+        userNodeDaoService.createOrUpdate("current_user", "test", 0, 0);
+        userNodeDaoService.createOrUpdate("least", "test", 1, 0);
+        userNodeDaoService.createOrUpdate("medium", "test", 2, 0);
+        userNodeDaoService.createOrUpdate("most", "test", 3, 0);
+        entityManager.flushAll();
+
+        User currentUser = new ImmutableUser(0, "current_user", "Current User", "current@user.com", true);
+        List<UserNode> mostPopulars = userNodeDaoService.findMostPopulars(currentUser);
+        assertTrue(mostPopulars.get(0).getGlobalPopularityScore() > mostPopulars.get(1).getGlobalPopularityScore() &&
+                   mostPopulars.get(1).getGlobalPopularityScore() > mostPopulars.get(2).getGlobalPopularityScore());
+    }
+
+    @Test
+    public void findMostPopularShouldNotReturnCurrentUser() throws Exception
+    {
+        ActiveObjects ao = createActiveObjects();
+        IUserNodeDaoService userNodeDaoService = new UserNodeDaoService(ao);
+        ao.migrate(UserNode.class);
+
+        userNodeDaoService.createOrUpdate("current_user", "test", 0, 0);
+        entityManager.flushAll();
+
+        User currentUser = new ImmutableUser(0, "current_user", "Current User", "current@user.com", true);
+        List<UserNode> mostPopulars = userNodeDaoService.findMostPopulars(currentUser);
+        assertThat(mostPopulars.size(), is(0));
+    }
+
+    @Test
+    public void findMostPopularShouldNotReturnIgnoredUsers() throws Exception
+    {
+        ActiveObjects ao = createActiveObjects();
+        IUserNodeDaoService userNodeDaoService = new UserNodeDaoService(ao);
+        ao.migrate(UserNode.class);
+
+        userNodeDaoService.createOrUpdate("current_user", "test", 0, 0);
+        userNodeDaoService.createOrUpdate("least", "test", 1, 0);
+        userNodeDaoService.createOrUpdate("medium", "test", 2, 0);
+        userNodeDaoService.createOrUpdate("most", "test", 3, 0);
+        entityManager.flushAll();
+
+        User currentUser = new ImmutableUser(0, "current_user", "Current User", "current@user.com", true);
+        List<UserNode> mostPopulars = userNodeDaoService.findMostPopulars(currentUser, "most");
+        assertThat(mostPopulars.get(0).getUserName(), is("medium"));
+    }
+}

src/test/java/it/AbstractIntegrationTestCase.java

+package it;
+
+import com.atlassian.confluence.plugin.functest.AbstractConfluencePluginWebTestCase;
+import com.atlassian.confluence.plugin.functest.JWebUnitConfluenceWebTester;
+import com.atlassian.confluence.plugin.functest.TesterConfiguration;
+import junit.framework.Assert;
+
+import java.io.IOException;
+import java.util.Properties;
+
+public class AbstractIntegrationTestCase extends AbstractConfluencePluginWebTestCase
+{
+    @Override
+    protected JWebUnitConfluenceWebTester createConfluenceWebTester()
+    {
+        Properties props = new Properties();
+        props.put("confluence.webapp.protocol", "http");
+        props.put("confluence.webapp.host", "localhost");
+
+        // this is deceiving: the func test library checks for the system properties
+        // *before* checking in this properties file for these values, so these
+        // properties are technically ignored
+        props.put("confluence.webapp.port", Integer.parseInt(System.getProperty("http.port")));
+        props.put("confluence.webapp.context.path", System.getProperty("context.path"));
+
+        props.put("confluence.auth.admin.username", "admin");
+        props.put("confluence.auth.admin.password", "admin");
+
+        TesterConfiguration conf;
+        try
+        {
+            conf = new TesterConfiguration(props);
+        }
+        catch (IOException ioe)
+        {
+            Assert.fail("Unable to create tester: " + ioe.getMessage());
+            return null;
+        }
+
+        JWebUnitConfluenceWebTester tester = new JWebUnitConfluenceWebTester(conf);
+
+        tester.getTestContext().setBaseUrl(tester.getBaseUrl());
+        tester.setScriptingEnabled(false);
+
+        return tester;
+    }
+}

src/test/java/it/IntegrationTestMyPlugin.java

+package it;
+
+public class IntegrationTestMyPlugin extends AbstractIntegrationTestCase
+{
+	public void testSomething()
+	{
+        gotoPage("");
+        assertTextPresent("Welcome");
+	}
+}