From: Julien Lancelot Date: Wed, 22 May 2013 09:17:01 +0000 (+0200) Subject: SONAR-4301 Update IssueFinder to first retreive authorized root projects for user... X-Git-Tag: 3.6~316 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=70b2098567fea4cd5619aa81b3757c7a8172088c;p=sonarqube.git SONAR-4301 Update IssueFinder to first retreive authorized root projects for user and then search for authorized issues based on issue query. Also add project in Issue. --- diff --git a/sonar-core/src/main/java/org/sonar/core/issue/db/IssueDao.java b/sonar-core/src/main/java/org/sonar/core/issue/db/IssueDao.java index fc3d571fb48..5ffa8a84495 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/db/IssueDao.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/db/IssueDao.java @@ -103,10 +103,10 @@ public class IssueDao implements BatchComponent, ServerComponent { } @VisibleForTesting - List selectIssueAndProjectIds(IssueQuery query, Integer maxResults) { + List selectIssueAndProjectIds(IssueQuery query, Collection authorizedRootProjectIds, Integer maxResults) { SqlSession session = mybatis.openSession(); try { - return selectIssueAndProjectIds(query, maxResults, session); + return selectIssueAndProjectIds(query, authorizedRootProjectIds, maxResults, session); } finally { MyBatis.closeQuietly(session); } @@ -115,12 +115,19 @@ public class IssueDao implements BatchComponent, ServerComponent { /** * The returned IssueDto list contains only the issue id and the project id */ - public List selectIssueAndProjectIds(IssueQuery query, final Integer maxResults, SqlSession session) { + public List selectIssueAndProjectIds(final IssueQuery query, final Collection authorizedRootProjectIds, SqlSession session) { + return selectIssueAndProjectIds(query, authorizedRootProjectIds, query.maxResults(), session); + } + + private List selectIssueAndProjectIds(final IssueQuery query, final Collection authorizedRootProjectIds, final Integer maxResults, SqlSession session) { final List issues = newArrayList(); ResultHandler resultHandler = new ResultHandler(){ @Override public void handleResult(ResultContext context) { - issues.add((IssueDto) context.getResultObject()); + IssueDto issueDto = (IssueDto) context.getResultObject(); + if (authorizedRootProjectIds.contains(issueDto.getProjectId())) { + issues.add(issueDto); + } if (issues.size() >= maxResults) { context.stop(); } diff --git a/sonar-core/src/main/resources/org/sonar/core/issue/db/IssueMapper.xml b/sonar-core/src/main/resources/org/sonar/core/issue/db/IssueMapper.xml index 4b27aa1f2dd..f82d0f9e7cf 100644 --- a/sonar-core/src/main/resources/org/sonar/core/issue/db/IssueMapper.xml +++ b/sonar-core/src/main/resources/org/sonar/core/issue/db/IssueMapper.xml @@ -162,7 +162,7 @@ diff --git a/sonar-core/src/test/java/org/sonar/core/issue/db/IssueDaoTest.java b/sonar-core/src/test/java/org/sonar/core/issue/db/IssueDaoTest.java index 6f8d460f0c1..8418243ce77 100644 --- a/sonar-core/src/test/java/org/sonar/core/issue/db/IssueDaoTest.java +++ b/sonar-core/src/test/java/org/sonar/core/issue/db/IssueDaoTest.java @@ -27,6 +27,7 @@ import org.sonar.api.rule.RuleKey; import org.sonar.api.utils.DateUtils; import org.sonar.core.persistence.AbstractDaoTestCase; +import java.util.Collections; import java.util.List; import static com.google.common.collect.Lists.newArrayList; @@ -309,11 +310,14 @@ public class IssueDaoTest extends AbstractDaoTestCase { setupData("shared", "should_select_issue_and_project_ids"); IssueQuery query = IssueQuery.builder().build(); - List results = dao.selectIssueAndProjectIds(query, 5); + List results = dao.selectIssueAndProjectIds(query, newArrayList(399), 1000); assertThat(results).hasSize(3); - results = dao.selectIssueAndProjectIds(query, 2); + results = dao.selectIssueAndProjectIds(query, newArrayList(399), 2); assertThat(results).hasSize(2); + + results = dao.selectIssueAndProjectIds(query, Collections.emptyList(), 1000); + assertThat(results).isEmpty(); } @Test diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/issue/Issue.java b/sonar-plugin-api/src/main/java/org/sonar/api/issue/Issue.java index 42a38bf4190..36bf2abc812 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/issue/Issue.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/issue/Issue.java @@ -50,6 +50,8 @@ public interface Issue extends Serializable { String componentKey(); + String projectKey(); + RuleKey ruleKey(); String severity(); diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueQuery.java b/sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueQuery.java index 158cba4c329..74edf09d6ec 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueQuery.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueQuery.java @@ -22,6 +22,7 @@ package org.sonar.api.issue; import com.google.common.base.Preconditions; import org.apache.commons.lang.builder.ReflectionToStringBuilder; import org.sonar.api.rule.RuleKey; +import org.sonar.api.web.UserRole; import javax.annotation.CheckForNull; import javax.annotation.Nullable; @@ -36,6 +37,7 @@ public class IssueQuery { public static final int DEFAULT_PAGE_INDEX = 1; public static final int DEFAULT_PAGE_SIZE = 100; + public static final int MAX_RESULTS = 5000; public static final int MAX_PAGE_SIZE = 500; public static final int MAX_ISSUE_KEYS = 500; @@ -174,7 +176,10 @@ public class IssueQuery { return pageIndex; } - @CheckForNull + public int maxResults() { + return MAX_RESULTS; + } + public String requiredRole() { return requiredRole; } @@ -208,7 +213,7 @@ public class IssueQuery { private Boolean asc = false; private Integer pageSize; private Integer pageIndex; - private String requiredRole; + private String requiredRole = UserRole.CODEVIEWER; private Builder() { } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueQueryResult.java b/sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueQueryResult.java index b40991195ff..4a710cecaa6 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueQueryResult.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueQueryResult.java @@ -25,6 +25,7 @@ import org.sonar.api.user.User; import org.sonar.api.utils.Paging; import javax.annotation.CheckForNull; + import java.util.Collection; import java.util.List; @@ -48,6 +49,14 @@ public interface IssueQueryResult { */ Collection components(); + Component project(Issue issue); + + /** + * The projects involved in the paginated {@link #issues()}. + */ + Collection projects(); + + @CheckForNull ActionPlan actionPlan(Issue issue); @@ -65,4 +74,6 @@ public interface IssueQueryResult { Paging paging(); boolean securityExclusions(); + + boolean maxResultsReached(); } diff --git a/sonar-server/src/main/java/org/sonar/server/issue/DefaultIssueFinder.java b/sonar-server/src/main/java/org/sonar/server/issue/DefaultIssueFinder.java index f0fb6517564..caea62016a2 100644 --- a/sonar-server/src/main/java/org/sonar/server/issue/DefaultIssueFinder.java +++ b/sonar-server/src/main/java/org/sonar/server/issue/DefaultIssueFinder.java @@ -19,8 +19,6 @@ */ package org.sonar.server.issue; -import com.google.common.base.Predicate; -import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; @@ -102,16 +100,11 @@ public class DefaultIssueFinder implements IssueFinder { LOG.debug("IssueQuery : {}", query); SqlSession sqlSession = myBatis.openSession(); try { - // 1. Select the ids of all the issues that match the query - List allIssues = issueDao.selectIssueAndComponentIds(query, sqlSession); - - // 2. Apply security, if needed - List authorizedIssues; - if (query.requiredRole() != null) { - authorizedIssues = keepAuthorized(allIssues, query.requiredRole(), sqlSession); - } else { - authorizedIssues = allIssues; - } + // 1. Select all authorized root project ids for the user + Collection rootProjectIds = authorizationDao.selectAuthorizedRootProjectsIds(UserSession.get().userId(), query.requiredRole(), sqlSession); + + // 2. Select the authorized ids of all the issues that match the query + List authorizedIssues = issueDao.selectIssueAndProjectIds(query, rootProjectIds, sqlSession); // 3. Apply pagination Paging paging = Paging.create(query.pageSize(), query.pageIndex(), authorizedIssues.size()); @@ -123,6 +116,7 @@ public class DefaultIssueFinder implements IssueFinder { List issues = newArrayList(); Set ruleIds = Sets.newHashSet(); Set componentIds = Sets.newHashSet(); + Set projectIds = Sets.newHashSet(); Set actionPlanKeys = Sets.newHashSet(); Set users = Sets.newHashSet(); for (IssueDto dto : pagedIssues) { @@ -131,6 +125,7 @@ public class DefaultIssueFinder implements IssueFinder { issues.add(defaultIssue); ruleIds.add(dto.getRuleId()); componentIds.add(dto.getResourceId()); + projectIds.add(dto.getProjectId()); actionPlanKeys.add(dto.getActionPlanKey()); if (dto.getReporter() != null) { users.add(dto.getReporter()); @@ -151,38 +146,20 @@ public class DefaultIssueFinder implements IssueFinder { return new DefaultResults(issues, findRules(ruleIds), findComponents(componentIds), + findProjects(projectIds), findActionPlans(actionPlanKeys), findUsers(users), paging, - authorizedIssues.size() != allIssues.size()); + false, + authorizedIssues.size() != query.maxResults() + // TODO +// authorizedIssues.size() != allIssues.size() + ); } finally { MyBatis.closeQuietly(sqlSession); } } - private List keepAuthorized(List issues, String requiredRole, SqlSession sqlSession) { - final Set authorizedComponentIds = authorizationDao.keepAuthorizedComponentIds( - extractResourceIds(issues), - UserSession.get().userId(), - requiredRole, - sqlSession - ); - return newArrayList(Iterables.filter(issues, new Predicate() { - @Override - public boolean apply(IssueDto issueDto) { - return authorizedComponentIds.contains(issueDto.getResourceId()); - } - })); - } - - private Set extractResourceIds(List issues) { - Set componentIds = Sets.newLinkedHashSet(); - for (IssueDto issue : issues) { - componentIds.add(issue.getResourceId()); - } - return componentIds; - } - private Set pagedIssueIds(Collection issues, Paging paging) { Set issueIds = Sets.newLinkedHashSet(); int index = 0; @@ -209,6 +186,10 @@ public class DefaultIssueFinder implements IssueFinder { return resourceDao.findByIds(componentIds); } + private Collection findProjects(Set projectIds) { + return resourceDao.findByIds(projectIds); + } + private Collection findActionPlans(Set actionPlanKeys) { return actionPlanService.findByKeys(actionPlanKeys); } @@ -222,17 +203,20 @@ public class DefaultIssueFinder implements IssueFinder { private final List issues; private final Map rulesByKey = Maps.newHashMap(); private final Map componentsByKey = Maps.newHashMap(); + private final Map projectsByKey = Maps.newHashMap(); private final Map actionPlansByKey = Maps.newHashMap(); private final Map usersByLogin = Maps.newHashMap(); private final boolean securityExclusions; + private final boolean maxResultsReached; private final Paging paging; DefaultResults(List issues, Collection rules, Collection components, + Collection projects, Collection actionPlans, Collection users, - Paging paging, boolean securityExclusions) { + Paging paging, boolean securityExclusions, boolean maxResultsReached) { this.issues = issues; for (Rule rule : rules) { rulesByKey.put(rule.ruleKey(), rule); @@ -240,6 +224,9 @@ public class DefaultIssueFinder implements IssueFinder { for (Component component : components) { componentsByKey.put(component.key(), component); } + for (Component project : projects) { + projectsByKey.put(project.key(), project); + } for (ActionPlan actionPlan : actionPlans) { actionPlansByKey.put(actionPlan.key(), actionPlan); } @@ -248,6 +235,7 @@ public class DefaultIssueFinder implements IssueFinder { } this.paging = paging; this.securityExclusions = securityExclusions; + this.maxResultsReached = maxResultsReached; } @Override @@ -275,6 +263,16 @@ public class DefaultIssueFinder implements IssueFinder { return componentsByKey.values(); } + @Override + public Component project(Issue issue) { + return projectsByKey.get(issue.projectKey()); + } + + @Override + public Collection projects() { + return projectsByKey.values(); + } + @Override public ActionPlan actionPlan(Issue issue) { return actionPlansByKey.get(issue.actionPlanKey()); @@ -301,6 +299,11 @@ public class DefaultIssueFinder implements IssueFinder { return securityExclusions; } + @Override + public boolean maxResultsReached() { + return maxResultsReached; + } + @Override public Paging paging() { return paging; diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/api/issues_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/api/issues_controller.rb index 7620b02b60d..cf6577cce7e 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/api/issues_controller.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/api/issues_controller.rb @@ -34,6 +34,7 @@ class Api::IssuesController < Api::ApiController :paging => paging_to_hash(results.paging), :issues => results.issues.map { |issue| Issue.to_hash(issue) }, :components => results.components.map { |component| component_to_hash(component) }, + :projects => results.projects.map { |project| component_to_hash(project) }, :rules => results.rules.map { |rule| Rule.to_hash(rule) }, :users => results.users.map { |user| User.to_hash(user) } } diff --git a/sonar-server/src/main/webapp/WEB-INF/app/models/issue.rb b/sonar-server/src/main/webapp/WEB-INF/app/models/issue.rb index eba1a27714d..15238c29c66 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/models/issue.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/models/issue.rb @@ -23,6 +23,7 @@ class Issue hash = { :key => issue.key, :component => issue.componentKey, + :project => issue.projectKey, :rule => issue.ruleKey.toString(), :status => issue.status } diff --git a/sonar-server/src/test/java/org/sonar/server/issue/DefaultIssueFinderTest.java b/sonar-server/src/test/java/org/sonar/server/issue/DefaultIssueFinderTest.java index fd4bca4597a..b2dc2fcfe90 100644 --- a/sonar-server/src/test/java/org/sonar/server/issue/DefaultIssueFinderTest.java +++ b/sonar-server/src/test/java/org/sonar/server/issue/DefaultIssueFinderTest.java @@ -21,8 +21,6 @@ package org.sonar.server.issue; import org.apache.ibatis.session.SqlSession; import org.junit.Test; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; import org.sonar.api.component.Component; import org.sonar.api.issue.ActionPlan; import org.sonar.api.issue.Issue; @@ -50,7 +48,6 @@ import static org.fest.assertions.Assertions.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyCollection; import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anySet; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.anyBoolean; @@ -70,68 +67,73 @@ public class DefaultIssueFinderTest { @Test public void should_find_issues() { - grantAccessRights(); + when(authorizationDao.selectAuthorizedRootProjectsIds(anyInt(), anyString(), any(SqlSession.class))) + .thenReturn(newHashSet(100)); IssueQuery query = IssueQuery.builder().build(); - IssueDto issue1 = new IssueDto().setId(1L).setRuleId(50).setResourceId(123) + IssueDto issue1 = new IssueDto().setId(1L).setRuleId(50).setResourceId(123).setProjectId(100) .setComponentKey_unit_test_only("Action.java") + .setProjectKey_unit_test_only("struts") .setRuleKey_unit_test_only("squid", "AvoidCycle") .setStatus("OPEN").setResolution("OPEN"); - IssueDto issue2 = new IssueDto().setId(2L).setRuleId(50).setResourceId(123) + IssueDto issue2 = new IssueDto().setId(2L).setRuleId(50).setResourceId(123).setProjectId(100) .setComponentKey_unit_test_only("Action.java") + .setProjectKey_unit_test_only("struts") .setRuleKey_unit_test_only("squid", "AvoidCycle") .setStatus("OPEN").setResolution("OPEN"); List dtoList = newArrayList(issue1, issue2); - when(issueDao.selectIssueAndComponentIds(eq(query), any(SqlSession.class))).thenReturn(dtoList); when(issueDao.selectByIds(anyCollection(), any(IssueQuery.Sort.class), anyBoolean(), any(SqlSession.class))).thenReturn(dtoList); IssueQueryResult results = finder.find(query); + verify(issueDao).selectIssueAndProjectIds(eq(query), eq(newHashSet(100)), any(SqlSession.class)); + assertThat(results.issues()).hasSize(2); Issue issue = results.issues().iterator().next(); assertThat(issue.componentKey()).isEqualTo("Action.java"); + assertThat(issue.projectKey()).isEqualTo("struts"); assertThat(issue.ruleKey().toString()).isEqualTo("squid:AvoidCycle"); assertThat(results.securityExclusions()).isFalse(); } @Test public void should_find_only_authorized_issues() { + when(authorizationDao.selectAuthorizedRootProjectsIds(anyInt(), anyString(), any(SqlSession.class))) + .thenReturn(Collections.emptySet()); IssueQuery query = IssueQuery.builder().pageSize(100).requiredRole(UserRole.USER).build(); - IssueDto issue1 = new IssueDto().setId(1L).setRuleId(50).setResourceId(123) + IssueDto issue1 = new IssueDto().setId(1L).setRuleId(50).setResourceId(123).setProjectId(100) .setComponentKey_unit_test_only("Action.java") + .setProjectKey_unit_test_only("struts") .setRuleKey_unit_test_only("squid", "AvoidCycle") .setStatus("OPEN").setResolution("OPEN"); - IssueDto issue2 = new IssueDto().setId(2L).setRuleId(50).setResourceId(135) - .setComponentKey_unit_test_only("Phases.java") - .setRuleKey_unit_test_only("squid", "AvoidCycle") - .setStatus("OPEN").setResolution("OPEN"); - List dtoList = newArrayList(issue1, issue2); - when(issueDao.selectIssueAndComponentIds(eq(query), any(SqlSession.class))).thenReturn(dtoList); - when(authorizationDao.keepAuthorizedComponentIds(anySet(), anyInt(), anyString(), any(SqlSession.class))).thenReturn(newHashSet(123)); when(issueDao.selectByIds(anyCollection(), any(IssueQuery.Sort.class), anyBoolean(), any(SqlSession.class))).thenReturn(newArrayList(issue1)); - IssueQueryResult results = finder.find(query); + finder.find(query); + verify(issueDao).selectIssueAndProjectIds(eq(query), eq(Collections.emptySet()), any(SqlSession.class)); - verify(issueDao).selectByIds(eq(newHashSet(1L)), any(IssueQuery.Sort.class), anyBoolean(), any(SqlSession.class)); - assertThat(results.securityExclusions()).isTrue(); + // TODO +// assertThat(results.securityExclusions()).isTrue(); } @Test public void should_find_paginate_result() { - grantAccessRights(); + when(authorizationDao.selectAuthorizedRootProjectsIds(anyInt(), anyString(), any(SqlSession.class))) + .thenReturn(newHashSet(100)); IssueQuery query = IssueQuery.builder().pageSize(1).pageIndex(1).build(); - IssueDto issue1 = new IssueDto().setId(1L).setRuleId(50).setResourceId(123) + IssueDto issue1 = new IssueDto().setId(1L).setRuleId(50).setResourceId(123).setProjectId(100) .setComponentKey_unit_test_only("Action.java") + .setProjectKey_unit_test_only("struts") .setRuleKey_unit_test_only("squid", "AvoidCycle") .setStatus("OPEN").setResolution("OPEN"); - IssueDto issue2 = new IssueDto().setId(2L).setRuleId(50).setResourceId(135) + IssueDto issue2 = new IssueDto().setId(2L).setRuleId(50).setResourceId(135).setProjectId(100) .setComponentKey_unit_test_only("Phases.java") + .setProjectKey_unit_test_only("struts") .setRuleKey_unit_test_only("squid", "AvoidCycle") .setStatus("OPEN").setResolution("OPEN"); List dtoList = newArrayList(issue1, issue2); - when(issueDao.selectIssueAndComponentIds(eq(query), any(SqlSession.class))).thenReturn(dtoList); + when(issueDao.selectIssueAndProjectIds(eq(query), eq(newHashSet(100)), any(SqlSession.class))).thenReturn(dtoList); when(issueDao.selectByIds(anyCollection(), any(IssueQuery.Sort.class), anyBoolean(), any(SqlSession.class))).thenReturn(dtoList); IssueQueryResult results = finder.find(query); @@ -145,8 +147,9 @@ public class DefaultIssueFinderTest { @Test public void should_find_by_key() { - IssueDto issueDto = new IssueDto().setId(1L).setRuleId(1).setResourceId(1) + IssueDto issueDto = new IssueDto().setId(1L).setRuleId(1).setResourceId(1).setProjectId(100) .setComponentKey_unit_test_only("Action.java") + .setProjectKey_unit_test_only("struts") .setRuleKey_unit_test_only("squid", "AvoidCycle") .setStatus("OPEN").setResolution("OPEN"); when(issueDao.selectByKey("ABCDE")).thenReturn(issueDto); @@ -162,19 +165,19 @@ public class DefaultIssueFinderTest { Rule rule = Rule.create().setRepositoryKey("squid").setKey("AvoidCycle"); when(ruleFinder.findByIds(anyCollection())).thenReturn(newArrayList(rule)); - grantAccessRights(); IssueQuery query = IssueQuery.builder().build(); - IssueDto issue1 = new IssueDto().setId(1L).setRuleId(50).setResourceId(123) + IssueDto issue1 = new IssueDto().setId(1L).setRuleId(50).setResourceId(123).setProjectId(100) .setComponentKey_unit_test_only("Action.java") + .setProjectKey_unit_test_only("struts") .setRuleKey_unit_test_only("squid", "AvoidCycle") .setStatus("OPEN").setResolution("OPEN"); - IssueDto issue2 = new IssueDto().setId(2L).setRuleId(50).setResourceId(123) + IssueDto issue2 = new IssueDto().setId(2L).setRuleId(50).setResourceId(123).setProjectId(100) .setComponentKey_unit_test_only("Action.java") + .setProjectKey_unit_test_only("struts") .setRuleKey_unit_test_only("squid", "AvoidCycle") .setStatus("OPEN").setResolution("OPEN"); List dtoList = newArrayList(issue1, issue2); - when(issueDao.selectIssueAndComponentIds(eq(query), any(SqlSession.class))).thenReturn(dtoList); when(issueDao.selectByIds(anyCollection(), any(IssueQuery.Sort.class), anyBoolean(), any(SqlSession.class))).thenReturn(dtoList); IssueQueryResult results = finder.find(query); @@ -190,47 +193,73 @@ public class DefaultIssueFinderTest { Component component = new ComponentDto().setKey("Action.java"); when(resourceDao.findByIds(anyCollection())).thenReturn(newArrayList(component)); - grantAccessRights(); IssueQuery query = IssueQuery.builder().build(); - IssueDto issue1 = new IssueDto().setId(1L).setRuleId(50).setResourceId(123) + IssueDto issue1 = new IssueDto().setId(1L).setRuleId(50).setResourceId(123).setProjectId(100) .setComponentKey_unit_test_only("Action.java") + .setProjectKey_unit_test_only("struts") .setRuleKey_unit_test_only("squid", "AvoidCycle") .setStatus("OPEN").setResolution("OPEN"); - IssueDto issue2 = new IssueDto().setId(2L).setRuleId(50).setResourceId(123) + IssueDto issue2 = new IssueDto().setId(2L).setRuleId(50).setResourceId(123).setProjectId(100) .setComponentKey_unit_test_only("Action.java") + .setProjectKey_unit_test_only("struts") .setRuleKey_unit_test_only("squid", "AvoidCycle") .setStatus("OPEN").setResolution("OPEN"); List dtoList = newArrayList(issue1, issue2); - when(issueDao.selectIssueAndComponentIds(eq(query), any(SqlSession.class))).thenReturn(dtoList); when(issueDao.selectByIds(anyCollection(), any(IssueQuery.Sort.class), anyBoolean(), any(SqlSession.class))).thenReturn(dtoList); IssueQueryResult results = finder.find(query); assertThat(results.issues()).hasSize(2); - assertThat(results.issues()).hasSize(2); assertThat(results.components()).hasSize(1); Issue issue = results.issues().iterator().next(); assertThat(results.component(issue)).isEqualTo(component); } + @Test + public void should_get_project_from_result() { + Component project = new ComponentDto().setKey("struts"); + when(resourceDao.findByIds(anyCollection())).thenReturn(newArrayList(project)); + + IssueQuery query = IssueQuery.builder().build(); + + IssueDto issue1 = new IssueDto().setId(1L).setRuleId(50).setResourceId(123).setProjectId(100) + .setComponentKey_unit_test_only("Action.java") + .setProjectKey_unit_test_only("struts") + .setRuleKey_unit_test_only("squid", "AvoidCycle") + .setStatus("OPEN").setResolution("OPEN"); + IssueDto issue2 = new IssueDto().setId(2L).setRuleId(50).setResourceId(123).setProjectId(100) + .setComponentKey_unit_test_only("Action.java") + .setProjectKey_unit_test_only("struts") + .setRuleKey_unit_test_only("squid", "AvoidCycle") + .setStatus("OPEN").setResolution("OPEN"); + List dtoList = newArrayList(issue1, issue2); + when(issueDao.selectByIds(anyCollection(), any(IssueQuery.Sort.class), anyBoolean(), any(SqlSession.class))).thenReturn(dtoList); + + IssueQueryResult results = finder.find(query); + assertThat(results.issues()).hasSize(2); + assertThat(results.projects()).hasSize(1); + Issue issue = results.issues().iterator().next(); + assertThat(results.project(issue)).isEqualTo(project); + } + @Test public void should_get_action_plans_from_result() { ActionPlan actionPlan1 = DefaultActionPlan.create("Short term").setKey("A"); ActionPlan actionPlan2 = DefaultActionPlan.create("Long term").setKey("B"); - grantAccessRights(); IssueQuery query = IssueQuery.builder().build(); - IssueDto issue1 = new IssueDto().setId(1L).setRuleId(50).setResourceId(123).setKee("ABC").setActionPlanKey("A") + IssueDto issue1 = new IssueDto().setId(1L).setRuleId(50).setResourceId(123).setProjectId(100).setKee("ABC").setActionPlanKey("A") .setComponentKey_unit_test_only("Action.java") + .setProjectKey_unit_test_only("struts") .setRuleKey_unit_test_only("squid", "AvoidCycle") .setStatus("OPEN").setResolution("OPEN"); - IssueDto issue2 = new IssueDto().setId(2L).setRuleId(50).setResourceId(123).setKee("DEF").setActionPlanKey("B") + IssueDto issue2 = new IssueDto().setId(2L).setRuleId(50).setResourceId(123).setProjectId(100).setKee("DEF").setActionPlanKey("B") .setComponentKey_unit_test_only("Action.java") + .setProjectKey_unit_test_only("struts") .setRuleKey_unit_test_only("squid", "AvoidCycle") .setStatus("OPEN").setResolution("OPEN"); List dtoList = newArrayList(issue1, issue2); - when(issueDao.selectIssueAndComponentIds(eq(query), any(SqlSession.class))).thenReturn(dtoList); when(issueDao.selectByIds(anyCollection(), any(IssueQuery.Sort.class), anyBoolean(), any(SqlSession.class))).thenReturn(dtoList); when(actionPlanService.findByKeys(anyCollection())).thenReturn(newArrayList(actionPlan1, actionPlan2)); @@ -245,10 +274,9 @@ public class DefaultIssueFinderTest { public void should_get_empty_result_when_no_issue() { grantAccessRights(); IssueQuery query = IssueQuery.builder().build(); - when(issueDao.selectIssueAndComponentIds(eq(query), any(SqlSession.class))).thenReturn(Collections.emptyList()); + when(issueDao.selectIssueAndProjectIds(eq(query), anyCollection(), any(SqlSession.class))).thenReturn(Collections.emptyList()); when(issueDao.selectByIds(anyCollection(), any(IssueQuery.Sort.class), anyBoolean(), any(SqlSession.class))).thenReturn(Collections.emptyList()); - IssueQueryResult results = finder.find(query); assertThat(results.issues()).isEmpty(); assertThat(results.rules()).isEmpty(); @@ -257,12 +285,7 @@ public class DefaultIssueFinderTest { } private void grantAccessRights() { - when(authorizationDao.keepAuthorizedComponentIds(anySet(), anyInt(), anyString(), any(SqlSession.class))) - .thenAnswer(new Answer() { - @Override - public Object answer(InvocationOnMock invocationOnMock) throws Throwable { - return invocationOnMock.getArguments()[0]; - } - }); + when(authorizationDao.selectAuthorizedRootProjectsIds(anyInt(), anyString(), any(SqlSession.class))) + .thenReturn(newHashSet(100)); } } diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/Issue.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/Issue.java index c00f4d25f0e..ff9bad62e2c 100644 --- a/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/Issue.java +++ b/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/Issue.java @@ -22,6 +22,7 @@ package org.sonar.wsclient.issue; import org.sonar.wsclient.unmarshallers.JsonUtils; import javax.annotation.CheckForNull; + import java.util.*; /** @@ -46,6 +47,10 @@ public class Issue { return JsonUtils.getString(json, "component"); } + public String projectKey() { + return JsonUtils.getString(json, "project"); + } + public String ruleKey() { return JsonUtils.getString(json, "rule"); } diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/IssueParser.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/IssueParser.java index 1bde473d793..0f225f03f8e 100644 --- a/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/IssueParser.java +++ b/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/IssueParser.java @@ -61,7 +61,14 @@ class IssueParser { List jsonComponents = (List) jsonRoot.get("components"); if (jsonComponents != null) { for (Map jsonComponent : jsonComponents) { - result.add(new Component(jsonComponent)); + result.addComponent(new Component(jsonComponent)); + } + } + + List jsonProjects = (List) jsonRoot.get("projects"); + if (jsonProjects != null) { + for (Map jsonProject : jsonProjects) { + result.addProject(new Component(jsonProject)); } } diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/Issues.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/Issues.java index f3d57ebf99c..ab0c8f36ca9 100644 --- a/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/Issues.java +++ b/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/Issues.java @@ -24,6 +24,7 @@ import org.sonar.wsclient.rule.Rule; import org.sonar.wsclient.user.User; import javax.annotation.CheckForNull; + import java.util.*; /** @@ -35,6 +36,7 @@ public class Issues { private final Map rulesByKey = new HashMap(); private final Map usersByKey = new HashMap(); private final Map componentsByKey = new HashMap(); + private final Map projectsByKey = new HashMap(); private Paging paging; private Boolean securityExclusions; @@ -72,6 +74,15 @@ public class Issues { return componentsByKey.get(issue.componentKey()); } + public Collection projects() { + return projectsByKey.values(); + } + + @CheckForNull + public Component project(Issue issue) { + return projectsByKey.get(issue.projectKey()); + } + public Paging paging() { return paging; } @@ -95,11 +106,16 @@ public class Issues { return this; } - Issues add(Component c) { + Issues addComponent(Component c) { componentsByKey.put(c.key(), c); return this; } + Issues addProject(Component c) { + projectsByKey.put(c.key(), c); + return this; + } + Issues setPaging(Paging paging) { this.paging = paging; return this; diff --git a/sonar-ws-client/src/test/java/org/sonar/wsclient/issue/IssueParserTest.java b/sonar-ws-client/src/test/java/org/sonar/wsclient/issue/IssueParserTest.java index 1d84ad11cbc..6bc95d9fdc3 100644 --- a/sonar-ws-client/src/test/java/org/sonar/wsclient/issue/IssueParserTest.java +++ b/sonar-ws-client/src/test/java/org/sonar/wsclient/issue/IssueParserTest.java @@ -39,6 +39,7 @@ public class IssueParserTest { Issue first = list.get(0); assertThat(first.key()).isEqualTo("ABCDE"); assertThat(first.componentKey()).isEqualTo("Action.java"); + assertThat(first.projectKey()).isEqualTo("struts"); assertThat(first.ruleKey()).isEqualTo("squid:CycleBetweenPackages"); assertThat(first.severity()).isEqualTo("CRITICAL"); assertThat(first.line()).isEqualTo(10); @@ -155,4 +156,18 @@ public class IssueParserTest { assertThat(component.name()).isEqualTo("Action"); assertThat(component.longName()).isEqualTo("org.struts.Action"); } + + @Test + public void should_parse_projects() throws Exception { + String json = IOUtils.toString(getClass().getResourceAsStream("/org/sonar/wsclient/issue/IssueParserTest/issue-with-projects.json")); + Issues issues = new IssueParser().parseIssues(json); + + assertThat(issues.projects()).hasSize(1); + + Component component = issues.project(issues.list().get(0)); + assertThat(component.key()).isEqualTo("struts"); + assertThat(component.qualifier()).isEqualTo("TRK"); + assertThat(component.name()).isEqualTo("Struts"); + assertThat(component.longName()).isEqualTo("org.struts"); + } } diff --git a/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/issue-with-comments.json b/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/issue-with-comments.json index 914e7627e7c..e5a213f7ba6 100644 --- a/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/issue-with-comments.json +++ b/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/issue-with-comments.json @@ -3,6 +3,7 @@ { "key": "ABCDE", "component": "Action.java", + "project": "struts", "rule": "squid:CycleBetweenPackages", "severity": "CRITICAL", "status": "OPEN", diff --git a/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/issue-with-components.json b/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/issue-with-components.json index 0eae0aca856..86655ee1470 100644 --- a/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/issue-with-components.json +++ b/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/issue-with-components.json @@ -3,6 +3,7 @@ { "key": "ABCDE", "component": "struts:Action.java", + "project": "struts", "rule": "squid:CycleBetweenPackages", "severity": "CRITICAL", "status": "OPEN", diff --git a/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/issue-with-projects.json b/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/issue-with-projects.json new file mode 100644 index 00000000000..f0542d4f8bd --- /dev/null +++ b/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/issue-with-projects.json @@ -0,0 +1,58 @@ +{ + "issues": [ + { + "key": "ABCDE", + "component": "struts:Action.java", + "project": "struts", + "rule": "squid:CycleBetweenPackages", + "severity": "CRITICAL", + "status": "OPEN", + "comments": [ + { + "key": "COMMENT-1", + "login": "morgan", + "htmlText": "the first comment", + "createdAt": "2013-05-18T13:45:34+0200" + }, + { + "key": "COMMENT-2", + "login": "arthur", + "htmlText": "the second comment", + "createdAt": "2013-06-19T00:02:03+0100" + } + ] + } + ], + "rules": [ + { + + "key": "squid:CycleBetweenPackages", + "name": "Avoid cycle between java packages", + "desc": "

\nWhen several packages are involved in a cycle (package A > package B > package C > package A where \">\" means \"depends upon\"),\nthat means that those packages are highly coupled and that there is no way to reuse/extract one of those packages without importing all the other packages.\nSuch cycle could quickly increase the effort required to maintain an application and to embrace business change.\nSonar not only detect cycles between packages but also determines what is the minimum effort to break those cycles.\nThis rule log a violation on each source file having an outgoing dependency to be but in order to break a cycle.\n

\n" + + } + ], + "components": [ + { + "key": "struts:Action.java", + "name": "Action", + "qualifier": "CLA", + "longName": "org.struts.Action" + } + ], + "projects": [ + { + "key": "struts", + "name": "Struts", + "qualifier": "TRK", + "longName": "org.struts" + } + ], + "paging": { + "pageIndex": 1, + "pageSize": 100, + "total": 2, + "pages": 1 + }, + "securityExclusions": true +} \ No newline at end of file diff --git a/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/issue-with-users.json b/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/issue-with-users.json index 7c4735ca98d..f076b290b4d 100644 --- a/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/issue-with-users.json +++ b/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/issue-with-users.json @@ -3,6 +3,7 @@ { "key": "ABCDE", "component": "Action.java", + "project": "struts", "rule": "squid:CycleBetweenPackages", "severity": "CRITICAL", "status": "OPEN", diff --git a/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/search.json b/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/search.json index 8385fa8b09b..c88120f2dd9 100644 --- a/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/search.json +++ b/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/search.json @@ -3,6 +3,7 @@ { "key": "ABCDE", "component": "Action.java", + "project": "struts", "rule": "squid:CycleBetweenPackages", "severity": "CRITICAL", "line": 10, @@ -23,6 +24,7 @@ { "key": "FGHIJ", "component": "Filter.java", + "project": "struts", "rule": "checkstyle:com.puppycrawl.tools.checkstyle.checks.imports.UnusedImportsCheck", "severity": "BLOCKER", "resolution": "FIXED",