]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6475 Add WS to list members of a group
authorJean-Baptiste Lievremont <jean-baptiste.lievremont@sonarsource.com>
Wed, 27 May 2015 13:53:44 +0000 (15:53 +0200)
committerJean-Baptiste Lievremont <jean-baptiste.lievremont@sonarsource.com>
Mon, 1 Jun 2015 12:14:33 +0000 (14:14 +0200)
18 files changed:
server/sonar-server/src/main/java/org/sonar/server/usergroups/ws/UserGroupsModule.java
server/sonar-server/src/main/java/org/sonar/server/usergroups/ws/UsersAction.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/usergroups/ws/UsersActionTest.java [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/all.json [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/all_ada.json [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/all_page1.json [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/all_page2.json [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/all_users.json [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/deselected.json [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/empty.json [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/selected.json [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/user/GroupMembershipDao.java
sonar-core/src/main/java/org/sonar/core/user/GroupMembershipMapper.java
sonar-core/src/main/java/org/sonar/core/user/UserMembershipDto.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/user/UserMembershipQuery.java [new file with mode: 0644]
sonar-core/src/main/resources/org/sonar/core/user/GroupMembershipMapper.xml
sonar-core/src/test/java/org/sonar/core/user/GroupMembershipDaoTest.java
sonar-core/src/test/resources/org/sonar/core/user/GroupMembershipDaoTest/shared_plus_empty_group.xml

index 8c2406e1c911c5f5c789f4ea8f9492fcf29001a1..f02fcaca448fa7bb55cbcdca9772e8a8948270c3 100644 (file)
@@ -31,7 +31,8 @@ public class UserGroupsModule extends Module {
       SearchAction.class,
       CreateAction.class,
       DeleteAction.class,
-      UpdateAction.class);
+      UpdateAction.class,
+      UsersAction.class);
   }
 
 }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/usergroups/ws/UsersAction.java b/server/sonar-server/src/main/java/org/sonar/server/usergroups/ws/UsersAction.java
new file mode 100644 (file)
index 0000000..c6c5d18
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.usergroups.ws;
+
+import java.util.List;
+import javax.annotation.Nullable;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService.NewAction;
+import org.sonar.api.server.ws.WebService.NewController;
+import org.sonar.api.server.ws.WebService.Param;
+import org.sonar.api.utils.Paging;
+import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.core.user.GroupDto;
+import org.sonar.core.user.GroupMembershipQuery;
+import org.sonar.core.user.UserMembershipDto;
+import org.sonar.core.user.UserMembershipQuery;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.exceptions.NotFoundException;
+
+public class UsersAction implements UserGroupsWsAction {
+
+  private static final String PARAM_ID = "id";
+  private static final String PARAM_SELECTED = "selected";
+
+  private static final String SELECTION_ALL = "all";
+  private static final String SELECTION_SELECTED = "selected";
+  private static final String SELECTION_DESELECTED = "deselected";
+
+  private final DbClient dbClient;
+
+  public UsersAction(DbClient dbClient) {
+    this.dbClient = dbClient;
+  }
+
+  @Override
+  public void define(NewController context) {
+    NewAction action = context.createAction("users")
+      .setDescription("List the members of a group.")
+      .setHandler(this)
+      .setResponseExample(getClass().getResource("example-users.json"))
+      .setSince("5.2");
+
+    action.createParam(PARAM_ID)
+      .setDescription("A group ID")
+      .setExampleValue("42")
+      .setRequired(true);
+
+    action.createParam(PARAM_SELECTED)
+      .setDescription("If specified, only show users who belong to this group (selected) or not (deselected).")
+      .setPossibleValues(SELECTION_SELECTED, SELECTION_DESELECTED, SELECTION_ALL)
+      .setDefaultValue(SELECTION_ALL);
+
+    action.createParam(Param.TEXT_QUERY)
+      .setDescription("If specified, only show users whose name or login contains the query.")
+      .setExampleValue("freddy");
+
+    action.addPagingParams(25);
+  }
+
+  @Override
+  public void handle(Request request, Response response) throws Exception {
+    Long groupId = request.mandatoryParamAsLong(PARAM_ID);
+    int pageSize = request.mandatoryParamAsInt(Param.PAGE_SIZE);
+    int page = request.mandatoryParamAsInt(Param.PAGE);
+    String queryString = request.param(Param.TEXT_QUERY);
+    String selected = request.param(PARAM_SELECTED);
+
+    UserMembershipQuery query = UserMembershipQuery.builder()
+      .groupId(groupId)
+      .memberSearch(queryString)
+      .membership(getMembership(selected))
+      .pageIndex(page)
+      .pageSize(pageSize)
+      .build();
+
+    DbSession session = dbClient.openSession(false);
+    try {
+      GroupDto group = dbClient.groupDao().selectById(session, groupId);
+      if (group == null) {
+        throw new NotFoundException(String.format("Unable to find a group with ID '%d'", groupId));
+      }
+      int total = dbClient.groupMembershipDao().countMembers(session, query);
+      Paging paging = Paging.create(pageSize, page, total);
+      List<UserMembershipDto> users = dbClient.groupMembershipDao().selectMembers(session, query, paging.offset(), pageSize);
+
+      JsonWriter json = response.newJsonWriter().beginObject();
+      writeMembers(json, users);
+      writePaging(json, paging);
+      json.endObject().close();
+    } finally {
+      session.close();
+    }
+  }
+
+  private void writeMembers(JsonWriter json, List<UserMembershipDto> users) {
+    json.name("users").beginArray();
+    for (UserMembershipDto user : users) {
+      json.beginObject()
+        .prop("login", user.getLogin())
+        .prop("name", user.getName())
+        .prop("selected", user.getGroupId() != null)
+        .endObject();
+    }
+    json.endArray();
+  }
+
+  private void writePaging(JsonWriter json, Paging paging) {
+    json.prop("p", paging.pageIndex())
+      .prop("ps", paging.pageSize())
+      .prop("total", paging.total());
+  }
+
+  private String getMembership(@Nullable String selected) {
+    String membership = GroupMembershipQuery.ANY;
+    if (SELECTION_SELECTED.equals(selected)) {
+      membership = GroupMembershipQuery.IN;
+    } else if (SELECTION_DESELECTED.equals(selected)) {
+      membership = GroupMembershipQuery.OUT;
+    }
+    return membership;
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/usergroups/ws/UsersActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/usergroups/ws/UsersActionTest.java
new file mode 100644 (file)
index 0000000..1540f03
--- /dev/null
@@ -0,0 +1,197 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.usergroups.ws;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.utils.System2;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.core.persistence.DbTester;
+import org.sonar.core.user.GroupDto;
+import org.sonar.core.user.GroupMembershipDao;
+import org.sonar.core.user.UserDto;
+import org.sonar.core.user.UserGroupDto;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.user.db.GroupDao;
+import org.sonar.server.user.db.UserDao;
+import org.sonar.server.user.db.UserGroupDao;
+import org.sonar.server.ws.WsTester;
+
+public class UsersActionTest {
+
+  @ClassRule
+  public static final DbTester dbTester = new DbTester();
+
+  WebService.Controller controller;
+
+  WsTester tester;
+
+  DbClient dbClient;
+
+  DbSession session;
+
+  @Before
+  public void setUp() {
+    dbTester.truncateTables();
+
+    System2 system2 = new System2();
+    UserDao userDao = new UserDao(dbTester.myBatis(), system2);
+    GroupDao groupDao = new GroupDao(system2);
+    UserGroupDao userGroupDao = new UserGroupDao();
+    GroupMembershipDao groupMembershipDao = new GroupMembershipDao(dbTester.myBatis());
+
+    dbClient = new DbClient(dbTester.database(), dbTester.myBatis(), userDao, groupDao, userGroupDao, groupMembershipDao);
+    session = dbClient.openSession(false);
+    session.commit();
+
+    tester = new WsTester(new UserGroupsWs(new UsersAction(dbClient)));
+    controller = tester.controller("api/users");
+
+  }
+
+  @After
+  public void tearDown() {
+    session.close();
+  }
+
+  @Test(expected = NotFoundException.class)
+  public void fail_on_unknown_user() throws Exception {
+    tester.newGetRequest("api/usergroups", "users")
+      .setParam("id", "42")
+      .setParam("login", "john").execute();
+  }
+
+  @Test
+  public void empty_users() throws Exception {
+    GroupDto group = createGroup();
+    session.commit();
+
+    tester.newGetRequest("api/usergroups", "users")
+      .setParam("login", "john")
+      .setParam("id", group.getId().toString())
+      .execute()
+      .assertJson(getClass(), "empty.json");
+  }
+
+  @Test
+  public void all_users() throws Exception {
+    GroupDto group = createGroup();
+    UserDto groupUser = createUser("ada", "Ada Lovelace");
+    createUser("grace", "Grace Hopper");
+    addUserToGroup(groupUser, group);
+    session.commit();
+
+    tester.newGetRequest("api/usergroups", "users")
+      .setParam("id", group.getId().toString())
+      .execute()
+      .assertJson(getClass(), "all.json");
+  }
+
+  @Test
+  public void selected_users() throws Exception {
+    GroupDto group = createGroup();
+    UserDto groupUser = createUser("ada", "Ada Lovelace");
+    createUser("grace", "Grace Hopper");
+    addUserToGroup(groupUser, group);
+    session.commit();
+
+    tester.newGetRequest("api/usergroups", "users")
+      .setParam("id", group.getId().toString())
+      .setParam("selected", "selected")
+      .execute()
+      .assertJson(getClass(), "selected.json");
+  }
+
+  @Test
+  public void deselected_users() throws Exception {
+    GroupDto group = createGroup();
+    UserDto groupUser = createUser("ada", "Ada Lovelace");
+    createUser("grace", "Grace Hopper");
+    addUserToGroup(groupUser, group);
+    session.commit();
+
+    tester.newGetRequest("api/usergroups", "users")
+      .setParam("id", group.getId().toString())
+      .setParam("selected", "deselected")
+      .execute()
+      .assertJson(getClass(), "deselected.json");
+  }
+
+  @Test
+  public void paging() throws Exception {
+    GroupDto group = createGroup();
+    UserDto groupUser = createUser("ada", "Ada Lovelace");
+    createUser("grace", "Grace Hopper");
+    addUserToGroup(groupUser, group);
+    session.commit();
+
+    tester.newGetRequest("api/usergroups", "users")
+      .setParam("id", group.getId().toString())
+      .setParam("ps", "1")
+      .execute()
+      .assertJson(getClass(), "all_page1.json");
+
+    tester.newGetRequest("api/usergroups", "users")
+      .setParam("id", group.getId().toString())
+      .setParam("ps", "1")
+      .setParam("p", "2")
+      .execute()
+      .assertJson(getClass(), "all_page2.json");
+  }
+
+  @Test
+  public void filtering() throws Exception {
+    GroupDto group = createGroup();
+    UserDto groupUser = createUser("ada", "Ada Lovelace");
+    createUser("grace", "Grace Hopper");
+    addUserToGroup(groupUser, group);
+    session.commit();
+
+    tester.newGetRequest("api/usergroups", "users")
+      .setParam("id", group.getId().toString())
+      .setParam("q", "ace")
+      .execute()
+      .assertJson(getClass(), "all.json");
+
+    tester.newGetRequest("api/usergroups", "users")
+      .setParam("id", group.getId().toString())
+      .setParam("q", "love")
+      .execute()
+      .assertJson(getClass(), "all_ada.json");
+  }
+
+  private GroupDto createGroup() {
+    return dbClient.groupDao().insert(session, new GroupDto()
+      .setName("sonar-users"));
+  }
+
+  private UserDto createUser(String login, String name) {
+    return dbClient.userDao().insert(session, new UserDto().setLogin(login).setName(name));
+  }
+
+  private void addUserToGroup(UserDto user, GroupDto usersGroup) {
+    dbClient.userGroupDao().insert(session, new UserGroupDto().setUserId(user.getId()).setGroupId(usersGroup.getId()));
+  }
+}
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/all.json b/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/all.json
new file mode 100644 (file)
index 0000000..8f0e903
--- /dev/null
@@ -0,0 +1,8 @@
+{
+  "p": 1,
+  "total": 2,
+  "users": [
+    {"login": "ada", "name": "Ada Lovelace", "selected": true},
+    {"login": "grace", "name": "Grace Hopper", "selected": false}
+  ]
+}
\ No newline at end of file
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/all_ada.json b/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/all_ada.json
new file mode 100644 (file)
index 0000000..cb5b2a6
--- /dev/null
@@ -0,0 +1,7 @@
+{
+  "p": 1,
+  "total": 1,
+  "users": [
+    {"login": "ada", "name": "Ada Lovelace", "selected": true}
+  ]
+}
\ No newline at end of file
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/all_page1.json b/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/all_page1.json
new file mode 100644 (file)
index 0000000..779ba39
--- /dev/null
@@ -0,0 +1,8 @@
+{
+  "p": 1,
+  "ps": 1,
+  "total": 2,
+  "users": [
+    {"login": "ada", "name": "Ada Lovelace", "selected": true}
+  ]
+}
\ No newline at end of file
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/all_page2.json b/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/all_page2.json
new file mode 100644 (file)
index 0000000..be6289a
--- /dev/null
@@ -0,0 +1,8 @@
+{
+  "p": 2,
+  "ps": 1,
+  "total": 2,
+  "users": [
+    {"login": "grace", "name": "Grace Hopper", "selected": false}
+  ]
+}
\ No newline at end of file
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/all_users.json b/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/all_users.json
new file mode 100644 (file)
index 0000000..cb5b2a6
--- /dev/null
@@ -0,0 +1,7 @@
+{
+  "p": 1,
+  "total": 1,
+  "users": [
+    {"login": "ada", "name": "Ada Lovelace", "selected": true}
+  ]
+}
\ No newline at end of file
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/deselected.json b/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/deselected.json
new file mode 100644 (file)
index 0000000..d181382
--- /dev/null
@@ -0,0 +1,7 @@
+{
+  "p": 1,
+  "total": 1,
+  "users": [
+    {"login": "grace", "name": "Grace Hopper", "selected": false}
+  ]
+}
\ No newline at end of file
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/empty.json b/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/empty.json
new file mode 100644 (file)
index 0000000..89d34ee
--- /dev/null
@@ -0,0 +1,5 @@
+{
+  "p": 1,
+  "total": 0,
+  "users": []
+}
\ No newline at end of file
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/selected.json b/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/selected.json
new file mode 100644 (file)
index 0000000..cb5b2a6
--- /dev/null
@@ -0,0 +1,7 @@
+{
+  "p": 1,
+  "total": 1,
+  "users": [
+    {"login": "ada", "name": "Ada Lovelace", "selected": true}
+  ]
+}
\ No newline at end of file
index 9a03d9335c30f9ccf4ca7be562c2d1fa7cb8fef0..09b25209addee89c9abb4eb041be51fc5104a6fe 100644 (file)
@@ -65,6 +65,16 @@ public class GroupMembershipDao implements DaoComponent {
     return mapper(session).countGroups(params);
   }
 
+  public List<UserMembershipDto> selectMembers(SqlSession session, UserMembershipQuery query, int offset, int limit) {
+    Map<String, Object> params = ImmutableMap.of("query", query, "groupId", query.groupId());
+    return mapper(session).selectMembers(params, new RowBounds(offset, limit));
+  }
+
+  public int countMembers(SqlSession session, UserMembershipQuery query) {
+    Map<String, Object> params = ImmutableMap.of("query", query, "groupId", query.groupId());
+    return mapper(session).countMembers(params);
+  }
+
   public Map<String, Integer> countUsersByGroups(final DbSession session, Collection<Long> groupIds) {
     final Map<String, Integer> result = Maps.newHashMap();
     DaoUtils.executeLargeInputs(groupIds, new Function<List<Long>, List<GroupUserCount>>() {
index 777aa1eea00c7f75c41798eb514397db937e7604..2d68ac5514ca989b770e3f0e2b3294333760a7c6 100644 (file)
@@ -32,6 +32,10 @@ public interface GroupMembershipMapper {
 
   int countGroups(Map<String, Object> params);
 
+  List<UserMembershipDto> selectMembers(Map<String, Object> params, RowBounds rowBounds);
+
+  int countMembers(Map<String, Object> params);
+
   List<GroupUserCount> countUsersByGroup(@Param("groupIds") List<Long> groupIds);
 
   List<LoginGroup> selectGroupsByLogins(@Param("logins") List<String> logins);
diff --git a/sonar-core/src/main/java/org/sonar/core/user/UserMembershipDto.java b/sonar-core/src/main/java/org/sonar/core/user/UserMembershipDto.java
new file mode 100644 (file)
index 0000000..afb6ca9
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.core.user;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+/**
+ * @since 5.2
+ */
+public class UserMembershipDto {
+
+  private Long id;
+  private Long groupId;
+  private String login;
+  private String name;
+
+  public Long getId() {
+    return id;
+  }
+
+  public UserMembershipDto setId(Long id) {
+    this.id = id;
+    return this;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public UserMembershipDto setName(String name) {
+    this.name = name;
+    return this;
+  }
+
+  @CheckForNull
+  public String getLogin() {
+    return login;
+  }
+
+  public UserMembershipDto setLogin(@Nullable String login) {
+    this.login = login;
+    return this;
+  }
+
+  @CheckForNull
+  public Long getGroupId() {
+    return groupId;
+  }
+
+  public UserMembershipDto setGroupId(@Nullable Long groupId) {
+    this.groupId = groupId;
+    return this;
+  }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/user/UserMembershipQuery.java b/sonar-core/src/main/java/org/sonar/core/user/UserMembershipQuery.java
new file mode 100644 (file)
index 0000000..209b206
--- /dev/null
@@ -0,0 +1,169 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.core.user;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableSet;
+import java.util.Set;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.StringUtils;
+
+public class UserMembershipQuery {
+
+  public static final int DEFAULT_PAGE_INDEX = 1;
+  public static final int DEFAULT_PAGE_SIZE = 100;
+
+  public static final String ANY = "ANY";
+  public static final String IN = "IN";
+  public static final String OUT = "OUT";
+  public static final Set<String> AVAILABLE_MEMBERSHIP = ImmutableSet.of(ANY, IN, OUT);
+
+  private final Long groupId;
+  private final String membership;
+
+  private final String memberSearch;
+
+  // for internal use in MyBatis
+  final String memberSearchSql;
+
+  // max results per page
+  private final int pageSize;
+
+  // index of selected page. Start with 1.
+  private final int pageIndex;
+
+
+  private UserMembershipQuery(Builder builder) {
+    this.groupId = builder.groupId;
+    this.membership = builder.membership;
+    this.memberSearch = builder.memberSearch;
+    this.memberSearchSql = memberSearchToSql(memberSearch);
+
+    this.pageSize = builder.pageSize;
+    this.pageIndex = builder.pageIndex;
+  }
+
+  private String memberSearchToSql(@Nullable String s) {
+    String sql = null;
+    if (s != null) {
+      sql = StringUtils.replace(StringUtils.upperCase(s), "%", "/%");
+      sql = StringUtils.replace(sql, "_", "/_");
+      sql = "%" + sql + "%";
+    }
+    return sql;
+  }
+
+  public Long groupId() {
+    return groupId;
+  }
+
+  @CheckForNull
+  public String membership() {
+    return membership;
+  }
+
+  /**
+   * Search for users names/logins containing a given string
+   */
+  @CheckForNull
+  public String memberSearch() {
+    return memberSearch;
+  }
+
+  public int pageSize() {
+    return pageSize;
+  }
+
+  public int pageIndex() {
+    return pageIndex;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static class Builder {
+    private Long groupId;
+    private String membership;
+    private String memberSearch;
+
+    private Integer pageIndex = DEFAULT_PAGE_INDEX;
+    private Integer pageSize = DEFAULT_PAGE_SIZE;
+
+    private Builder() {
+    }
+
+    public Builder groupId(Long groupId) {
+      this.groupId = groupId;
+      return this;
+    }
+
+    public Builder membership(@Nullable String membership) {
+      this.membership = membership;
+      return this;
+    }
+
+    public Builder memberSearch(@Nullable String s) {
+      this.memberSearch = StringUtils.defaultIfBlank(s, null);
+      return this;
+    }
+
+    public Builder pageSize(@Nullable Integer i) {
+      this.pageSize = i;
+      return this;
+    }
+
+    public Builder pageIndex(@Nullable Integer i) {
+      this.pageIndex = i;
+      return this;
+    }
+
+    private void initMembership() {
+      if (membership == null) {
+        membership = UserMembershipQuery.ANY;
+      } else {
+        Preconditions.checkArgument(AVAILABLE_MEMBERSHIP.contains(membership),
+          "Membership is not valid (got " + membership + "). Availables values are " + AVAILABLE_MEMBERSHIP);
+      }
+    }
+
+    private void initPageSize() {
+      if (pageSize == null) {
+        pageSize = DEFAULT_PAGE_SIZE;
+      }
+    }
+
+    private void initPageIndex() {
+      if (pageIndex == null) {
+        pageIndex = DEFAULT_PAGE_INDEX;
+      }
+      Preconditions.checkArgument(pageIndex > 0, "Page index must be greater than 0 (got " + pageIndex + ")");
+    }
+
+    public UserMembershipQuery build() {
+      Preconditions.checkNotNull(groupId, "Group ID cant be null.");
+      initMembership();
+      initPageIndex();
+      initPageSize();
+      return new UserMembershipQuery(this);
+    }
+  }
+}
index 2663c390189453c3f5c39267111240ab60c690b8..21ba56c9785dfa5e947d958649a12690b5bd0ec7 100644 (file)
     </where>
   </select>
 
+  <sql id="userCommonClauses">
+    FROM users u
+    LEFT JOIN groups_users gu ON gu.user_id=u.id AND gu.group_id=#{groupId}
+    <where>
+      <choose>
+        <when test="query.membership() == 'IN'">
+          AND gu.group_id IS NOT NULL
+        </when>
+        <when test="query.membership() == 'OUT'">
+          AND gu.group_id IS NULL
+        </when>
+      </choose>
+      <if test="query.memberSearch() != null">
+        AND ((UPPER(u.login) LIKE #{query.memberSearchSql} ESCAPE '/') OR (UPPER(u.name) LIKE #{query.memberSearchSql} ESCAPE '/'))
+      </if>
+      AND u.active=${_true}
+    </where>
+  </sql>
+
+  <select id="selectMembers" parameterType="map" resultType="org.sonar.core.user.UserMembershipDto">
+    SELECT u.id as id, u.login as login, u.name as name, gu.group_id as groupId
+    <include refid="userCommonClauses" />
+    ORDER BY u.name ASC
+  </select>
+
+  <select id="countMembers" parameterType="map" resultType="int">
+    SELECT COUNT(u.id)
+    <include refid="userCommonClauses" />
+  </select>
+
 </mapper>
index ed86008d099b719ae583831473dca579836a81fa..8b8992f07f0724baed5ba38165ba97d42991be4f 100644 (file)
@@ -202,4 +202,146 @@ public class GroupMembershipDaoTest {
       session.close();
     }
   }
+
+  @Test
+  public void count_members() {
+    dbTester.prepareDbUnit(getClass(), "shared_plus_empty_group.xml");
+    DbSession session = dbTester.myBatis().openSession(false);
+
+    try {
+      // 100 has 1 member and 1 non member
+      assertThat(dao.countMembers(session, UserMembershipQuery.builder().groupId(100L).membership(UserMembershipQuery.IN).build())).isEqualTo(1);
+      assertThat(dao.countMembers(session, UserMembershipQuery.builder().groupId(100L).membership(UserMembershipQuery.OUT).build())).isEqualTo(1);
+      // 101 has 2 members
+      assertThat(dao.countMembers(session, UserMembershipQuery.builder().groupId(101L).membership(UserMembershipQuery.IN).build())).isEqualTo(2);
+      assertThat(dao.countMembers(session, UserMembershipQuery.builder().groupId(101L).membership(UserMembershipQuery.OUT).build())).isZero();
+      // 102 has 1 member and 1 non member
+      assertThat(dao.countMembers(session, UserMembershipQuery.builder().groupId(102L).membership(UserMembershipQuery.IN).build())).isEqualTo(1);
+      assertThat(dao.countMembers(session, UserMembershipQuery.builder().groupId(102L).membership(UserMembershipQuery.OUT).build())).isEqualTo(1);
+      // 103 has no member
+      assertThat(dao.countMembers(session, UserMembershipQuery.builder().groupId(103L).membership(UserMembershipQuery.IN).build())).isZero();
+      assertThat(dao.countMembers(session, UserMembershipQuery.builder().groupId(103L).membership(UserMembershipQuery.OUT).build())).isEqualTo(2);
+    } finally {
+      session.close();
+    }
+  }
+
+  @Test
+  public void select_group_members_by_query() {
+    dbTester.prepareDbUnit(getClass(), "shared_plus_empty_group.xml");
+
+    DbSession session = dbTester.myBatis().openSession(false);
+
+    try {
+      // 100 has 1 member
+      assertThat(dao.selectMembers(session, UserMembershipQuery.builder().groupId(100L).membership(UserMembershipQuery.IN).build(), 0, 10)).hasSize(1);
+      // 101 has 2 members
+      assertThat(dao.selectMembers(session, UserMembershipQuery.builder().groupId(101L).membership(UserMembershipQuery.IN).build(), 0, 10)).hasSize(2);
+      // 102 has 1 member
+      assertThat(dao.selectMembers(session, UserMembershipQuery.builder().groupId(102L).membership(UserMembershipQuery.IN).build(), 0, 10)).hasSize(1);
+      // 103 has no member
+      assertThat(dao.selectMembers(session, UserMembershipQuery.builder().groupId(103L).membership(UserMembershipQuery.IN).build(), 0, 10)).isEmpty();
+    } finally {
+      session.close();
+    }
+  }
+
+  @Test
+  public void select_users_not_affected_to_a_group_by_query() {
+    dbTester.prepareDbUnit(getClass(), "shared_plus_empty_group.xml");
+
+    DbSession session = dbTester.myBatis().openSession(false);
+
+    try {
+      // 100 has 1 member
+      assertThat(dao.selectMembers(session, UserMembershipQuery.builder().groupId(100L).membership(UserMembershipQuery.OUT).build(), 0, 10)).hasSize(1);
+      // 101 has 2 members
+      assertThat(dao.selectMembers(session, UserMembershipQuery.builder().groupId(101L).membership(UserMembershipQuery.OUT).build(), 0, 10)).isEmpty();
+      // 102 has 1 member
+      assertThat(dao.selectMembers(session, UserMembershipQuery.builder().groupId(102L).membership(UserMembershipQuery.OUT).build(), 0, 10)).hasSize(1);
+      // 103 has no member
+      assertThat(dao.selectMembers(session, UserMembershipQuery.builder().groupId(103L).membership(UserMembershipQuery.OUT).build(), 0, 10)).hasSize(2);
+    } finally {
+      session.close();
+    }
+  }
+
+  @Test
+  public void search_by_user_name_or_login() {
+    dbTester.prepareDbUnit(getClass(), "shared_plus_empty_group.xml");
+
+    DbSession session = dbTester.myBatis().openSession(false);
+
+    try {
+
+      List<UserMembershipDto> result = dao.selectMembers(session, UserMembershipQuery.builder().groupId(100L).memberSearch("admin").build(), 0, 10);
+      assertThat(result).hasSize(2);
+
+      assertThat(result.get(0).getName()).isEqualTo("Admin");
+      assertThat(result.get(1).getName()).isEqualTo("Not Admin");
+
+      result = dao.selectMembers(session, UserMembershipQuery.builder().groupId(100L).memberSearch("not").build(), 0, 10);
+      assertThat(result).hasSize(1);
+
+    } finally {
+      session.close();
+    }
+  }
+
+  @Test
+  public void search_by_login_or_name_with_capitalization() {
+    dbTester.prepareDbUnit(getClass(), "shared_plus_empty_group.xml");
+
+    DbSession session = dbTester.myBatis().openSession(false);
+
+    try {
+      List<UserMembershipDto> result = dao.selectMembers(session, UserMembershipQuery.builder().groupId(100L).memberSearch("admin").build(), 0, 10);
+      assertThat(result).hasSize(2);
+
+      result = dao.selectMembers(session, UserMembershipQuery.builder().groupId(100L).memberSearch("AdMiN").build(), 0, 10);
+      assertThat(result).hasSize(2);
+    } finally {
+      session.close();
+    }
+
+  }
+
+  @Test
+  public void should_be_sorted_by_user_name() {
+    dbTester.prepareDbUnit(getClass(), "shared_plus_empty_group.xml");
+
+    DbSession session = dbTester.myBatis().openSession(false);
+
+    try {
+      List<UserMembershipDto> result = dao.selectMembers(session, UserMembershipQuery.builder().groupId(100L).build(), 0, 10);
+      assertThat(result).hasSize(2);
+      assertThat(result.get(0).getName()).isEqualTo("Admin");
+      assertThat(result.get(1).getName()).isEqualTo("Not Admin");
+    } finally {
+      session.close();
+    }
+  }
+
+  @Test
+  public void members_should_be_paginated() {
+    dbTester.prepareDbUnit(getClass(), "shared_plus_empty_group.xml");
+
+    DbSession session = dbTester.myBatis().openSession(false);
+
+    try {
+      List<UserMembershipDto> result = dao.selectMembers(session, UserMembershipQuery.builder().groupId(100L).build(), 0, 2);
+      assertThat(result).hasSize(2);
+      assertThat(result.get(0).getName()).isEqualTo("Admin");
+      assertThat(result.get(1).getName()).isEqualTo("Not Admin");
+
+      result = dao.selectMembers(session, UserMembershipQuery.builder().groupId(100L).build(), 1, 2);
+      assertThat(result).hasSize(1);
+      assertThat(result.get(0).getName()).isEqualTo("Not Admin");
+
+      result = dao.selectMembers(session, UserMembershipQuery.builder().groupId(100L).build(), 2, 1);
+      assertThat(result).isEmpty();
+    } finally {
+      session.close();
+    }
+  }
 }
index 487e11ff71fce13101c4526b772d2248a978d842..36b892243269ef29e6c963a4f2269d7f6fdcac01 100644 (file)
@@ -13,4 +13,8 @@
   <!-- user 201 is in users group -->
   <groups_users user_id="201" group_id="101"/>
 
+  <users id="200" login="admin" name="Admin" active="[true]"/>
+  <users id="201" login="not.admin" name="Not Admin" active="[true]"/>
+  <users id="202" login="inactive" name="Inactive" active="[false]"/>
+
 </dataset>