]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-4301 Update IssueFinder to first retreive authorized root projects for user...
authorJulien Lancelot <julien.lancelot@gmail.com>
Wed, 22 May 2013 09:17:01 +0000 (11:17 +0200)
committerJulien Lancelot <julien.lancelot@gmail.com>
Wed, 22 May 2013 09:17:01 +0000 (11:17 +0200)
19 files changed:
sonar-core/src/main/java/org/sonar/core/issue/db/IssueDao.java
sonar-core/src/main/resources/org/sonar/core/issue/db/IssueMapper.xml
sonar-core/src/test/java/org/sonar/core/issue/db/IssueDaoTest.java
sonar-plugin-api/src/main/java/org/sonar/api/issue/Issue.java
sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueQuery.java
sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueQueryResult.java
sonar-server/src/main/java/org/sonar/server/issue/DefaultIssueFinder.java
sonar-server/src/main/webapp/WEB-INF/app/controllers/api/issues_controller.rb
sonar-server/src/main/webapp/WEB-INF/app/models/issue.rb
sonar-server/src/test/java/org/sonar/server/issue/DefaultIssueFinderTest.java
sonar-ws-client/src/main/java/org/sonar/wsclient/issue/Issue.java
sonar-ws-client/src/main/java/org/sonar/wsclient/issue/IssueParser.java
sonar-ws-client/src/main/java/org/sonar/wsclient/issue/Issues.java
sonar-ws-client/src/test/java/org/sonar/wsclient/issue/IssueParserTest.java
sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/issue-with-comments.json
sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/issue-with-components.json
sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/issue-with-projects.json [new file with mode: 0644]
sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/issue-with-users.json
sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/search.json

index fc3d571fb48e4e8f6f05042b95404c93934a3fb0..5ffa8a844951e9d6179a98b7d22d5da880798fd6 100644 (file)
@@ -103,10 +103,10 @@ public class IssueDao implements BatchComponent, ServerComponent {
   }
 
   @VisibleForTesting
-  List<IssueDto> selectIssueAndProjectIds(IssueQuery query, Integer maxResults) {
+  List<IssueDto> selectIssueAndProjectIds(IssueQuery query, Collection<Integer> 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<IssueDto> selectIssueAndProjectIds(IssueQuery query, final Integer maxResults, SqlSession session) {
+  public List<IssueDto> selectIssueAndProjectIds(final IssueQuery query, final Collection<Integer> authorizedRootProjectIds, SqlSession session) {
+    return selectIssueAndProjectIds(query, authorizedRootProjectIds, query.maxResults(), session);
+  }
+
+  private List<IssueDto> selectIssueAndProjectIds(final IssueQuery query, final Collection<Integer> authorizedRootProjectIds, final Integer maxResults, SqlSession session) {
     final List<IssueDto> 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();
         }
index 4b27aa1f2ddf444c43089e2a1d9fab2045787407..f82d0f9e7cf912eb89351f366e51321af192434c 100644 (file)
   </select>
 
   <select id="selectIssueAndProjectIds" parameterType="map" resultType="Issue">
-    select i.id, i.resource_id as resourceId
+    select i.id, i.project_id as projectId
     <include refid="selectQueryConditions"/>
   </select>
 
index 6f8d460f0c148bd3875bb8cfc77be3d1edff7b4e..8418243ce7736fdcf0ca949b612638e3296b47a5 100644 (file)
@@ -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<IssueDto> results = dao.selectIssueAndProjectIds(query, 5);
+    List<IssueDto> 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.<Integer>emptyList(), 1000);
+    assertThat(results).isEmpty();
   }
 
   @Test
index 42a38bf419026f0760057ef1d8666cd5790ebede..36bf2abc812defab5feffd0eff82874b81460ab6 100644 (file)
@@ -50,6 +50,8 @@ public interface Issue extends Serializable {
 
   String componentKey();
 
+  String projectKey();
+
   RuleKey ruleKey();
 
   String severity();
index 158cba4c32965818e0e27f29cc431c413c5a71d2..74edf09d6ecb9ff2e42d3701bf9093e79bd26636 100644 (file)
@@ -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() {
     }
index b40991195ff7f0f94e9ef44f9d0edbfd5faf7936..4a710cecaa69ef75c3f61c096ee5e59fa5f6a526 100644 (file)
@@ -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<Component> components();
 
+  Component project(Issue issue);
+
+  /**
+   * The projects involved in the paginated {@link #issues()}.
+   */
+  Collection<Component> projects();
+
+
   @CheckForNull
   ActionPlan actionPlan(Issue issue);
 
@@ -65,4 +74,6 @@ public interface IssueQueryResult {
   Paging paging();
 
   boolean securityExclusions();
+
+  boolean maxResultsReached();
 }
index f0fb6517564f69195cfebb5faf269cd550e7cc57..caea62016a2291a4f19252c16b28d337444e3461 100644 (file)
@@ -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<IssueDto> allIssues = issueDao.selectIssueAndComponentIds(query, sqlSession);
-
-      // 2. Apply security, if needed
-      List<IssueDto> 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<Integer> rootProjectIds = authorizationDao.selectAuthorizedRootProjectsIds(UserSession.get().userId(), query.requiredRole(), sqlSession);
+
+      // 2. Select the authorized ids of all the issues that match the query
+      List<IssueDto> 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<Issue> issues = newArrayList();
       Set<Integer> ruleIds = Sets.newHashSet();
       Set<Integer> componentIds = Sets.newHashSet();
+      Set<Integer> projectIds = Sets.newHashSet();
       Set<String> actionPlanKeys = Sets.newHashSet();
       Set<String> 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<IssueDto> keepAuthorized(List<IssueDto> issues, String requiredRole, SqlSession sqlSession) {
-    final Set<Integer> authorizedComponentIds = authorizationDao.keepAuthorizedComponentIds(
-      extractResourceIds(issues),
-      UserSession.get().userId(),
-      requiredRole,
-      sqlSession
-    );
-    return newArrayList(Iterables.filter(issues, new Predicate<IssueDto>() {
-      @Override
-      public boolean apply(IssueDto issueDto) {
-        return authorizedComponentIds.contains(issueDto.getResourceId());
-      }
-    }));
-  }
-
-  private Set<Integer> extractResourceIds(List<IssueDto> issues) {
-    Set<Integer> componentIds = Sets.newLinkedHashSet();
-    for (IssueDto issue : issues) {
-      componentIds.add(issue.getResourceId());
-    }
-    return componentIds;
-  }
-
   private Set<Long> pagedIssueIds(Collection<IssueDto> issues, Paging paging) {
     Set<Long> issueIds = Sets.newLinkedHashSet();
     int index = 0;
@@ -209,6 +186,10 @@ public class DefaultIssueFinder implements IssueFinder {
     return resourceDao.findByIds(componentIds);
   }
 
+  private Collection<Component> findProjects(Set<Integer> projectIds) {
+    return resourceDao.findByIds(projectIds);
+  }
+
   private Collection<ActionPlan> findActionPlans(Set<String> actionPlanKeys) {
     return actionPlanService.findByKeys(actionPlanKeys);
   }
@@ -222,17 +203,20 @@ public class DefaultIssueFinder implements IssueFinder {
     private final List<Issue> issues;
     private final Map<RuleKey, Rule> rulesByKey = Maps.newHashMap();
     private final Map<String, Component> componentsByKey = Maps.newHashMap();
+    private final Map<String, Component> projectsByKey = Maps.newHashMap();
     private final Map<String, ActionPlan> actionPlansByKey = Maps.newHashMap();
     private final Map<String, User> usersByLogin = Maps.newHashMap();
     private final boolean securityExclusions;
+    private final boolean maxResultsReached;
     private final Paging paging;
 
     DefaultResults(List<Issue> issues,
                    Collection<Rule> rules,
                    Collection<Component> components,
+                   Collection<Component> projects,
                    Collection<ActionPlan> actionPlans,
                    Collection<User> 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<Component> 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;
index 7620b02b60dd3ef9a30524e54da00fa5b5bb2e63..cf6577cce7eec7c06fe4a6702ad8d8d111039fa8 100644 (file)
@@ -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) }
     }
index eba1a27714d83678804c4a5613df7f6b6856edd7..15238c29c66bfb62b065cd6c7714dff104a17114 100644 (file)
@@ -23,6 +23,7 @@ class Issue
     hash = {
       :key => issue.key,
       :component => issue.componentKey,
+      :project => issue.projectKey,
       :rule => issue.ruleKey.toString(),
       :status => issue.status
     }
index fd4bca4597a880fe4b7170e18ca3aa5a67dc596a..b2dc2fcfe90b2d3f64aa0fddde2c9440bd0337df 100644 (file)
@@ -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<IssueDto> 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.<Integer>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<IssueDto> 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.<Integer>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<IssueDto> 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<IssueDto> 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<IssueDto> 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<IssueDto> 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<IssueDto> 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.<IssueDto>emptyList());
+    when(issueDao.selectIssueAndProjectIds(eq(query), anyCollection(), any(SqlSession.class))).thenReturn(Collections.<IssueDto>emptyList());
     when(issueDao.selectByIds(anyCollection(), any(IssueQuery.Sort.class), anyBoolean(), any(SqlSession.class))).thenReturn(Collections.<IssueDto>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<Object>() {
-        @Override
-        public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
-          return invocationOnMock.getArguments()[0];
-        }
-      });
+    when(authorizationDao.selectAuthorizedRootProjectsIds(anyInt(), anyString(), any(SqlSession.class)))
+      .thenReturn(newHashSet(100));
   }
 }
index c00f4d25f0e716d809a7188b9737a4e8dece3d45..ff9bad62e2c8a9bc3bf97a60e54a0cba40348383 100644 (file)
@@ -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");
   }
index 1bde473d79350b8aee09040acb6ea6084de03e05..0f225f03f8e153a03be47266815d26c9ce0be47d 100644 (file)
@@ -61,7 +61,14 @@ class IssueParser {
     List<Map> jsonComponents = (List) jsonRoot.get("components");
     if (jsonComponents != null) {
       for (Map jsonComponent : jsonComponents) {
-        result.add(new Component(jsonComponent));
+        result.addComponent(new Component(jsonComponent));
+      }
+    }
+
+    List<Map> jsonProjects = (List) jsonRoot.get("projects");
+    if (jsonProjects != null) {
+      for (Map jsonProject : jsonProjects) {
+        result.addProject(new Component(jsonProject));
       }
     }
 
index f3d57ebf99cf2970865b47b27432fe85890a3064..ab0c8f36ca959cae57abbcec2545ece2bf4482fa 100644 (file)
@@ -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<String, Rule> rulesByKey = new HashMap<String, Rule>();
   private final Map<String, User> usersByKey = new HashMap<String, User>();
   private final Map<String, Component> componentsByKey = new HashMap<String, Component>();
+  private final Map<String, Component> projectsByKey = new HashMap<String, Component>();
   private Paging paging;
   private Boolean securityExclusions;
 
@@ -72,6 +74,15 @@ public class Issues {
     return componentsByKey.get(issue.componentKey());
   }
 
+  public Collection<Component> 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;
index 1d84ad11cbc1f86e74a7b41cbed3a7f4334efe5d..6bc95d9fdc372991fd7fcde40b97f5cdcec708a6 100644 (file)
@@ -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");
+  }
 }
index 914e7627e7cb71f85dbd06f4b9e09605c76ba280..e5a213f7ba67cb46c9e74b4cf170346fdb93649e 100644 (file)
@@ -3,6 +3,7 @@
         {
             "key": "ABCDE",
             "component": "Action.java",
+            "project": "struts",
             "rule": "squid:CycleBetweenPackages",
             "severity": "CRITICAL",
             "status": "OPEN",
index 0eae0aca8561ea50bd96848cdfb9788987a55615..86655ee14700e716af605feacc1558d89f83131e 100644 (file)
@@ -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 (file)
index 0000000..f0542d4
--- /dev/null
@@ -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": "<p>\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</p>\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
index 7c4735ca98d0a55effd0d337f9aaf13679932d9d..f076b290b4d51d4f494429f9e929d8e37bc95e93 100644 (file)
@@ -3,6 +3,7 @@
         {
             "key": "ABCDE",
             "component": "Action.java",
+            "project": "struts",
             "rule": "squid:CycleBetweenPackages",
             "severity": "CRITICAL",
             "status": "OPEN",
index 8385fa8b09bcc7fe715b91e1b34cb843275dfe8c..c88120f2dd9880475ab831d4ba72d2cdedbf9aca 100644 (file)
@@ -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",