*/
package org.sonar.server.qualitygate;
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import java.util.Collection;
+import java.util.List;
+import javax.annotation.Nonnull;
import org.sonar.api.server.ServerSide;
+import org.sonar.api.utils.Paging;
+import org.sonar.api.web.UserRole;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
import org.sonar.db.qualitygate.ProjectQgateAssociation;
import org.sonar.db.qualitygate.ProjectQgateAssociationDao;
import org.sonar.db.qualitygate.ProjectQgateAssociationDto;
import org.sonar.db.qualitygate.ProjectQgateAssociationQuery;
import org.sonar.db.qualitygate.QualityGateDao;
-import org.sonar.db.qualitygate.QualityGateDto;
-import org.sonar.server.exceptions.NotFoundException;
-
-import java.util.List;
+import org.sonar.server.user.UserSession;
-import static com.google.common.collect.Lists.newArrayList;
+import static com.google.common.collect.FluentIterable.from;
+import static org.sonar.api.utils.Paging.forPageIndex;
+import static org.sonar.server.ws.WsUtils.checkFound;
@ServerSide
public class QgateProjectFinder {
+ private final DbClient dbClient;
+ private final QualityGateDao qualitygateDao;
+ private final ProjectQgateAssociationDao associationDao;
+ private final UserSession userSession;
+
+ public QgateProjectFinder(DbClient dbClient, UserSession userSession) {
+ this.dbClient = dbClient;
+ this.userSession = userSession;
+ this.qualitygateDao = dbClient.qualityGateDao();
+ this.associationDao = dbClient.projectQgateAssociationDao();
+ }
+
+ public Association find(ProjectQgateAssociationQuery query) {
+ DbSession dbSession = dbClient.openSession(false);
+ try {
+ getQualityGateId(dbSession, query.gateId());
+ List<ProjectQgateAssociationDto> projects = associationDao.selectProjects(dbSession, query);
+ List<ProjectQgateAssociationDto> authorizedProjects = keepAuthorizedProjects(dbSession, projects);
+
+ Paging paging = forPageIndex(query.pageIndex())
+ .withPageSize(query.pageSize())
+ .andTotal(authorizedProjects.size());
+ return new Association(toProjectAssociations(getPaginatedProjects(authorizedProjects, paging)), paging.hasNextPage());
+ } finally {
+ dbClient.closeSession(dbSession);
+ }
+ }
+
+ private Long getQualityGateId(DbSession dbSession, String gateId) {
+ return checkFound(qualitygateDao.selectById(dbSession, Long.valueOf(gateId)), "Quality gate '" + gateId + "' does not exists.").getId();
+ }
+
+ private static List<ProjectQgateAssociationDto> getPaginatedProjects(List<ProjectQgateAssociationDto> projects, Paging paging) {
+ return from(projects)
+ .skip(paging.offset())
+ .limit(paging.pageSize())
+ .toList();
+ }
+
+ private static List<ProjectQgateAssociation> toProjectAssociations(List<ProjectQgateAssociationDto> dtos) {
+ return from(dtos).transform(ToProjectAssociation.INSTANCE).toList();
+ }
+
+ private List<ProjectQgateAssociationDto> keepAuthorizedProjects(DbSession dbSession, List<ProjectQgateAssociationDto> projects) {
+ List<Long> projectIds = from(projects).transform(ToProjectId.INSTANCE).toList();
+ Collection<Long> authorizedProjectIds = dbClient.authorizationDao().keepAuthorizedProjectIds(dbSession, projectIds, userSession.getUserId(), UserRole.USER);
+ return from(projects).filter(new MatchProjectId(authorizedProjectIds)).toList();
+ }
+
public static class Association {
private List<ProjectQgateAssociation> projects;
private boolean hasMoreResults;
}
}
- private final QualityGateDao qualitygateDao;
- private final ProjectQgateAssociationDao associationDao;
+ private enum ToProjectId implements Function<ProjectQgateAssociationDto, Long> {
+ INSTANCE;
- public QgateProjectFinder(QualityGateDao qualitygateDao, ProjectQgateAssociationDao associationDao) {
- this.qualitygateDao = qualitygateDao;
- this.associationDao = associationDao;
+ @Override
+ public Long apply(@Nonnull ProjectQgateAssociationDto input) {
+ return input.getId();
+ }
}
- public Association find(ProjectQgateAssociationQuery query) {
- Long gateId = validateId(query.gateId());
- int pageSize = query.pageSize();
- int pageIndex = query.pageIndex();
-
- int offset = (pageIndex - 1) * pageSize;
- // Add one to page size in order to be able to know if there's more results or not
- int limit = pageSize + 1;
- List<ProjectQgateAssociationDto> dtos = associationDao.selectProjects(query, gateId, offset, limit);
- boolean hasMoreResults = false;
- if (dtos.size() == limit) {
- hasMoreResults = true;
- // Removed last entry as it's only need to know if there more results or not
- dtos.remove(dtos.size() - 1);
+ private static class MatchProjectId implements Predicate<ProjectQgateAssociationDto> {
+ private final Collection<Long> projectIds;
+
+ private MatchProjectId(Collection<Long> projectIds) {
+ this.projectIds = projectIds;
}
- return new Association(toProjectAssociations(dtos), hasMoreResults);
- }
- private Long validateId(String gateId) {
- QualityGateDto qualityGateDto = qualitygateDao.selectById(Long.valueOf(gateId));
- if (qualityGateDto == null) {
- throw new NotFoundException("Quality gate '" + gateId + "' does not exists.");
+ @Override
+ public boolean apply(@Nonnull ProjectQgateAssociationDto input) {
+ return projectIds.contains(input.getId());
}
- return qualityGateDto.getId();
}
- private List<ProjectQgateAssociation> toProjectAssociations(List<ProjectQgateAssociationDto> dtos) {
- List<ProjectQgateAssociation> groups = newArrayList();
- for (ProjectQgateAssociationDto groupMembershipDto : dtos) {
- groups.add(groupMembershipDto.toQgateAssociation());
+ private enum ToProjectAssociation implements Function<ProjectQgateAssociationDto, ProjectQgateAssociation> {
+ INSTANCE;
+
+ @Override
+ public ProjectQgateAssociation apply(@Nonnull ProjectQgateAssociationDto input) {
+ return input.toQgateAssociation();
}
- return groups;
}
+
}
*/
package org.sonar.server.qualitygate.ws;
-import org.sonar.api.server.ws.WebService.Param;
-
import com.google.common.io.Resources;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
+import org.sonar.api.server.ws.WebService.Param;
import org.sonar.api.utils.text.JsonWriter;
import org.sonar.db.qualitygate.ProjectQgateAssociation;
import org.sonar.db.qualitygate.ProjectQgateAssociationQuery;
@Override
public void define(WebService.NewController controller) {
WebService.NewAction action = controller.createAction("search")
- .setDescription("Search for projects associated (or not) to a quality gate")
+ .setDescription("Search for projects associated (or not) to a quality gate.<br/>" +
+ "Only authorized projects for current user will be returned.")
.setSince("4.3")
.setResponseExample(Resources.getResource(this.getClass(), "example-search.json"))
.setHandler(this);
JsonWriter writer = response.newJsonWriter();
writer.beginObject().prop("more", associations.hasMoreResults());
writer.name("results").beginArray();
+
for (ProjectQgateAssociation project : associations.projects()) {
writer.beginObject().prop("id", project.id()).prop("name", project.name()).prop(Param.SELECTED, project.isMember()).endObject();
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.qualitygate;
+
+import com.google.common.base.Function;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.utils.System2;
+import org.sonar.api.web.UserRole;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.property.PropertyDto;
+import org.sonar.db.qualitygate.ProjectQgateAssociation;
+import org.sonar.db.qualitygate.QualityGateDto;
+import org.sonar.db.user.GroupRoleDto;
+import org.sonar.db.user.UserDto;
+import org.sonar.db.user.UserRoleDto;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.qualitygate.QgateProjectFinder.Association;
+import org.sonar.server.tester.UserSessionRule;
+
+import static com.google.common.collect.FluentIterable.from;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.db.component.ComponentTesting.newProjectDto;
+import static org.sonar.db.qualitygate.ProjectQgateAssociationQuery.IN;
+import static org.sonar.db.qualitygate.ProjectQgateAssociationQuery.OUT;
+import static org.sonar.db.qualitygate.ProjectQgateAssociationQuery.builder;
+import static org.sonar.db.user.UserTesting.newUserDto;
+import static org.sonar.server.qualitygate.QualityGates.SONAR_QUALITYGATE_PROPERTY;
+
+public class QgateProjectFinderTest {
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Rule
+ public UserSessionRule userSession = UserSessionRule.standalone();
+
+ @Rule
+ public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+ DbClient dbClient = dbTester.getDbClient();
+
+ DbSession dbSession = dbTester.getSession();
+
+ ComponentDbTester componentDbTester = new ComponentDbTester(dbTester);
+
+ UserDto userDto;
+
+ QualityGateDto qGate;
+
+ QgateProjectFinder underTest = new QgateProjectFinder(dbClient, userSession);
+
+ @Before
+ public void setUp() throws Exception {
+ userDto = newUserDto();
+ dbClient.userDao().insert(dbSession, userDto);
+
+ qGate = new QualityGateDto().setName("Default Quality Gate");
+ dbClient.qualityGateDao().insert(qGate, dbSession);
+
+ dbTester.commit();
+ }
+
+ @Test
+ public void return_empty_association() throws Exception {
+ Association result = underTest.find(
+ builder()
+ .gateId(Long.toString(qGate.getId()))
+ .build());
+
+ assertThat(result.projects()).isEmpty();
+ }
+
+ @Test
+ public void return_all_projects() throws Exception {
+ ComponentDto associatedProject = insertProjectAuthorizedToAnyone(newProjectDto());
+ ComponentDto unassociatedProject = insertProjectAuthorizedToAnyone(newProjectDto());
+ associateProjectToQualitGate(associatedProject.getId());
+
+ Association result = underTest.find(
+ builder()
+ .gateId(Long.toString(qGate.getId()))
+ .build());
+
+ Map<Long, ProjectQgateAssociation> projectsById = projectsById(result.projects());
+ assertThat(projectsById).hasSize(2);
+
+ verifyProject(projectsById.get(associatedProject.getId()), true, associatedProject.name());
+ verifyProject(projectsById.get(unassociatedProject.getId()), false, unassociatedProject.name());
+ }
+
+ @Test
+ public void return_only_associated_project() throws Exception {
+ ComponentDto associatedProject = insertProjectAuthorizedToAnyone(newProjectDto());
+ insertProjectAuthorizedToAnyone(newProjectDto());
+ associateProjectToQualitGate(associatedProject.getId());
+
+ Association result = underTest.find(
+ builder()
+ .membership(IN)
+ .gateId(Long.toString(qGate.getId()))
+ .build());
+
+ Map<Long, ProjectQgateAssociation> projectsById = projectsById(result.projects());
+ assertThat(projectsById).hasSize(1);
+ verifyProject(projectsById.get(associatedProject.getId()), true, associatedProject.name());
+ }
+
+ @Test
+ public void return_only_unassociated_project() throws Exception {
+ ComponentDto associatedProject = insertProjectAuthorizedToAnyone(newProjectDto());
+ ComponentDto unassociatedProject = insertProjectAuthorizedToAnyone(newProjectDto());
+ associateProjectToQualitGate(associatedProject.getId());
+
+ Association result = underTest.find(
+ builder()
+ .membership(OUT)
+ .gateId(Long.toString(qGate.getId()))
+ .build());
+
+ Map<Long, ProjectQgateAssociation> projectsById = projectsById(result.projects());
+ assertThat(projectsById).hasSize(1);
+ verifyProject(projectsById.get(unassociatedProject.getId()), false, unassociatedProject.name());
+ }
+
+ @Test
+ public void return_only_authorized_projects() throws Exception {
+ userSession.login(userDto.getLogin()).setUserId(userDto.getId().intValue());
+ ComponentDto project1 = componentDbTester.insertComponent(newProjectDto());
+ componentDbTester.insertComponent(newProjectDto());
+
+ // User can only see project 1
+ dbClient.roleDao().insertUserRole(dbSession, new UserRoleDto().setUserId(userDto.getId()).setResourceId(project1.getId()).setRole(UserRole.USER));
+ dbTester.commit();
+
+ Association result = underTest.find(
+ builder()
+ .gateId(Long.toString(qGate.getId()))
+ .build());
+
+ verifyProjects(result, project1.getId());
+ }
+
+ @Test
+ public void test_paging() throws Exception {
+ ComponentDto project1 = insertProjectAuthorizedToAnyone(newProjectDto().setName("Project 1"));
+ ComponentDto project2 = insertProjectAuthorizedToAnyone(newProjectDto().setName("Project 2"));
+ ComponentDto project3 = insertProjectAuthorizedToAnyone(newProjectDto().setName("Project 3"));
+ associateProjectToQualitGate(project1.getId());
+
+ // Return partial result on first page
+ verifyPaging(underTest.find(
+ builder().gateId(Long.toString(qGate.getId()))
+ .pageIndex(1)
+ .pageSize(1)
+ .build()),
+ true, project1.getId());
+
+ // Return partial result on second page
+ verifyPaging(underTest.find(
+ builder().gateId(Long.toString(qGate.getId()))
+ .pageIndex(2)
+ .pageSize(1)
+ .build()),
+ true, project2.getId());
+
+ // Return partial result on first page
+ verifyPaging(underTest.find(
+ builder().gateId(Long.toString(qGate.getId()))
+ .pageIndex(1)
+ .pageSize(2)
+ .build()),
+ true, project1.getId(), project2.getId());
+
+ // Return all result on first page
+ verifyPaging(underTest.find(
+ builder().gateId(Long.toString(qGate.getId()))
+ .pageIndex(1)
+ .pageSize(3)
+ .build()),
+ false, project1.getId(), project2.getId(), project3.getId());
+
+ // Return no result as page index is off limit
+ verifyPaging(underTest.find(
+ builder().gateId(Long.toString(qGate.getId()))
+ .pageIndex(3)
+ .pageSize(3)
+ .build()),
+ false);
+ }
+
+ @Test
+ public void fail_on_unknown_quality_gate() throws Exception {
+ expectedException.expect(NotFoundException.class);
+ underTest.find(builder().gateId("123").build());
+ }
+
+ private void verifyProject(ProjectQgateAssociation project, boolean expectedMembership, String expectedName) {
+ assertThat(project.isMember()).isEqualTo(expectedMembership);
+ assertThat(project.name()).isEqualTo(expectedName);
+ }
+
+ private void verifyProjects(Association association, Long... expectedProjectIds) {
+ assertThat(association.projects()).extracting("id").containsOnly(expectedProjectIds);
+ }
+
+ private void verifyPaging(Association association, boolean expectedHasMoreResults, Long... expectedProjectIds) {
+ assertThat(association.hasMoreResults()).isEqualTo(expectedHasMoreResults);
+ assertThat(association.projects()).extracting("id").containsOnly(expectedProjectIds);
+ }
+
+ private void associateProjectToQualitGate(long projectId) {
+ dbClient.propertiesDao().insertProperty(
+ new PropertyDto().setKey(SONAR_QUALITYGATE_PROPERTY)
+ .setResourceId(projectId)
+ .setValue(Long.toString(qGate.getId())));
+ dbTester.commit();
+ }
+
+ private ComponentDto insertProjectAuthorizedToAnyone(ComponentDto project) {
+ componentDbTester.insertComponent(project);
+ dbClient.roleDao().insertGroupRole(dbSession, new GroupRoleDto().setGroupId(null).setResourceId(project.getId()).setRole(UserRole.USER));
+ dbSession.commit();
+ return project;
+ }
+
+ private ComponentDto insertProjectAuthorizedToUser(ComponentDto project, UserDto userDto) {
+ componentDbTester.insertComponent(project);
+ dbClient.roleDao().insertGroupRole(dbSession, new GroupRoleDto().setGroupId(null).setResourceId(project.getId()).setRole(UserRole.USER));
+ dbSession.commit();
+ return project;
+ }
+
+ private static Map<Long, ProjectQgateAssociation> projectsById(List<ProjectQgateAssociation> projects) {
+ return from(projects).uniqueIndex(ProjectToId.INSTANCE);
+ }
+
+ private enum ProjectToId implements Function<ProjectQgateAssociation, Long> {
+ INSTANCE;
+
+ @Override
+ public Long apply(@Nonnull ProjectQgateAssociation input) {
+ return input.id();
+ }
+ }
+}
*/
package org.sonar.db.qualitygate;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ImmutableMap;
import java.util.List;
-import java.util.Map;
-import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession;
import org.sonar.db.Dao;
-import org.sonar.db.MyBatis;
+import org.sonar.db.DbSession;
public class ProjectQgateAssociationDao implements Dao {
- private final MyBatis mybatis;
-
- public ProjectQgateAssociationDao(MyBatis mybatis) {
- this.mybatis = mybatis;
- }
-
- public List<ProjectQgateAssociationDto> selectProjects(ProjectQgateAssociationQuery query, Long gateId, int offset, int limit) {
- SqlSession session = mybatis.openSession(false);
- try {
- Map<String, Object> params = ImmutableMap.of("query", query, "gateId", gateId.toString());
- return mapper(session).selectProjects(params, new RowBounds(offset, limit));
- } finally {
- MyBatis.closeQuietly(session);
- }
- }
-
- @VisibleForTesting
- List<ProjectQgateAssociationDto> selectProjects(ProjectQgateAssociationQuery query, Long gateId) {
- return selectProjects(query, gateId, 0, Integer.MAX_VALUE);
+ public List<ProjectQgateAssociationDto> selectProjects(DbSession dbSession, ProjectQgateAssociationQuery query) {
+ return mapper(dbSession).selectProjects(query);
}
private static ProjectQgateAssociationMapper mapper(SqlSession session) {
package org.sonar.db.qualitygate;
import java.util.List;
-import java.util.Map;
-import org.apache.ibatis.session.RowBounds;
+import org.apache.ibatis.annotations.Param;
public interface ProjectQgateAssociationMapper {
- List<ProjectQgateAssociationDto> selectProjects(ProjectQgateAssociationQuery query);
+ List<ProjectQgateAssociationDto> selectProjects(@Param("query") ProjectQgateAssociationQuery query);
- List<ProjectQgateAssociationDto> selectProjects(Map<String, Object> params, RowBounds rowBounds);
}
JOIN resource_index ind ON ind.root_project_id=proj.id
</if>
LEFT JOIN properties prop ON prop.resource_id=proj.id AND prop.prop_key='sonar.qualitygate' AND prop.text_value LIKE
- #{gateId}
+ #{query.gateId}
<where>
<choose>
<when test="query.membership() == 'selected'">
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.utils.System2;
+import org.sonar.db.DbSession;
import org.sonar.db.DbTester;
import static org.assertj.core.api.Assertions.assertThat;
@Rule
public DbTester dbTester = DbTester.create(System2.INSTANCE);
+ DbSession dbSession = dbTester.getSession();
+
ProjectQgateAssociationDao dao = dbTester.getDbClient().projectQgateAssociationDao();
@Test
dbTester.prepareDbUnit(getClass(), "shared.xml");
ProjectQgateAssociationQuery query = ProjectQgateAssociationQuery.builder().gateId("42").build();
- List<ProjectQgateAssociationDto> result = dao.selectProjects(query, 42L);
+ List<ProjectQgateAssociationDto> result = dao.selectProjects(dbSession, query);
assertThat(result).hasSize(5);
}
public void select_projects_by_query() {
dbTester.prepareDbUnit(getClass(), "shared.xml");
- assertThat(dao.selectProjects(ProjectQgateAssociationQuery.builder().gateId("42").membership(ProjectQgateAssociationQuery.IN).build(), 42L)).hasSize(3);
- assertThat(dao.selectProjects(ProjectQgateAssociationQuery.builder().gateId("42").membership(ProjectQgateAssociationQuery.OUT).build(), 42L)).hasSize(2);
+ assertThat(dao.selectProjects(dbSession, ProjectQgateAssociationQuery.builder().gateId("42").membership(ProjectQgateAssociationQuery.IN).build())).hasSize(3);
+ assertThat(dao.selectProjects(dbSession, ProjectQgateAssociationQuery.builder().gateId("42").membership(ProjectQgateAssociationQuery.OUT).build())).hasSize(2);
}
@Test
public void search_by_project_name() {
dbTester.prepareDbUnit(getClass(), "shared.xml");
- List<ProjectQgateAssociationDto> result = dao.selectProjects(ProjectQgateAssociationQuery.builder().gateId("42").projectSearch("one").build(), 42L);
+ List<ProjectQgateAssociationDto> result = dao.selectProjects(dbSession, ProjectQgateAssociationQuery.builder().gateId("42").projectSearch("one").build());
assertThat(result).hasSize(1);
assertThat(result.get(0).getName()).isEqualTo("Project One");
- result = dao.selectProjects(ProjectQgateAssociationQuery.builder().gateId("42").projectSearch("one").build(), 42L);
+ result = dao.selectProjects(dbSession, ProjectQgateAssociationQuery.builder().gateId("42").projectSearch("one").build());
assertThat(result).hasSize(1);
- result = dao.selectProjects(ProjectQgateAssociationQuery.builder().gateId("42").projectSearch("project").build(), 42L);
+ result = dao.selectProjects(dbSession, ProjectQgateAssociationQuery.builder().gateId("42").projectSearch("project").build());
assertThat(result).hasSize(2);
}
public void should_be_sorted_by_project_name() {
dbTester.prepareDbUnit(getClass(), "shared.xml");
- List<ProjectQgateAssociationDto> result = dao.selectProjects(ProjectQgateAssociationQuery.builder().gateId("42").build(), 42L);
+ List<ProjectQgateAssociationDto> result = dao.selectProjects(dbSession, ProjectQgateAssociationQuery.builder().gateId("42").build());
assertThat(result).hasSize(5);
assertThat(result.get(0).getName()).isEqualTo("Project Five");
assertThat(result.get(1).getName()).isEqualTo("Project Four");