]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-5531 Update IssueStorage to allow issue index modification on server side
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Tue, 16 Sep 2014 11:16:09 +0000 (13:16 +0200)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Tue, 16 Sep 2014 11:16:09 +0000 (13:16 +0200)
42 files changed:
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/InitialOpenIssuesStackTest.java
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingDecoratorTest.java
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingTest.java
server/sonar-server/src/main/java/org/sonar/server/batch/UploadReportAction.java
server/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java
server/sonar-server/src/main/java/org/sonar/server/issue/IssueService.java
server/sonar-server/src/main/java/org/sonar/server/issue/ServerIssueStorage.java
server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileLoader.java
server/sonar-server/src/main/java/org/sonar/server/rule/DefaultRuleFinder.java
server/sonar-server/src/main/java/org/sonar/server/rule/RuleService.java
server/sonar-server/src/main/java/org/sonar/server/search/BaseIndex.java
server/sonar-server/src/test/java/org/sonar/server/batch/UploadReportActionMediumTest.java
server/sonar-server/src/test/java/org/sonar/server/component/ComponentCleanerServiceMediumTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/DefaultIssueFinderTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/ServerIssueStorageTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/actionplan/ActionPlanServiceTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/db/IssueBackendMediumTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueAuthorizationIndexMediumTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexMediumTest.java
server/sonar-server/src/test/java/org/sonar/server/permission/InternalPermissionServiceMediumTest.java
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ActiveRuleBackendMediumTest.java
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/RegisterQualityProfilesMediumTest.java
server/sonar-server/src/test/java/org/sonar/server/rule/index/RuleIndexMediumTest.java
server/sonar-server/src/test/resources/org/sonar/server/issue/ServerIssueStorageTest/should_insert_new_issues-result.xml [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/issue/ServerIssueStorageTest/should_insert_new_issues.xml [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/issue/ServerIssueStorageTest/should_update_issues-result.xml [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/issue/ServerIssueStorageTest/should_update_issues.xml [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/issue/ScanIssueStorage.java
sonar-batch/src/test/java/org/sonar/batch/issue/ScanIssueStorageTest.java
sonar-batch/src/test/resources/org/sonar/batch/issue/ScanIssueStorageTest/should_insert_new_issues-result.xml [new file with mode: 0644]
sonar-batch/src/test/resources/org/sonar/batch/issue/ScanIssueStorageTest/should_insert_new_issues.xml [new file with mode: 0644]
sonar-batch/src/test/resources/org/sonar/batch/issue/ScanIssueStorageTest/should_resolve_conflicts_on_updates-result.xml [new file with mode: 0644]
sonar-batch/src/test/resources/org/sonar/batch/issue/ScanIssueStorageTest/should_resolve_conflicts_on_updates.xml [new file with mode: 0644]
sonar-batch/src/test/resources/org/sonar/batch/issue/ScanIssueStorageTest/should_update_issues-result.xml [new file with mode: 0644]
sonar-batch/src/test/resources/org/sonar/batch/issue/ScanIssueStorageTest/should_update_issues.xml [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/issue/db/IssueDto.java
sonar-core/src/main/java/org/sonar/core/issue/db/IssueStorage.java
sonar-core/src/main/java/org/sonar/core/issue/db/UpdateConflictResolver.java
sonar-core/src/test/java/org/sonar/core/issue/db/IssueDtoTest.java
sonar-core/src/test/java/org/sonar/core/issue/db/IssueStorageTest.java
sonar-core/src/test/java/org/sonar/core/issue/db/UpdateConflictResolverTest.java

index aa81851122d349ac9c16904f81e28e8ec6036b36..08cb3cfc3d6517d0b846b09bd1d06594bfeece71 100644 (file)
@@ -70,7 +70,7 @@ public class InitialOpenIssuesStackTest {
 
   @Test
   public void get_and_remove_issues() {
-    IssueDto issueDto = new IssueDto().setComponentKey_unit_test_only("org.struts.Action").setKee("ISSUE-1");
+    IssueDto issueDto = new IssueDto().setComponentKey("org.struts.Action").setKee("ISSUE-1");
     stack.addIssue(issueDto);
 
     List<IssueDto> issueDtos = stack.selectAndRemoveIssues("org.struts.Action");
@@ -82,8 +82,8 @@ public class InitialOpenIssuesStackTest {
 
   @Test
   public void get_and_remove_with_many_issues_on_same_resource() {
-    stack.addIssue(new IssueDto().setComponentKey_unit_test_only("org.struts.Action").setKee("ISSUE-1"));
-    stack.addIssue(new IssueDto().setComponentKey_unit_test_only("org.struts.Action").setKee("ISSUE-2"));
+    stack.addIssue(new IssueDto().setComponentKey("org.struts.Action").setKee("ISSUE-1"));
+    stack.addIssue(new IssueDto().setComponentKey("org.struts.Action").setKee("ISSUE-2"));
 
     List<IssueDto> issueDtos = stack.selectAndRemoveIssues("org.struts.Action");
     assertThat(issueDtos).hasSize(2);
@@ -93,7 +93,7 @@ public class InitialOpenIssuesStackTest {
 
   @Test
   public void get_and_remove_do_nothing_if_resource_not_found() {
-    stack.addIssue(new IssueDto().setComponentKey_unit_test_only("org.struts.Action").setKee("ISSUE-1"));
+    stack.addIssue(new IssueDto().setComponentKey("org.struts.Action").setKee("ISSUE-1"));
 
     List<IssueDto> issueDtos = stack.selectAndRemoveIssues("Other");
     assertThat(issueDtos).hasSize(0);
@@ -119,7 +119,7 @@ public class InitialOpenIssuesStackTest {
 
   @Test
   public void clear_issues() {
-    stack.addIssue(new IssueDto().setComponentKey_unit_test_only("org.struts.Action").setKee("ISSUE-1"));
+    stack.addIssue(new IssueDto().setComponentKey("org.struts.Action").setKee("ISSUE-1"));
 
     assertThat(stack.selectAllIssues()).hasSize(1);
 
index 121b473d2d88d127d190e66b850bcb1d1df8a9e5..c2f20fc26cce79cbc62a1564114f4c49f7071b93 100644 (file)
@@ -137,7 +137,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
     Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123);
 
     // INPUT : one issue existing during previous scan
-    IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey_unit_test_only("squid", "AvoidCycle");
+    IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey("squid", "AvoidCycle");
 
     IssueTrackingResult trackingResult = new IssueTrackingResult();
     trackingResult.addUnmatched(unmatchedIssue);
@@ -163,7 +163,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
     Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123);
 
     // INPUT : one issue existing during previous scan
-    IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(6).setStatus("OPEN").setRuleKey_unit_test_only("manual", "Performance");
+    IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(6).setStatus("OPEN").setRuleKey("manual", "Performance");
     when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance"));
 
     IssueTrackingResult trackingResult = new IssueTrackingResult();
@@ -209,7 +209,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
     Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123);
 
     // INPUT : one issue existing during previous scan
-    IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(1).setStatus("CLOSED").setRuleKey_unit_test_only("manual", "Performance");
+    IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(1).setStatus("CLOSED").setRuleKey("manual", "Performance");
     when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance"));
 
     IssueTrackingResult trackingResult = new IssueTrackingResult();
@@ -243,7 +243,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
     Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123);
 
     // INPUT : one issue existing during previous scan
-    IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(null).setStatus("OPEN").setRuleKey_unit_test_only("manual", "Performance");
+    IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(null).setStatus("OPEN").setRuleKey("manual", "Performance");
     when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance"));
 
     IssueTrackingResult trackingResult = new IssueTrackingResult();
@@ -279,7 +279,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
 
     // INPUT : one issue existing during previous scan
     final int issueOnLine = 6;
-    IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(issueOnLine).setStatus("OPEN").setRuleKey_unit_test_only("manual", "Performance");
+    IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(issueOnLine).setStatus("OPEN").setRuleKey("manual", "Performance");
     when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance"));
 
     IssueTrackingResult trackingResult = new IssueTrackingResult();
@@ -327,7 +327,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
 
     // INPUT : one issue existing during previous scan
     final int issueOnLine = 3;
-    IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(issueOnLine).setStatus("OPEN").setRuleKey_unit_test_only("manual", "Performance");
+    IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(issueOnLine).setStatus("OPEN").setRuleKey("manual", "Performance");
     when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance"));
 
     IssueTrackingResult trackingResult = new IssueTrackingResult();
@@ -375,7 +375,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
     Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123);
 
     // INPUT : one issue existing during previous scan
-    IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(1).setStatus("OPEN").setRuleKey_unit_test_only("manual", "Performance");
+    IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(1).setStatus("OPEN").setRuleKey("manual", "Performance");
     when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance").setStatus(Rule.STATUS_REMOVED));
 
     IssueTrackingResult trackingResult = new IssueTrackingResult();
@@ -408,7 +408,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
     Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123);
 
     // INPUT : one issue existing during previous scan
-    IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(1).setStatus("OPEN").setRuleKey_unit_test_only("manual", "Performance");
+    IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(1).setStatus("OPEN").setRuleKey("manual", "Performance");
     when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(null);
 
     IssueTrackingResult trackingResult = new IssueTrackingResult();
@@ -441,7 +441,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
     Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123);
 
     // INPUT : one issue existing during previous scan
-    IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(6).setStatus("OPEN").setRuleKey_unit_test_only("manual", "Performance");
+    IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(6).setStatus("OPEN").setRuleKey("manual", "Performance");
     when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(null);
 
     IssueTrackingResult trackingResult = new IssueTrackingResult();
@@ -486,7 +486,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
     Project project = new Project("struts");
     DefaultIssue openIssue = new DefaultIssue();
     when(issueCache.byComponent("struts")).thenReturn(Arrays.asList(openIssue));
-    IssueDto deadIssue = new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey_unit_test_only("squid", "AvoidCycle");
+    IssueDto deadIssue = new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey("squid", "AvoidCycle");
     when(initialOpenIssues.selectAllIssues()).thenReturn(Arrays.asList(deadIssue));
 
     decorator.doDecorate(project);
@@ -507,8 +507,8 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
 
   @Test
   public void merge_matched_issue() throws Exception {
-    IssueDto previousIssue = new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey_unit_test_only("squid", "AvoidCycle")
-      .setLine(10).setSeverity("MAJOR").setMessage("Message").setEffortToFix(1.5).setDebt(1L).setRootComponentKey_unit_test_only("sample");
+    IssueDto previousIssue = new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey("squid", "AvoidCycle")
+      .setLine(10).setSeverity("MAJOR").setMessage("Message").setEffortToFix(1.5).setDebt(1L).setRootComponentKey("sample");
     DefaultIssue issue = new DefaultIssue();
 
     IssueTrackingResult trackingResult = mock(IssueTrackingResult.class);
@@ -526,7 +526,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
 
   @Test
   public void merge_matched_issue_on_manual_severity() throws Exception {
-    IssueDto previousIssue = new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey_unit_test_only("squid", "AvoidCycle")
+    IssueDto previousIssue = new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey("squid", "AvoidCycle")
       .setLine(10).setManualSeverity(true).setSeverity("MAJOR").setMessage("Message").setEffortToFix(1.5).setDebt(1L);
     DefaultIssue issue = new DefaultIssue();
 
@@ -544,7 +544,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
   public void merge_issue_changelog_with_previous_changelog() throws Exception {
     when(initialOpenIssues.selectChangelog("ABCDE")).thenReturn(newArrayList(new IssueChangeDto().setIssueKey("ABCD")));
 
-    IssueDto previousIssue = new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey_unit_test_only("squid", "AvoidCycle")
+    IssueDto previousIssue = new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey("squid", "AvoidCycle")
       .setLine(10).setMessage("Message").setEffortToFix(1.5).setDebt(1L);
     DefaultIssue issue = new DefaultIssue();
 
index 15064b8a9daea9f1b78428bea6ece78a641ac3d1..761ca65210e1c196a714731961e285df28cca880 100644 (file)
 
 package org.sonar.plugins.core.issue;
 
-import org.sonar.api.batch.SonarIndex;
-
 import com.google.common.base.Charsets;
 import com.google.common.io.Resources;
 import org.junit.Before;
 import org.junit.Test;
+import org.sonar.api.batch.SonarIndex;
 import org.sonar.api.issue.Issue;
 import org.sonar.api.issue.internal.DefaultIssue;
 import org.sonar.api.resources.Project;
@@ -355,7 +354,7 @@ public class IssueTrackingTest {
     referenceIssue.setKee(Long.toString(id));
     referenceIssue.setLine(lineId);
     referenceIssue.setMessage(message);
-    referenceIssue.setRuleKey_unit_test_only(ruleRepo, ruleKey);
+    referenceIssue.setRuleKey(ruleRepo, ruleKey);
     referenceIssue.setChecksum(lineChecksum);
     referenceIssue.setResolution(null);
     referenceIssue.setStatus(Issue.STATUS_OPEN);
index a893c68ada2831cc8abe66d32689d9cdf1db3a72..a35ffcb46ad96944ffa6d1798c80cc8a912ddeda 100644 (file)
@@ -93,6 +93,10 @@ public class UploadReportAction implements RequestHandler {
       dbClient.issueDao().synchronizeAfter(session,
         index.get(IssueIndex.class).getLastSynchronization(),
         ImmutableMap.of("project", projectKey));
+
+      // Index project's permissions indexes
+      permissionService.synchronizePermissions(session, project.key());
+
       session.commit();
 
     } finally {
index 9ed1662e7467838d48b702be90e5515cbfb30a8b..1fa162a5dd707194605422014dca8849498e6eaf 100644 (file)
@@ -459,6 +459,7 @@ public class InternalRubyIssueService implements ServerComponent {
     });
   }
 
+
   /**
    * Execute issue filter from parameters
    */
index 0dd4724da906247186fe5b3ad406a8a39c0ef78f..6ef2e3b252f73dc40f84f9011e92c185dfa161a4 100644 (file)
@@ -63,11 +63,8 @@ import org.sonar.server.search.QueryContext;
 import org.sonar.server.user.UserSession;
 
 import javax.annotation.Nullable;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
+
+import java.util.*;
 
 /**
  * @since 3.6
@@ -295,6 +292,8 @@ public class IssueService implements ServerComponent {
   }
 
   public IssueQueryResult loadIssue(String issueKey) {
+    // TODO use IssueIndex for ACL
+    // TODO load DTO
     IssueQueryResult result = finder.find(IssueQuery.builder().issueKeys(Arrays.asList(issueKey)).requiredRole(UserRole.USER).build());
     if (result.issues().size() != 1) {
       // TODO throw 404
@@ -337,10 +336,7 @@ public class IssueService implements ServerComponent {
   }
 
   public org.sonar.server.search.Result<Issue> search(IssueQuery query, QueryContext options) {
-
     IssueIndex issueIndex = indexClient.get(IssueIndex.class);
-
     return issueIndex.search(query, options);
-
   }
 }
index 90ccf5f4762168a3d5f96512025c10add0486f0a..bdc93fbc925a6943a6721cfdb62f3e38123927c9 100644 (file)
@@ -29,8 +29,8 @@ import org.sonar.core.persistence.MyBatis;
 import org.sonar.core.resource.ResourceDto;
 import org.sonar.core.resource.ResourceQuery;
 import org.sonar.server.db.DbClient;
-import org.sonar.server.search.IndexDefinition;
-import org.sonar.server.search.action.UpsertDto;
+
+import java.util.Date;
 
 /**
  * @since 3.6
@@ -44,22 +44,21 @@ public class ServerIssueStorage extends IssueStorage implements ServerComponent
     this.dbClient = dbClient;
   }
 
-  @Override
-  public void save(Iterable<DefaultIssue> issues) {
-    super.save(issues);
-    DbSession session = dbClient.openSession(false);
-    try {
-      for (DefaultIssue issue : issues) {
-        IssueDto issueDto = dbClient.issueDao().getByKey(session, issue.key());
-        session.enqueue(new UpsertDto<IssueDto>(IndexDefinition.ISSUES.getIndexType(), issueDto));
-      }
-      session.commit();
-    } finally {
-      session.close();
-    }
+  protected void doInsert(DbSession session, Date now, DefaultIssue issue) {
+    long componentId = componentId(issue);
+    long projectId = projectId(issue);
+    int ruleId = ruleId(issue);
+    IssueDto dto = IssueDto.toDtoForInsert(issue, componentId, projectId, ruleId, now);
+
+    dbClient.issueDao().insert(session, dto);
+  }
+
+  protected void doUpdate(DbSession session, Date now, DefaultIssue issue) {
+    IssueDto dto = IssueDto.toDtoForUpdate(issue, projectId(issue), now);
+
+    dbClient.issueDao().update(session, dto);
   }
 
-  @Override
   protected long componentId(DefaultIssue issue) {
     // TODO should be using ComponentDao
     ResourceDto resourceDto = dbClient.resourceDao().getResource(ResourceQuery.create().setKey(issue.componentKey()));
@@ -69,7 +68,6 @@ public class ServerIssueStorage extends IssueStorage implements ServerComponent
     return resourceDto.getId();
   }
 
-  @Override
   protected long projectId(DefaultIssue issue) {
     // TODO should be using ComponentDao
     ResourceDto resourceDto = dbClient.resourceDao().getResource(ResourceQuery.create().setKey(issue.projectKey()));
index 3d7f1803797b8959272dfd2a5ac0f6280a829e36..d34959bdb44e22f67e28d58b09636644bd7e082f 100644 (file)
@@ -41,6 +41,8 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
 
+import static com.google.common.collect.Lists.newArrayList;
+
 public class IssueIndex extends BaseIndex<Issue, IssueDto, String> {
 
   public IssueIndex(IssueNormalizer normalizer, SearchClient client) {
@@ -110,6 +112,15 @@ public class IssueIndex extends BaseIndex<Issue, IssueDto, String> {
     return new IssueDoc(fields);
   }
 
+  @Override
+  public Issue getNullableByKey(String key) {
+    Result<Issue> result = search(IssueQuery.builder().issueKeys(newArrayList(key)).build(), new QueryContext());
+    if (result.getTotal() == 1) {
+      return result.getHits().get(0);
+    }
+    return null;
+  }
+
   public Result<Issue> search(IssueQuery query, QueryContext options) {
 
     SearchRequestBuilder esSearch = getClient()
index 1460a8950b4e02c1e3c29949b0aae06abb5a33e8..20b689a13b98647511db6e8f15873ad2bb90fd46 100644 (file)
@@ -86,7 +86,7 @@ public class QProfileLoader implements ServerComponent {
 
   @CheckForNull
   public ActiveRule getActiveRule(ActiveRuleKey key) {
-    return index.get(ActiveRuleIndex.class).getByKey(key);
+    return index.get(ActiveRuleIndex.class).getNullableByKey(key);
   }
 
   public List<ActiveRule> findActiveRulesByRule(RuleKey key) {
index bb96fd210b6ab9b1aede034eef270ad07f119da2..319e0b8e6b35f036b77e76b273c5833f49a9910b 100644 (file)
@@ -73,7 +73,7 @@ public class DefaultRuleFinder implements RuleFinder {
 
   @CheckForNull
   public org.sonar.api.rules.Rule findByKey(RuleKey key) {
-    Rule rule = index.getByKey(key);
+    Rule rule = index.getNullableByKey(key);
     if (rule != null && rule.status() != RuleStatus.REMOVED) {
       return toRule(rule);
     } else {
index 45850dd4dcd94afb1963a64ad4bf184f337bf07f..e10e619b656114b95f2848ad95ba28b7d4463871 100644 (file)
@@ -55,7 +55,7 @@ public class RuleService implements ServerComponent {
 
   @CheckForNull
   public Rule getByKey(RuleKey key) {
-    return index.getByKey(key);
+    return index.getNullableByKey(key);
   }
 
   public List<Rule> getByKeys(Collection<RuleKey> keys) {
@@ -63,7 +63,7 @@ public class RuleService implements ServerComponent {
   }
 
   public Rule getNonNullByKey(RuleKey key) {
-    Rule rule = index.getByKey(key);
+    Rule rule = index.getNullableByKey(key);
     if (rule == null) {
       throw new NotFoundException("Rule not found: " + key);
     }
index 53e2fdfae8b3291685d68a620231c684647e8f0e..6faafd538df6eda512dfbde9fffddb3b130bd51f 100644 (file)
@@ -26,12 +26,7 @@ import com.google.common.collect.Multimap;
 import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsResponse;
 import org.elasticsearch.action.count.CountRequestBuilder;
 import org.elasticsearch.action.count.CountResponse;
-import org.elasticsearch.action.get.GetRequestBuilder;
-import org.elasticsearch.action.get.GetResponse;
-import org.elasticsearch.action.get.MultiGetItemResponse;
-import org.elasticsearch.action.get.MultiGetRequest;
-import org.elasticsearch.action.get.MultiGetRequestBuilder;
-import org.elasticsearch.action.get.MultiGetResponse;
+import org.elasticsearch.action.get.*;
 import org.elasticsearch.action.search.SearchRequestBuilder;
 import org.elasticsearch.action.search.SearchResponse;
 import org.elasticsearch.action.search.SearchScrollRequestBuilder;
@@ -54,21 +49,14 @@ import org.joda.time.DateTime;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.sonar.core.persistence.Dto;
+import org.sonar.server.exceptions.NotFoundException;
 
+import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 
 import java.io.IOException;
 import java.io.Serializable;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Queue;
+import java.util.*;
 
 public abstract class BaseIndex<DOMAIN, DTO extends Dto<KEY>, KEY extends Serializable>
   implements Index<DOMAIN, DTO, KEY> {
@@ -386,6 +374,15 @@ public abstract class BaseIndex<DOMAIN, DTO extends Dto<KEY>, KEY extends Serial
   protected abstract DOMAIN toDoc(Map<String, Object> fields);
 
   public DOMAIN getByKey(KEY key) {
+    DOMAIN value = getNullableByKey(key);
+    if (value == null) {
+      throw new NotFoundException(String.format("Key '%s' not found", key));
+    }
+    return value;
+  }
+
+  @CheckForNull
+  public DOMAIN getNullableByKey(KEY key) {
     GetRequestBuilder request = client.prepareGet()
       .setType(this.getIndexType())
       .setIndex(this.getIndexName())
index 1f8deae524298a0c5eb17cadc0336a7564a3c8dc..0e39c7af09ec1680f20ec956af26af733b9c2bd4 100644 (file)
@@ -48,6 +48,8 @@ import org.sonar.server.tester.ServerTester;
 import org.sonar.server.user.MockUserSession;
 import org.sonar.server.ws.WsTester;
 
+import java.util.Date;
+
 import static org.fest.assertions.Assertions.assertThat;
 
 public class UploadReportActionMediumTest {
@@ -99,7 +101,7 @@ public class UploadReportActionMediumTest {
 
     session.commit();
 
-    assertThat(tester.get(IssueAuthorizationIndex.class).getByKey(project.getKey())).isNull();
+    assertThat(tester.get(IssueAuthorizationIndex.class).getNullableByKey(project.getKey())).isNull();
 
     MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION);
     WsTester.TestRequest request = wsTester.newGetRequest(BatchWs.API_ENDPOINT, UploadReportAction.UPLOAD_REPORT_ACTION);
@@ -108,7 +110,7 @@ public class UploadReportActionMediumTest {
     request.execute();
 
     // Check that issue authorization index has been created
-    assertThat(tester.get(IssueAuthorizationIndex.class).getByKey(project.getKey())).isNotNull();
+    assertThat(tester.get(IssueAuthorizationIndex.class).getNullableByKey(project.getKey())).isNotNull();
   }
 
   @Test(expected = ForbiddenException.class)
@@ -135,6 +137,10 @@ public class UploadReportActionMediumTest {
       .setProjectId(1L);
     db.componentDao().insert(session, project);
 
+    // project can be seen by anyone
+    tester.get(PermissionFacade.class).insertGroupPermission(project.getId(), DefaultGroups.ANYONE, UserRole.USER, session);
+    db.issueAuthorizationDao().synchronizeAfter(session, new Date(0));
+
     ComponentDto resource = new ComponentDto()
       .setProjectId(1L)
       .setKey("MyComponent")
@@ -162,7 +168,7 @@ public class UploadReportActionMediumTest {
     // Clear issue index to simulate that the issue has been inserted by the batch, so that it's not yet index in E/S
     clearIssueIndex();
     assertThat(db.issueDao().getByKey(session, issue.getKey())).isNotNull();
-    assertThat(tester.get(IssueIndex.class).getByKey(issue.getKey())).isNull();
+    assertThat(tester.get(IssueIndex.class).getNullableByKey(issue.getKey())).isNull();
 
     MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION);
 
@@ -171,7 +177,7 @@ public class UploadReportActionMediumTest {
     request.execute();
 
     // Check that the issue has well be indexed in E/S
-    assertThat(tester.get(IssueIndex.class).getByKey(issue.getKey())).isNotNull();
+    assertThat(tester.get(IssueIndex.class).getNullableByKey(issue.getKey())).isNotNull();
   }
 
   private void clearIssueIndex(){
index f87765e1147f383465f0abee35e4d8436bc40d90..0f9ed0d34835f90c628dbe04667123955615c4c0 100644 (file)
@@ -95,11 +95,11 @@ public class ComponentCleanerServiceMediumTest {
 
     session.commit();
 
-    assertThat(tester.get(IssueAuthorizationIndex.class).getByKey(project.getKey())).isNotNull();
+    assertThat(tester.get(IssueAuthorizationIndex.class).getNullableByKey(project.getKey())).isNotNull();
 
     service.delete(project.getKey());
 
-    assertThat(tester.get(IssueAuthorizationIndex.class).getByKey(project.getKey())).isNull();
+    assertThat(tester.get(IssueAuthorizationIndex.class).getNullableByKey(project.getKey())).isNull();
   }
 
   @Test(expected = IllegalArgumentException.class)
index 663e57a542049310bc9a1954a2e4e81a7b4eb5f1..738de5c4c631dd8afa15aa5a154c2755026f94fe 100644 (file)
@@ -41,9 +41,9 @@ import org.sonar.core.issue.db.IssueDao;
 import org.sonar.core.issue.db.IssueDto;
 import org.sonar.core.persistence.MyBatis;
 import org.sonar.core.resource.ResourceDao;
-import org.sonar.server.rule.DefaultRuleFinder;
 import org.sonar.core.user.DefaultUser;
 import org.sonar.server.issue.actionplan.ActionPlanService;
+import org.sonar.server.rule.DefaultRuleFinder;
 
 import java.util.Collections;
 import java.util.List;
@@ -84,14 +84,14 @@ public class
     IssueQuery query = IssueQuery.builder().build();
 
     IssueDto issue1 = new IssueDto().setId(1L).setRuleId(50).setComponentId(123l).setRootComponentId(100l)
-      .setComponentKey_unit_test_only("Action.java")
-      .setRootComponentKey_unit_test_only("struts")
-      .setRuleKey_unit_test_only("squid", "AvoidCycle")
+      .setComponentKey("Action.java")
+      .setRootComponentKey("struts")
+      .setRuleKey("squid", "AvoidCycle")
       .setStatus("OPEN").setResolution("OPEN");
     IssueDto issue2 = new IssueDto().setId(2L).setRuleId(50).setComponentId(123l).setRootComponentId(100l)
-      .setComponentKey_unit_test_only("Action.java")
-      .setRootComponentKey_unit_test_only("struts")
-      .setRuleKey_unit_test_only("squid", "AvoidCycle")
+      .setComponentKey("Action.java")
+      .setRootComponentKey("struts")
+      .setRuleKey("squid", "AvoidCycle")
       .setStatus("OPEN").setResolution("OPEN");
     List<IssueDto> dtoList = newArrayList(issue1, issue2);
     when(issueDao.selectByIds(anyCollection(), any(SqlSession.class))).thenReturn(dtoList);
@@ -111,14 +111,14 @@ public class
     IssueQuery query = IssueQuery.builder().pageSize(1).pageIndex(1).build();
 
     IssueDto issue1 = new IssueDto().setId(1L).setRuleId(50).setComponentId(123l).setRootComponentId(100l)
-      .setComponentKey_unit_test_only("Action.java")
-      .setRootComponentKey_unit_test_only("struts")
-      .setRuleKey_unit_test_only("squid", "AvoidCycle")
+      .setComponentKey("Action.java")
+      .setRootComponentKey("struts")
+      .setRuleKey("squid", "AvoidCycle")
       .setStatus("OPEN").setResolution("OPEN");
     IssueDto issue2 = new IssueDto().setId(2L).setRuleId(50).setComponentId(135l).setRootComponentId(100l)
-      .setComponentKey_unit_test_only("Phases.java")
-      .setRootComponentKey_unit_test_only("struts")
-      .setRuleKey_unit_test_only("squid", "AvoidCycle")
+      .setComponentKey("Phases.java")
+      .setRootComponentKey("struts")
+      .setRuleKey("squid", "AvoidCycle")
       .setStatus("OPEN").setResolution("OPEN");
     List<IssueDto> dtoList = newArrayList(issue1, issue2);
     when(issueDao.selectIssueIds(eq(query), anyInt(), any(SqlSession.class))).thenReturn(dtoList);
@@ -136,9 +136,9 @@ public class
   @Test
   public void find_by_key() {
     IssueDto issueDto = new IssueDto().setId(1L).setRuleId(1).setComponentId(1l).setRootComponentId(100l)
-      .setComponentKey_unit_test_only("Action.java")
-      .setRootComponentKey_unit_test_only("struts")
-      .setRuleKey_unit_test_only("squid", "AvoidCycle")
+      .setComponentKey("Action.java")
+      .setRootComponentKey("struts")
+      .setRuleKey("squid", "AvoidCycle")
       .setStatus("OPEN").setResolution("OPEN");
     when(issueDao.selectByKey("ABCDE")).thenReturn(issueDto);
 
@@ -156,14 +156,14 @@ public class
     IssueQuery query = IssueQuery.builder().build();
 
     IssueDto issue1 = new IssueDto().setId(1L).setRuleId(50).setComponentId(123l).setRootComponentId(100l)
-      .setComponentKey_unit_test_only("Action.java")
-      .setRootComponentKey_unit_test_only("struts")
-      .setRuleKey_unit_test_only("squid", "AvoidCycle")
+      .setComponentKey("Action.java")
+      .setRootComponentKey("struts")
+      .setRuleKey("squid", "AvoidCycle")
       .setStatus("OPEN").setResolution("OPEN");
     IssueDto issue2 = new IssueDto().setId(2L).setRuleId(50).setComponentId(123l).setRootComponentId(100l)
-      .setComponentKey_unit_test_only("Action.java")
-      .setRootComponentKey_unit_test_only("struts")
-      .setRuleKey_unit_test_only("squid", "AvoidCycle")
+      .setComponentKey("Action.java")
+      .setRootComponentKey("struts")
+      .setRuleKey("squid", "AvoidCycle")
       .setStatus("OPEN").setResolution("OPEN");
     List<IssueDto> dtoList = newArrayList(issue1, issue2);
     when(issueDao.selectByIds(anyCollection(), any(SqlSession.class))).thenReturn(dtoList);
@@ -184,9 +184,9 @@ public class
     IssueQuery query = IssueQuery.builder().hideRules(true).build();
 
     IssueDto issue = new IssueDto().setId(1L).setRuleId(50).setComponentId(123l).setRootComponentId(100l)
-      .setComponentKey_unit_test_only("Action.java")
-      .setRootComponentKey_unit_test_only("struts")
-      .setRuleKey_unit_test_only("squid", "AvoidCycle")
+      .setComponentKey("Action.java")
+      .setRootComponentKey("struts")
+      .setRuleKey("squid", "AvoidCycle")
       .setStatus("OPEN").setResolution("OPEN");
     when(issueDao.selectByIds(anyCollection(), any(SqlSession.class))).thenReturn(newArrayList(issue));
 
@@ -204,14 +204,14 @@ public class
     IssueQuery query = IssueQuery.builder().build();
 
     IssueDto issue1 = new IssueDto().setId(1L).setRuleId(50).setComponentId(123l).setRootComponentId(100l)
-      .setComponentKey_unit_test_only("Action.java")
-      .setRootComponentKey_unit_test_only("struts")
-      .setRuleKey_unit_test_only("squid", "AvoidCycle")
+      .setComponentKey("Action.java")
+      .setRootComponentKey("struts")
+      .setRuleKey("squid", "AvoidCycle")
       .setStatus("OPEN").setResolution("OPEN");
     IssueDto issue2 = new IssueDto().setId(2L).setRuleId(50).setComponentId(123l).setRootComponentId(100l)
-      .setComponentKey_unit_test_only("Action.java")
-      .setRootComponentKey_unit_test_only("struts")
-      .setRuleKey_unit_test_only("squid", "AvoidCycle")
+      .setComponentKey("Action.java")
+      .setRootComponentKey("struts")
+      .setRuleKey("squid", "AvoidCycle")
       .setStatus("OPEN").setResolution("OPEN");
     List<IssueDto> dtoList = newArrayList(issue1, issue2);
     when(issueDao.selectByIds(anyCollection(), any(SqlSession.class))).thenReturn(dtoList);
@@ -231,14 +231,14 @@ public class
     IssueQuery query = IssueQuery.builder().build();
 
     IssueDto issue1 = new IssueDto().setId(1L).setRuleId(50).setComponentId(123l).setRootComponentId(100l)
-      .setComponentKey_unit_test_only("Action.java")
-      .setRootComponentKey_unit_test_only("struts")
-      .setRuleKey_unit_test_only("squid", "AvoidCycle")
+      .setComponentKey("Action.java")
+      .setRootComponentKey("struts")
+      .setRuleKey("squid", "AvoidCycle")
       .setStatus("OPEN").setResolution("OPEN");
     IssueDto issue2 = new IssueDto().setId(2L).setRuleId(50).setComponentId(123l).setRootComponentId(100l)
-      .setComponentKey_unit_test_only("Action.java")
-      .setRootComponentKey_unit_test_only("struts")
-      .setRuleKey_unit_test_only("squid", "AvoidCycle")
+      .setComponentKey("Action.java")
+      .setRootComponentKey("struts")
+      .setRuleKey("squid", "AvoidCycle")
       .setStatus("OPEN").setResolution("OPEN");
     List<IssueDto> dtoList = newArrayList(issue1, issue2);
     when(issueDao.selectByIds(anyCollection(), any(SqlSession.class))).thenReturn(dtoList);
@@ -258,14 +258,14 @@ public class
     IssueQuery query = IssueQuery.builder().build();
 
     IssueDto issue1 = new IssueDto().setId(1L).setRuleId(50).setComponentId(123l).setRootComponentId(100l).setKee("ABC").setActionPlanKey("A")
-      .setComponentKey_unit_test_only("Action.java")
-      .setRootComponentKey_unit_test_only("struts")
-      .setRuleKey_unit_test_only("squid", "AvoidCycle")
+      .setComponentKey("Action.java")
+      .setRootComponentKey("struts")
+      .setRuleKey("squid", "AvoidCycle")
       .setStatus("OPEN").setResolution("OPEN");
     IssueDto issue2 = new IssueDto().setId(2L).setRuleId(50).setComponentId(123l).setRootComponentId(100l).setKee("DEF").setActionPlanKey("B")
-      .setComponentKey_unit_test_only("Action.java")
-      .setRootComponentKey_unit_test_only("struts")
-      .setRuleKey_unit_test_only("squid", "AvoidCycle")
+      .setComponentKey("Action.java")
+      .setRootComponentKey("struts")
+      .setRuleKey("squid", "AvoidCycle")
       .setStatus("OPEN").setResolution("OPEN");
     List<IssueDto> dtoList = newArrayList(issue1, issue2);
     when(issueDao.selectByIds(anyCollection(), any(SqlSession.class))).thenReturn(dtoList);
@@ -288,10 +288,10 @@ public class
     IssueQuery query = IssueQuery.builder().build();
 
     IssueDto issue1 = new IssueDto().setId(1L).setRuleId(50).setComponentId(123l).setRootComponentId(100l).setKee("ABC").setAssignee("perceval")
-      .setRuleKey_unit_test_only("squid", "AvoidCycle")
+      .setRuleKey("squid", "AvoidCycle")
       .setStatus("OPEN").setResolution("OPEN");
     IssueDto issue2 = new IssueDto().setId(2L).setRuleId(50).setComponentId(123l).setRootComponentId(100l).setKee("DEF").setReporter("arthur")
-      .setRuleKey_unit_test_only("squid", "AvoidCycle")
+      .setRuleKey("squid", "AvoidCycle")
       .setStatus("OPEN").setResolution("OPEN");
     List<IssueDto> dtoList = newArrayList(issue1, issue2);
     when(issueDao.selectByIds(anyCollection(), any(SqlSession.class))).thenReturn(dtoList);
@@ -322,9 +322,9 @@ public class
     IssueQuery query = IssueQuery.builder().build();
 
     IssueDto issue = new IssueDto().setId(1L).setRuleId(50).setComponentId(123l).setRootComponentId(100l)
-      .setComponentKey_unit_test_only("Action.java")
-      .setRootComponentKey_unit_test_only("struts")
-      .setRuleKey_unit_test_only("squid", "AvoidCycle")
+      .setComponentKey("Action.java")
+      .setRootComponentKey("struts")
+      .setRuleKey("squid", "AvoidCycle")
       .setStatus("OPEN").setResolution("OPEN")
       .setDebt(10L);
     List<IssueDto> dtoList = newArrayList(issue);
index 9a2cd6f98cf8b34282d5a33b2fce5c7068d9994e..1ce4db1a1b780470b69630d2f2cded5985bd57da 100644 (file)
@@ -23,17 +23,23 @@ package org.sonar.server.issue;
 import org.junit.Before;
 import org.junit.Test;
 import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.issue.internal.DefaultIssueComment;
+import org.sonar.api.issue.internal.IssueChangeContext;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rules.Rule;
 import org.sonar.api.rules.RuleFinder;
 import org.sonar.api.rules.RuleQuery;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.api.utils.Duration;
 import org.sonar.api.utils.System2;
 import org.sonar.core.persistence.AbstractDaoTestCase;
 import org.sonar.core.resource.ResourceDao;
 import org.sonar.server.component.db.ComponentDao;
 import org.sonar.server.db.DbClient;
+import org.sonar.server.issue.db.IssueDao;
 
 import java.util.Collection;
+import java.util.Date;
 
 import static org.fest.assertions.Assertions.assertThat;
 import static org.fest.assertions.Fail.fail;
@@ -42,18 +48,22 @@ public class ServerIssueStorageTest extends AbstractDaoTestCase {
 
   DbClient dbClient;
 
+  ServerIssueStorage storage;
+
   @Before
   public void setupDbClient() {
     dbClient = new DbClient(getDatabase(), getMyBatis(),
       new ComponentDao(System2.INSTANCE),
+      new IssueDao(System2.INSTANCE),
       new ResourceDao(getMyBatis(), System2.INSTANCE));
+
+    storage = new ServerIssueStorage(getMyBatis(), new FakeRuleFinder(), dbClient);
   }
 
   @Test
   public void load_component_id_from_db() throws Exception {
     setupData("load_component_id_from_db");
 
-    ServerIssueStorage storage = new ServerIssueStorage(getMyBatis(), new FakeRuleFinder(), dbClient);
     long componentId = storage.componentId(new DefaultIssue().setComponentKey("struts:Action.java"));
 
     assertThat(componentId).isEqualTo(123);
@@ -63,7 +73,6 @@ public class ServerIssueStorageTest extends AbstractDaoTestCase {
   public void fail_to_load_component_id_if_unknown_component() throws Exception {
     setupData("empty");
 
-    ServerIssueStorage storage = new ServerIssueStorage(getMyBatis(), new FakeRuleFinder(), dbClient);
     try {
       storage.componentId(new DefaultIssue().setComponentKey("struts:Action.java"));
       fail();
@@ -76,7 +85,6 @@ public class ServerIssueStorageTest extends AbstractDaoTestCase {
   public void load_project_id_from_db() throws Exception {
     setupData("load_project_id_from_db");
 
-    ServerIssueStorage storage = new ServerIssueStorage(getMyBatis(), new FakeRuleFinder(), dbClient);
     long projectId = storage.projectId(new DefaultIssue().setProjectKey("struts"));
 
     assertThat(projectId).isEqualTo(1);
@@ -86,7 +94,6 @@ public class ServerIssueStorageTest extends AbstractDaoTestCase {
   public void fail_to_load_project_id_if_unknown_component() throws Exception {
     setupData("empty");
 
-    ServerIssueStorage storage = new ServerIssueStorage(getMyBatis(), new FakeRuleFinder(), dbClient);
     try {
       storage.projectId(new DefaultIssue().setProjectKey("struts"));
       fail();
@@ -95,6 +102,82 @@ public class ServerIssueStorageTest extends AbstractDaoTestCase {
     }
   }
 
+  @Test
+  public void should_insert_new_issues() throws Exception {
+    setupData("should_insert_new_issues");
+
+    DefaultIssueComment comment = DefaultIssueComment.create("ABCDE", "emmerik", "the comment");
+    // override generated key
+    comment.setKey("FGHIJ");
+
+    Date date = DateUtils.parseDate("2013-05-18");
+    DefaultIssue issue = new DefaultIssue()
+      .setKey("ABCDE")
+      .setNew(true)
+
+      .setRuleKey(RuleKey.of("squid", "AvoidCycle"))
+      .setLine(5000)
+      .setDebt(Duration.create(10L))
+      .setReporter("emmerik")
+      .setResolution("OPEN")
+      .setStatus("OPEN")
+      .setSeverity("BLOCKER")
+      .setAttribute("foo", "bar")
+      .addComment(comment)
+      .setCreationDate(date)
+      .setUpdateDate(date)
+      .setCloseDate(date)
+
+      .setComponentKey("struts:Action");
+
+    storage.save(issue);
+
+    checkTables("should_insert_new_issues", new String[]{"id", "created_at", "updated_at", "issue_change_creation_date"}, "issues", "issue_changes");
+  }
+
+  @Test
+  public void should_update_issues() throws Exception {
+    setupData("should_update_issues");
+
+    IssueChangeContext context = IssueChangeContext.createUser(new Date(), "emmerik");
+
+    DefaultIssueComment comment = DefaultIssueComment.create("ABCDE", "emmerik", "the comment");
+    // override generated key
+    comment.setKey("FGHIJ");
+
+    Date date = DateUtils.parseDate("2013-05-18");
+    DefaultIssue issue = new DefaultIssue()
+      .setKey("ABCDE")
+      .setNew(false)
+      .setChanged(true)
+
+        // updated fields
+      .setLine(5000)
+      .setDebt(Duration.create(10L))
+      .setChecksum("FFFFF")
+      .setAuthorLogin("simon")
+      .setAssignee("loic")
+      .setFieldChange(context, "severity", "INFO", "BLOCKER")
+      .setReporter("emmerik")
+      .setResolution("FIXED")
+      .setStatus("RESOLVED")
+      .setSeverity("BLOCKER")
+      .setAttribute("foo", "bar")
+      .addComment(comment)
+      .setCreationDate(date)
+      .setUpdateDate(date)
+      .setCloseDate(date)
+
+        // unmodifiable fields
+      .setRuleKey(RuleKey.of("xxx", "unknown"))
+      .setComponentKey("struts:Action")
+      .setProjectKey("struts");
+
+    storage.save(issue);
+
+    checkTables("should_update_issues", new String[]{"id", "created_at", "updated_at", "issue_change_creation_date"}, "issues", "issue_changes");
+  }
+
   static class FakeRuleFinder implements RuleFinder {
 
     @Override
index a7bb7ec50f33f662e590011fd4573a02399c209c..be984e7cf0ab96e6acb904e593f04d307b04f6a5 100644 (file)
@@ -144,7 +144,7 @@ public class ActionPlanServiceTest {
     when(actionPlanDao.findByKey("ABCD")).thenReturn(new ActionPlanDto().setKey("ABCD").setProjectKey_unit_test_only(projectKey));
     when(resourceDao.getResource(any(ResourceQuery.class))).thenReturn(new ResourceDto().setKey(projectKey).setId(1l));
 
-    IssueDto issueDto = new IssueDto().setId(100L).setStatus(Issue.STATUS_OPEN).setRuleKey_unit_test_only("squid", "s100");
+    IssueDto issueDto = new IssueDto().setId(100L).setStatus(Issue.STATUS_OPEN).setRuleKey("squid", "s100");
     when(issueDao.selectIssues(any(IssueQuery.class))).thenReturn(newArrayList(issueDto));
     when(issueUpdater.plan(any(DefaultIssue.class), eq((ActionPlan) null), any(IssueChangeContext.class))).thenReturn(true);
 
index 80fe1f10446793e05035e9b94829b41d99754d1f..b2a231eb6943aeff0e59a7455acdf6c82116ee15 100644 (file)
@@ -26,8 +26,11 @@ import org.junit.ClassRule;
 import org.junit.Test;
 import org.sonar.api.issue.Issue;
 import org.sonar.api.rule.RuleKey;
+import org.sonar.api.security.DefaultGroups;
+import org.sonar.api.web.UserRole;
 import org.sonar.core.component.ComponentDto;
 import org.sonar.core.issue.db.IssueDto;
+import org.sonar.core.permission.PermissionFacade;
 import org.sonar.core.persistence.DbSession;
 import org.sonar.core.rule.RuleDto;
 import org.sonar.server.component.db.ComponentDao;
@@ -81,6 +84,10 @@ public class IssueBackendMediumTest {
       .setProjectId(1L);
     tester.get(ComponentDao.class).insert(dbSession, project);
 
+    // project can be seen by anyone
+    tester.get(PermissionFacade.class).insertGroupPermission(project.getId(), DefaultGroups.ANYONE, UserRole.USER, dbSession);
+    dbClient.issueAuthorizationDao().synchronizeAfter(dbSession, new Date(0));
+
     ComponentDto resource = new ComponentDto()
       .setProjectId(1L)
       .setKey("MyComponent")
@@ -105,7 +112,6 @@ public class IssueBackendMediumTest {
 
     // should find by key
     Issue issueDoc = indexClient.get(IssueIndex.class).getByKey(issue.getKey());
-    assertThat(issueDoc).isNotNull();
 
     // Check all normalized fields
     assertThat(issueDoc.actionPlanKey()).isEqualTo(issue.getActionPlanKey());
@@ -150,9 +156,9 @@ public class IssueBackendMediumTest {
     IssueDto issue = new IssueDto().setId(1L)
       .setRuleId(rule.getId())
       .setRootComponentId(project.getId())
-      .setRootComponentKey_unit_test_only(project.key())
+      .setRootComponentKey(project.key())
       .setComponentId(resource.getId())
-      .setComponentKey_unit_test_only(resource.key())
+      .setComponentKey(resource.key())
       .setStatus("OPEN").setResolution("OPEN")
       .setKee(UUID.randomUUID().toString());
     dbClient.issueDao().insert(dbSession, issue);
index 6d3ca263618eac2190d8309cdb6461e7583a4e0a..0895eedaa5be7ed19d48ec43c45ea7d5f5a1ce6b 100644 (file)
@@ -82,7 +82,7 @@ public class IssueAuthorizationIndexMediumTest {
 
     session.commit();
 
-    assertThat(index.getByKey(project.getKey())).isNull();
+    assertThat(index.getNullableByKey(project.getKey())).isNull();
     db.issueAuthorizationDao().synchronizeAfter(session, new Date(0));
     session.commit();
 
@@ -96,7 +96,7 @@ public class IssueAuthorizationIndexMediumTest {
 
     tester.clearIndexes();
     tester.get(Platform.class).executeStartupTasks();
-    assertThat(index.getByKey(project.getKey())).isNotNull();
+    assertThat(index.getNullableByKey(project.getKey())).isNotNull();
   }
 
   @Test
@@ -120,12 +120,12 @@ public class IssueAuthorizationIndexMediumTest {
 
     db.issueAuthorizationDao().synchronizeAfter(session, new Date(0));
     session.commit();
-    assertThat(index.getByKey(project.getKey())).isNotNull();
+    assertThat(index.getNullableByKey(project.getKey())).isNotNull();
 
     db.issueAuthorizationDao().deleteByKey(session, project.key());
     session.commit();
 
-    assertThat(index.getByKey(project.getKey())).isNull();
+    assertThat(index.getNullableByKey(project.getKey())).isNull();
   }
 
 }
index fcdc2b7e3fd2f1ca5836c5cb784e8224aabcc6c1..fe9bd4652619ed2d5f18b5663c54a5cde5ae4cd5 100644 (file)
@@ -38,6 +38,7 @@ import org.sonar.core.user.GroupDto;
 import org.sonar.core.user.UserDto;
 import org.sonar.server.component.db.ComponentDao;
 import org.sonar.server.db.DbClient;
+import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.rule.RuleTesting;
 import org.sonar.server.rule.db.RuleDao;
 import org.sonar.server.search.QueryContext;
@@ -101,7 +102,21 @@ public class IssueIndexMediumTest {
   }
 
   @Test
-  public void filter_by_actionPlan() throws Exception {
+  public void get_by_key() throws Exception {
+    IssueDto issue = createIssue();
+    db.issueDao().insert(session, issue);
+    session.commit();
+
+    assertThat(index.getByKey(issue.getKey())).isNotNull();
+  }
+
+  @Test(expected = NotFoundException.class)
+  public void fail_to_get_unknown_key() throws Exception {
+    index.getByKey("unknown");
+  }
+
+  @Test
+  public void filter_by_action_plan() throws Exception {
     String plan1 = "plan1";
     String plan2 = "plan2";
     IssueDto issue1 = createIssue()
index 1aaa07ce01551078044dc9360b0bd990e3304dd6..0ac268f91e892e2241cf73b508a596fd1a781460 100644 (file)
@@ -84,13 +84,13 @@ public class InternalPermissionServiceMediumTest {
     MockUserSession.set().setLogin("admin").addProjectPermissions(UserRole.ADMIN, project.key());
 
     assertThat(tester.get(RoleDao.class).selectUserPermissions(session, user.getLogin(), project.getId())).isEmpty();
-    assertThat(index.getByKey(project.getKey())).isNull();
+    assertThat(index.getNullableByKey(project.getKey())).isNull();
 
     service.addPermission(params(user.getLogin(), null, project.key(), UserRole.USER));
     session.commit();
 
     assertThat(tester.get(RoleDao.class).selectUserPermissions(session, user.getLogin(), project.getId())).hasSize(1);
-    assertThat(index.getByKey(project.getKey())).isNotNull();
+    assertThat(index.getNullableByKey(project.getKey())).isNotNull();
   }
 
   private Map<String, Object> params(@Nullable String login, @Nullable String group, @Nullable String component, String permission) {
index a730ff72db117d1d73eef55034480ff4e6407a0c..81907de87f239466cf86a06e301c7af51885318c 100644 (file)
@@ -64,23 +64,23 @@ public class ActiveRuleBackendMediumTest extends SearchMediumTest {
 
     // 1. Synchronize since 0
     tester.clearIndexes();
-    assertThat(index.get(ActiveRuleIndex.class).getByKey(activeRule.getKey())).isNull();
+    assertThat(index.get(ActiveRuleIndex.class).getNullableByKey(activeRule.getKey())).isNull();
     db.activeRuleDao().synchronizeAfter(dbSession, new Date(0L));
     dbSession.commit();
-    assertThat(index.get(ActiveRuleIndex.class).getByKey(activeRule.getKey())).isNotNull();
+    assertThat(index.get(ActiveRuleIndex.class).getNullableByKey(activeRule.getKey())).isNotNull();
 
     // 2. Synchronize since beginning
     tester.clearIndexes();
-    assertThat(index.get(ActiveRuleIndex.class).getByKey(activeRule.getKey())).isNull();
+    assertThat(index.get(ActiveRuleIndex.class).getNullableByKey(activeRule.getKey())).isNull();
     db.activeRuleDao().synchronizeAfter(dbSession, beginning);
     dbSession.commit();
-    assertThat(index.get(ActiveRuleIndex.class).getByKey(activeRule.getKey())).isNotNull();
+    assertThat(index.get(ActiveRuleIndex.class).getNullableByKey(activeRule.getKey())).isNotNull();
 
     // 3. Assert startup picks it up
     tester.clearIndexes();
     Date before = index.get(ActiveRuleIndex.class).getLastSynchronization();
     tester.get(Platform.class).executeStartupTasks();
-    assertThat(index.get(ActiveRuleIndex.class).getByKey(activeRule.getKey())).isNotNull();
+    assertThat(index.get(ActiveRuleIndex.class).getNullableByKey(activeRule.getKey())).isNotNull();
     assertThat(before.before(index.get(ActiveRuleIndex.class).getLastSynchronization())).isTrue();
   }
 
index 1c22edba15bcdda2a40202b937d2b8328de5a817..39d73517b8e7993f1e3d6f6d1ff506e440b69696 100644 (file)
@@ -82,14 +82,14 @@ public class RegisterQualityProfilesMediumTest {
     ActiveRuleKey activeRuleKey = ActiveRuleKey.of(profile.getKey(), ruleKey);
 
     // 0. Check and clear ES
-    assertThat(tester.get(ActiveRuleIndex.class).getByKey(activeRuleKey)).isNotNull();
+    assertThat(tester.get(ActiveRuleIndex.class).getNullableByKey(activeRuleKey)).isNotNull();
     tester.clearIndexes();
-    assertThat(tester.get(ActiveRuleIndex.class).getByKey(activeRuleKey)).isNull();
+    assertThat(tester.get(ActiveRuleIndex.class).getNullableByKey(activeRuleKey)).isNull();
     tester.get(Platform.class).restart();
-    assertThat(tester.get(ActiveRuleIndex.class).getByKey(activeRuleKey)).isNotNull();
+    assertThat(tester.get(ActiveRuleIndex.class).getNullableByKey(activeRuleKey)).isNotNull();
 
     // Check ActiveRules in ES
-    org.sonar.server.qualityprofile.ActiveRule activeRule = tester.get(ActiveRuleIndex.class).getByKey(activeRuleKey);
+    org.sonar.server.qualityprofile.ActiveRule activeRule = tester.get(ActiveRuleIndex.class).getNullableByKey(activeRuleKey);
     assertThat(activeRule.key().qProfile()).isEqualTo(profile.getKee());
     assertThat(activeRule.key().ruleKey()).isEqualTo(ruleKey);
     assertThat(activeRule.severity()).isEqualTo(Severity.CRITICAL);
index 4a0e06311323632461ad9c0f701577bd5afd5e11..0f0808ae47b25696216b468c6821ca3fa440b10c 100644 (file)
@@ -82,7 +82,7 @@ public class RuleIndexMediumTest extends SearchMediumTest {
 
   @Test
   public void getByKey_null_if_not_found() throws InterruptedException {
-    Rule rule = index.getByKey(RuleKey.of("javascript", "unknown"));
+    Rule rule = index.getNullableByKey(RuleKey.of("javascript", "unknown"));
 
     assertThat(rule).isNull();
   }
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/issue/ServerIssueStorageTest/should_insert_new_issues-result.xml b/server/sonar-server/src/test/resources/org/sonar/server/issue/ServerIssueStorageTest/should_insert_new_issues-result.xml
new file mode 100644 (file)
index 0000000..59d288b
--- /dev/null
@@ -0,0 +1,26 @@
+<dataset>
+  <issues id="1" kee="ABCDE" resolution="OPEN" status="OPEN" severity="BLOCKER" manual_severity="[false]"
+      assignee="[null]"
+      author_login="[null]"
+      checksum="[null]"
+      effort_to_fix="[null]"
+      technical_debt="10"
+      message="[null]"
+      line="5000"
+      component_id="100"
+      root_component_id="10"
+      rule_id="200"
+      created_at="[null]"
+      updated_at="[null]"
+      reporter="emmerik"
+      issue_attributes="foo=bar"
+      action_plan_key="[null]"
+      issue_creation_date="2013-05-18"
+      issue_update_date="2013-05-18"
+      issue_close_date="2013-05-18"
+      />
+
+  <issue_changes id="1" kee="FGHIJ" issue_key="ABCDE" change_type="comment" user_login="emmerik" change_data="the comment"
+                 created_at="[null]" updated_at="[null]" issue_change_creation_date="[null]" />
+
+</dataset>
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/issue/ServerIssueStorageTest/should_insert_new_issues.xml b/server/sonar-server/src/test/resources/org/sonar/server/issue/ServerIssueStorageTest/should_insert_new_issues.xml
new file mode 100644 (file)
index 0000000..683ead4
--- /dev/null
@@ -0,0 +1,4 @@
+<dataset>
+  <projects id="10" scope="PRJ" qualifier="TRK" kee="struts" name="Struts"/>
+  <projects id="100" scope="FIL" qualifier="CLA" kee="struts:Action" name="Action"/>
+</dataset>
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/issue/ServerIssueStorageTest/should_update_issues-result.xml b/server/sonar-server/src/test/resources/org/sonar/server/issue/ServerIssueStorageTest/should_update_issues-result.xml
new file mode 100644 (file)
index 0000000..82db8ee
--- /dev/null
@@ -0,0 +1,32 @@
+<dataset>
+  <issues id="1"
+          kee="ABCDE"
+          resolution="FIXED"
+          status="RESOLVED"
+          severity="BLOCKER"
+          manual_severity="[false]"
+          assignee="loic"
+          author_login="simon"
+          checksum="FFFFF"
+          effort_to_fix="[null]"
+          technical_debt="10"
+          message="[null]"
+          line="5000"
+          component_id="100"
+          root_component_id="10"
+          rule_id="200"
+          created_at="2013-05-18"
+          updated_at="2013-05-18"
+          reporter="emmerik"
+          issue_attributes="foo=bar"
+          action_plan_key="[null]"
+          issue_creation_date="2013-05-18 00:00:00.0"
+          issue_update_date="2013-05-18 00:00:00.0"
+          issue_close_date="2013-05-18 00:00:00.0"
+    />
+
+  <issue_changes id="1" kee="FGHIJ" issue_key="ABCDE" change_type="comment" user_login="emmerik"
+                 change_data="the comment" created_at="[null]" updated_at="[null]" issue_change_creation_date="[null]"/>
+  <issue_changes id="2" kee="[null]" issue_key="ABCDE" change_type="diff" user_login="emmerik"
+                 change_data="severity=INFO|BLOCKER" created_at="[null]" updated_at="[null]" issue_change_creation_date="[null]"/>
+</dataset>
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/issue/ServerIssueStorageTest/should_update_issues.xml b/server/sonar-server/src/test/resources/org/sonar/server/issue/ServerIssueStorageTest/should_update_issues.xml
new file mode 100644 (file)
index 0000000..8b552f4
--- /dev/null
@@ -0,0 +1,31 @@
+<dataset>
+
+  <projects id="10" scope="PRJ" qualifier="TRK" kee="struts" name="Struts"/>
+  <projects id="100" scope="FIL" qualifier="CLA" kee="struts:Action" name="Action"/>
+
+  <issues id="1"
+          kee="ABCDE"
+          resolution="OPEN"
+          status="OPEN"
+          severity="BLOCKER"
+          manual_severity="[false]"
+          assignee="loic"
+          author_login="simon"
+          checksum="FFFFF"
+          effort_to_fix="[null]"
+          technical_debt="[null]"
+          message="[null]"
+          line="3000"
+          component_id="100"
+          root_component_id="10"
+          rule_id="200"
+          created_at="2010-01-01"
+          updated_at="2011-02-02"
+          reporter="emmerik"
+          issue_attributes="foo=bar"
+          action_plan_key="[null]"
+          issue_creation_date="2010-01-01"
+          issue_update_date="2010-02-02"
+          issue_close_date="[null]"
+    />
+</dataset>
index 723082eb3355e6e99fa089b72dc5a9ce7181096b..9f3ae235db83d331c10e97375a860a00e2cc73f5 100644 (file)
@@ -21,21 +21,29 @@ package org.sonar.batch.issue;
 
 import org.sonar.api.BatchComponent;
 import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.issue.Issue;
 import org.sonar.api.issue.internal.DefaultIssue;
 import org.sonar.api.rules.RuleFinder;
 import org.sonar.batch.ProjectTree;
 import org.sonar.batch.index.SnapshotCache;
+import org.sonar.core.issue.db.IssueDto;
+import org.sonar.core.issue.db.IssueMapper;
 import org.sonar.core.issue.db.IssueStorage;
+import org.sonar.core.issue.db.UpdateConflictResolver;
+import org.sonar.core.persistence.DbSession;
 import org.sonar.core.persistence.MyBatis;
 import org.sonar.core.resource.ResourceDao;
 import org.sonar.core.resource.ResourceDto;
 import org.sonar.core.resource.ResourceQuery;
 
+import java.util.Date;
+
 public class ScanIssueStorage extends IssueStorage implements BatchComponent {
 
   private final SnapshotCache snapshotCache;
   private final ResourceDao resourceDao;
   private final ProjectTree projectTree;
+  private final UpdateConflictResolver conflictResolver = new UpdateConflictResolver();
 
   public ScanIssueStorage(MyBatis mybatis, RuleFinder ruleFinder, SnapshotCache snapshotCache, ResourceDao resourceDao, ProjectTree projectTree) {
     super(mybatis, ruleFinder);
@@ -44,7 +52,32 @@ public class ScanIssueStorage extends IssueStorage implements BatchComponent {
     this.projectTree = projectTree;
   }
 
-  @Override
+  protected void doInsert(DbSession session, Date now, DefaultIssue issue) {
+    IssueMapper issueMapper = session.getMapper(IssueMapper.class);
+    long componentId = componentId(issue);
+    long projectId = projectId(issue);
+    int ruleId = ruleId(issue);
+    IssueDto dto = IssueDto.toDtoForInsert(issue, componentId, projectId, ruleId, now);
+    issueMapper.insert(dto);
+  }
+
+  protected void doUpdate(DbSession session, Date now, DefaultIssue issue) {
+    IssueMapper issueMapper = session.getMapper(IssueMapper.class);
+    IssueDto dto = IssueDto.toDtoForUpdate(issue, projectId(issue), now);
+    if (Issue.STATUS_CLOSED.equals(issue.status()) || issue.selectedAt() == null) {
+      // Issue is closed by scan or changed by end-user
+      issueMapper.update(dto);
+
+    } else {
+      int count = issueMapper.updateIfBeforeSelectedDate(dto);
+      if (count == 0) {
+        // End-user and scan changed the issue at the same time.
+        // See https://jira.codehaus.org/browse/SONAR-4309
+        conflictResolver.resolve(issue, issueMapper);
+      }
+    }
+  }
+
   protected long componentId(DefaultIssue issue) {
     Snapshot snapshot = snapshotCache.get(issue.componentKey());
     if (snapshot != null) {
@@ -59,7 +92,6 @@ public class ScanIssueStorage extends IssueStorage implements BatchComponent {
     return resourceDto.getId();
   }
 
-  @Override
   protected long projectId(DefaultIssue issue) {
     return projectTree.getRootProject().getId();
   }
index 1d0144ce4eeb22374d575fb6dd286905453956ec..983d560d70db35aa40da23ca65324ac81aaf1d0e 100644 (file)
@@ -26,11 +26,15 @@ import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
 import org.sonar.api.database.model.Snapshot;
 import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.issue.internal.DefaultIssueComment;
+import org.sonar.api.issue.internal.IssueChangeContext;
 import org.sonar.api.resources.Project;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rules.Rule;
 import org.sonar.api.rules.RuleFinder;
 import org.sonar.api.rules.RuleQuery;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.api.utils.Duration;
 import org.sonar.api.utils.System2;
 import org.sonar.batch.ProjectTree;
 import org.sonar.batch.index.SnapshotCache;
@@ -38,6 +42,7 @@ import org.sonar.core.persistence.AbstractDaoTestCase;
 import org.sonar.core.resource.ResourceDao;
 
 import java.util.Collection;
+import java.util.Date;
 
 import static org.fest.assertions.Assertions.assertThat;
 import static org.fest.assertions.Fail.fail;
@@ -100,6 +105,126 @@ public class ScanIssueStorageTest extends AbstractDaoTestCase {
     assertThat(projectId).isEqualTo(100);
   }
 
+  @Test
+  public void should_insert_new_issues() throws Exception {
+    setupData("should_insert_new_issues");
+
+    Project project = new Project("struts");
+    project.setId(10);
+    when(projectTree.getRootProject()).thenReturn(project);
+
+    DefaultIssueComment comment = DefaultIssueComment.create("ABCDE", "emmerik", "the comment");
+    // override generated key
+    comment.setKey("FGHIJ");
+
+    Date date = DateUtils.parseDate("2013-05-18");
+    DefaultIssue issue = new DefaultIssue()
+      .setKey("ABCDE")
+      .setNew(true)
+
+      .setRuleKey(RuleKey.of("squid", "AvoidCycle"))
+      .setLine(5000)
+      .setDebt(Duration.create(10L))
+      .setReporter("emmerik")
+      .setResolution("OPEN")
+      .setStatus("OPEN")
+      .setSeverity("BLOCKER")
+      .setAttribute("foo", "bar")
+      .addComment(comment)
+      .setCreationDate(date)
+      .setUpdateDate(date)
+      .setCloseDate(date)
+
+      .setComponentKey("struts:Action");
+
+    storage.save(issue);
+
+    checkTables("should_insert_new_issues", new String[]{"id", "created_at", "updated_at", "issue_change_creation_date"}, "issues", "issue_changes");
+  }
+
+  @Test
+  public void should_update_issues() throws Exception {
+    setupData("should_update_issues");
+
+    IssueChangeContext context = IssueChangeContext.createUser(new Date(), "emmerik");
+
+    Project project = new Project("struts");
+    project.setId(10);
+    when(projectTree.getRootProject()).thenReturn(project);
+
+    DefaultIssueComment comment = DefaultIssueComment.create("ABCDE", "emmerik", "the comment");
+    // override generated key
+    comment.setKey("FGHIJ");
+
+    Date date = DateUtils.parseDate("2013-05-18");
+    DefaultIssue issue = new DefaultIssue()
+      .setKey("ABCDE")
+      .setNew(false)
+      .setChanged(true)
+
+        // updated fields
+      .setLine(5000)
+      .setDebt(Duration.create(10L))
+      .setChecksum("FFFFF")
+      .setAuthorLogin("simon")
+      .setAssignee("loic")
+      .setFieldChange(context, "severity", "INFO", "BLOCKER")
+      .setReporter("emmerik")
+      .setResolution("FIXED")
+      .setStatus("RESOLVED")
+      .setSeverity("BLOCKER")
+      .setAttribute("foo", "bar")
+      .addComment(comment)
+      .setCreationDate(date)
+      .setUpdateDate(date)
+      .setCloseDate(date)
+
+        // unmodifiable fields
+      .setRuleKey(RuleKey.of("xxx", "unknown"))
+      .setComponentKey("not:a:component");
+
+    storage.save(issue);
+
+    checkTables("should_update_issues", new String[]{"id", "created_at", "updated_at", "issue_change_creation_date"}, "issues", "issue_changes");
+  }
+
+  @Test
+  public void should_resolve_conflicts_on_updates() throws Exception {
+    setupData("should_resolve_conflicts_on_updates");
+
+    Project project = new Project("struts");
+    project.setId(10);
+    when(projectTree.getRootProject()).thenReturn(project);
+
+    Date date = DateUtils.parseDate("2013-05-18");
+    DefaultIssue issue = new DefaultIssue()
+      .setKey("ABCDE")
+      .setNew(false)
+      .setChanged(true)
+      .setCreationDate(DateUtils.parseDate("2005-05-12"))
+      .setUpdateDate(date)
+      .setRuleKey(RuleKey.of("squid", "AvoidCycles"))
+      .setComponentKey("struts:Action")
+
+        // issue in database has been updated in 2013, after the loading by scan
+      .setSelectedAt(DateUtils.parseDate("2005-01-01"))
+
+        // fields to be updated
+      .setLine(444)
+      .setSeverity("BLOCKER")
+      .setChecksum("FFFFF")
+      .setAttribute("JIRA", "http://jira.com")
+
+        // fields overridden by end-user -> do not save
+      .setAssignee("looser")
+      .setResolution(null)
+      .setStatus("REOPEN");
+
+    storage.save(issue);
+
+    checkTables("should_resolve_conflicts_on_updates", new String[]{"id", "created_at", "updated_at", "issue_change_creation_date"}, "issues");
+  }
+
   static class FakeRuleFinder implements RuleFinder {
 
     @Override
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/issue/ScanIssueStorageTest/should_insert_new_issues-result.xml b/sonar-batch/src/test/resources/org/sonar/batch/issue/ScanIssueStorageTest/should_insert_new_issues-result.xml
new file mode 100644 (file)
index 0000000..59d288b
--- /dev/null
@@ -0,0 +1,26 @@
+<dataset>
+  <issues id="1" kee="ABCDE" resolution="OPEN" status="OPEN" severity="BLOCKER" manual_severity="[false]"
+      assignee="[null]"
+      author_login="[null]"
+      checksum="[null]"
+      effort_to_fix="[null]"
+      technical_debt="10"
+      message="[null]"
+      line="5000"
+      component_id="100"
+      root_component_id="10"
+      rule_id="200"
+      created_at="[null]"
+      updated_at="[null]"
+      reporter="emmerik"
+      issue_attributes="foo=bar"
+      action_plan_key="[null]"
+      issue_creation_date="2013-05-18"
+      issue_update_date="2013-05-18"
+      issue_close_date="2013-05-18"
+      />
+
+  <issue_changes id="1" kee="FGHIJ" issue_key="ABCDE" change_type="comment" user_login="emmerik" change_data="the comment"
+                 created_at="[null]" updated_at="[null]" issue_change_creation_date="[null]" />
+
+</dataset>
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/issue/ScanIssueStorageTest/should_insert_new_issues.xml b/sonar-batch/src/test/resources/org/sonar/batch/issue/ScanIssueStorageTest/should_insert_new_issues.xml
new file mode 100644 (file)
index 0000000..683ead4
--- /dev/null
@@ -0,0 +1,4 @@
+<dataset>
+  <projects id="10" scope="PRJ" qualifier="TRK" kee="struts" name="Struts"/>
+  <projects id="100" scope="FIL" qualifier="CLA" kee="struts:Action" name="Action"/>
+</dataset>
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/issue/ScanIssueStorageTest/should_resolve_conflicts_on_updates-result.xml b/sonar-batch/src/test/resources/org/sonar/batch/issue/ScanIssueStorageTest/should_resolve_conflicts_on_updates-result.xml
new file mode 100644 (file)
index 0000000..26b0dd5
--- /dev/null
@@ -0,0 +1,33 @@
+<dataset>
+  <rules tags="[null]" system_tags="[null]" id="200" name="Avoid Cycles" plugin_rule_key="AvoidCycles"
+         plugin_config_key="[null]" plugin_name="squid"/>
+
+  <projects id="10" scope="PRJ" qualifier="TRK" kee="struts" name="Struts"/>
+  <projects id="100" scope="FIL" qualifier="CLA" kee="struts:Action" name="Action"/>
+
+  <issues id="1"
+          kee="ABCDE"
+          resolution="FIXED"
+          status="RESOLVED"
+          severity="BLOCKER"
+          manual_severity="[false]"
+          assignee="winner"
+          author_login="[null]"
+          checksum="FFFFF"
+          effort_to_fix="[null]"
+          technical_debt="[null]"
+          message="[null]"
+          line="444"
+          component_id="100"
+          root_component_id="10"
+          rule_id="200"
+          reporter="[null]"
+          issue_attributes="JIRA=http://jira.com"
+          action_plan_key="[null]"
+          created_at="2005-05-12"
+          updated_at="2013-05-18"
+          issue_creation_date="2005-05-12 00:00:00.0"
+          issue_update_date="2013-05-18 00:00:00.0"
+          issue_close_date="[null]"
+    />
+</dataset>
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/issue/ScanIssueStorageTest/should_resolve_conflicts_on_updates.xml b/sonar-batch/src/test/resources/org/sonar/batch/issue/ScanIssueStorageTest/should_resolve_conflicts_on_updates.xml
new file mode 100644 (file)
index 0000000..c1058c7
--- /dev/null
@@ -0,0 +1,54 @@
+<!--
+  ~ SonarQube, open source software quality management tool.
+  ~ Copyright (C) 2008-2014 SonarSource
+  ~ mailto:contact AT sonarsource DOT com
+  ~
+  ~ SonarQube is free software; you can redistribute it and/or
+  ~ modify it under the terms of the GNU Lesser General Public
+  ~ License as published by the Free Software Foundation; either
+  ~ version 3 of the License, or (at your option) any later version.
+  ~
+  ~ SonarQube is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  ~ Lesser General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU Lesser General Public License
+  ~ along with this program; if not, write to the Free Software Foundation,
+  ~ Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+  -->
+
+<dataset>
+
+  <rules tags="[null]" system_tags="[null]" id="200" name="Avoid Cycles" plugin_rule_key="AvoidCycles"
+         plugin_config_key="[null]" plugin_name="squid" />
+
+  <projects id="10" scope="PRJ" qualifier="TRK" kee="struts" name="Struts"/>
+  <projects id="100" scope="FIL" qualifier="CLA" kee="struts:Action" name="Action"/>
+
+  <issues id="1"
+          kee="ABCDE"
+          assignee="winner"
+          resolution="FIXED"
+          status="RESOLVED"
+          severity="MAJOR"
+          manual_severity="[false]"
+          author_login="[null]"
+          checksum="FFFFF"
+          effort_to_fix="[null]"
+          technical_debt="[null]"
+          message="[null]"
+          line="1"
+          component_id="100"
+          root_component_id="10"
+          rule_id="200"
+          reporter="[null]"
+          issue_attributes=""
+          action_plan_key="[null]"
+          created_at="2005-05-12"
+          updated_at="2013-05-18"
+          issue_creation_date="2005-05-12 00:00:00.0"
+          issue_update_date="2013-05-18 00:00:00.0"
+          issue_close_date="[null]"
+    />
+</dataset>
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/issue/ScanIssueStorageTest/should_update_issues-result.xml b/sonar-batch/src/test/resources/org/sonar/batch/issue/ScanIssueStorageTest/should_update_issues-result.xml
new file mode 100644 (file)
index 0000000..82db8ee
--- /dev/null
@@ -0,0 +1,32 @@
+<dataset>
+  <issues id="1"
+          kee="ABCDE"
+          resolution="FIXED"
+          status="RESOLVED"
+          severity="BLOCKER"
+          manual_severity="[false]"
+          assignee="loic"
+          author_login="simon"
+          checksum="FFFFF"
+          effort_to_fix="[null]"
+          technical_debt="10"
+          message="[null]"
+          line="5000"
+          component_id="100"
+          root_component_id="10"
+          rule_id="200"
+          created_at="2013-05-18"
+          updated_at="2013-05-18"
+          reporter="emmerik"
+          issue_attributes="foo=bar"
+          action_plan_key="[null]"
+          issue_creation_date="2013-05-18 00:00:00.0"
+          issue_update_date="2013-05-18 00:00:00.0"
+          issue_close_date="2013-05-18 00:00:00.0"
+    />
+
+  <issue_changes id="1" kee="FGHIJ" issue_key="ABCDE" change_type="comment" user_login="emmerik"
+                 change_data="the comment" created_at="[null]" updated_at="[null]" issue_change_creation_date="[null]"/>
+  <issue_changes id="2" kee="[null]" issue_key="ABCDE" change_type="diff" user_login="emmerik"
+                 change_data="severity=INFO|BLOCKER" created_at="[null]" updated_at="[null]" issue_change_creation_date="[null]"/>
+</dataset>
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/issue/ScanIssueStorageTest/should_update_issues.xml b/sonar-batch/src/test/resources/org/sonar/batch/issue/ScanIssueStorageTest/should_update_issues.xml
new file mode 100644 (file)
index 0000000..8b552f4
--- /dev/null
@@ -0,0 +1,31 @@
+<dataset>
+
+  <projects id="10" scope="PRJ" qualifier="TRK" kee="struts" name="Struts"/>
+  <projects id="100" scope="FIL" qualifier="CLA" kee="struts:Action" name="Action"/>
+
+  <issues id="1"
+          kee="ABCDE"
+          resolution="OPEN"
+          status="OPEN"
+          severity="BLOCKER"
+          manual_severity="[false]"
+          assignee="loic"
+          author_login="simon"
+          checksum="FFFFF"
+          effort_to_fix="[null]"
+          technical_debt="[null]"
+          message="[null]"
+          line="3000"
+          component_id="100"
+          root_component_id="10"
+          rule_id="200"
+          created_at="2010-01-01"
+          updated_at="2011-02-02"
+          reporter="emmerik"
+          issue_attributes="foo=bar"
+          action_plan_key="[null]"
+          issue_creation_date="2010-01-01"
+          issue_update_date="2010-02-02"
+          issue_close_date="[null]"
+    />
+</dataset>
index 58f7f2a5807afd386966acba5a16b9b99e733ac3..63278903bf2156b05ca7cc1c133cd5753b0fec10 100644 (file)
@@ -34,6 +34,7 @@ import org.sonar.core.rule.RuleDto;
 
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
+
 import java.io.Serializable;
 import java.util.Date;
 import java.util.UUID;
@@ -367,26 +368,26 @@ public final class IssueDto extends Dto<String> implements Serializable {
   }
 
   /**
-   * Only for unit tests
+   * Should only be used to persist in E/S
    */
-  public IssueDto setRuleKey_unit_test_only(String repo, String rule) {
+  public IssueDto setRuleKey(String repo, String rule) {
     this.ruleRepo = repo;
     this.ruleKey = rule;
     return this;
   }
 
   /**
-   * Only for unit tests
+   * Should only be used to persist in E/S
    */
-  public IssueDto setComponentKey_unit_test_only(String componentKey) {
+  public IssueDto setComponentKey(String componentKey) {
     this.componentKey = componentKey;
     return this;
   }
 
   /**
-   * Only for unit tests
+   * Should only be used to persist in E/S
    */
-  public IssueDto setRootComponentKey_unit_test_only(String rootComponentKey) {
+  public IssueDto setRootComponentKey(String rootComponentKey) {
     this.rootComponentKey = rootComponentKey;
     return this;
   }
@@ -411,8 +412,11 @@ public final class IssueDto extends Dto<String> implements Serializable {
       .setReporter(issue.reporter())
       .setAssignee(issue.assignee())
       .setRuleId(ruleId)
+      .setRuleKey(issue.ruleKey().repository(), issue.ruleKey().rule())
       .setComponentId(componentId)
+      .setComponentKey(issue.componentKey())
       .setRootComponentId(rootComponentId)
+      .setRootComponentKey(issue.projectKey())
       .setActionPlanKey(issue.actionPlanKey())
       .setIssueAttributes(KeyValueFormat.format(issue.attributes()))
       .setAuthorLogin(issue.authorLogin())
@@ -442,6 +446,9 @@ public final class IssueDto extends Dto<String> implements Serializable {
       .setActionPlanKey(issue.actionPlanKey())
       .setIssueAttributes(KeyValueFormat.format(issue.attributes()))
       .setAuthorLogin(issue.authorLogin())
+      .setRuleKey(issue.ruleKey().repository(), issue.ruleKey().rule())
+      .setComponentKey(issue.componentKey())
+      .setRootComponentKey(issue.projectKey())
       .setRootComponentId(rootComponentId)
       .setIssueCreationDate(issue.creationDate())
       .setIssueCloseDate(issue.closeDate())
index 0de970e0f8d0f99263f2074ffa17b6fb4eefb90c..ea5a1f2ed965c69c6feacd342a3e0687c0313980 100644 (file)
@@ -20,7 +20,6 @@
 package org.sonar.core.issue.db;
 
 import com.google.common.collect.Lists;
-import org.apache.ibatis.session.SqlSession;
 import org.sonar.api.issue.Issue;
 import org.sonar.api.issue.IssueComment;
 import org.sonar.api.issue.internal.DefaultIssue;
@@ -49,7 +48,6 @@ public abstract class IssueStorage {
 
   private final MyBatis mybatis;
   private final RuleFinder ruleFinder;
-  private final UpdateConflictResolver conflictResolver = new UpdateConflictResolver();
 
   protected IssueStorage(MyBatis mybatis, RuleFinder ruleFinder) {
     this.mybatis = mybatis;
@@ -72,12 +70,11 @@ public abstract class IssueStorage {
     List<DefaultIssue> toBeUpdated = Lists.newArrayList();
     DbSession batchSession = mybatis.openSession(true);
     int count = 0;
-    IssueMapper issueMapper = batchSession.getMapper(IssueMapper.class);
     IssueChangeMapper issueChangeMapper = batchSession.getMapper(IssueChangeMapper.class);
     try {
       for (DefaultIssue issue : issues) {
         if (issue.isNew()) {
-          insert(issueMapper, now, issue);
+          doInsert(batchSession, now, issue);
           insertChanges(issueChangeMapper, issue);
           if (count > BatchSession.MAX_BATCH_SIZE) {
             batchSession.commit();
@@ -94,22 +91,15 @@ public abstract class IssueStorage {
     return toBeUpdated;
   }
 
-  private void insert(IssueMapper issueMapper, Date now, DefaultIssue issue) {
-    long componentId = componentId(issue);
-    long projectId = projectId(issue);
-    int ruleId = ruleId(issue);
-    IssueDto dto = IssueDto.toDtoForInsert(issue, componentId, projectId, ruleId, now);
-    issueMapper.insert(dto);
-  }
+  protected abstract void doInsert(DbSession batchSession, Date now, DefaultIssue issue);
 
   private void update(List<DefaultIssue> toBeUpdated, Date now) {
     if (!toBeUpdated.isEmpty()) {
-      SqlSession session = mybatis.openSession(false);
+      DbSession session = mybatis.openSession(false);
       try {
-        IssueMapper issueMapper = session.getMapper(IssueMapper.class);
         IssueChangeMapper issueChangeMapper = session.getMapper(IssueChangeMapper.class);
         for (DefaultIssue issue : toBeUpdated) {
-          update(issueMapper, now, issue);
+          doUpdate(session, now, issue);
           insertChanges(issueChangeMapper, issue);
         }
         session.commit();
@@ -119,21 +109,7 @@ public abstract class IssueStorage {
     }
   }
 
-  private void update(IssueMapper issueMapper, Date now, DefaultIssue issue) {
-    IssueDto dto = IssueDto.toDtoForUpdate(issue, projectId(issue), now);
-    if (Issue.STATUS_CLOSED.equals(issue.status()) || issue.selectedAt() == null) {
-      // Issue is closed by scan or changed by end-user
-      issueMapper.update(dto);
-
-    } else {
-      int count = issueMapper.updateIfBeforeSelectedDate(dto);
-      if (count == 0) {
-        // End-user and scan changed the issue at the same time.
-        // See https://jira.codehaus.org/browse/SONAR-4309
-        conflictResolver.resolve(issue, issueMapper);
-      }
-    }
-  }
+  protected abstract void doUpdate(DbSession batchSession, Date now, DefaultIssue issue);
 
   private void insertChanges(IssueChangeMapper mapper, DefaultIssue issue) {
     for (IssueComment comment : issue.comments()) {
@@ -150,11 +126,7 @@ public abstract class IssueStorage {
     }
   }
 
-  protected abstract long componentId(DefaultIssue issue);
-
-  protected abstract long projectId(DefaultIssue issue);
-
-  private int ruleId(Issue issue) {
+  protected int ruleId(Issue issue) {
     Rule rule = ruleFinder.findByKey(issue.ruleKey());
     if (rule == null) {
       throw new IllegalStateException("Rule not found: " + issue.ruleKey());
index 73370a7face4a717d06ab85d52ad44c0be1799c0..883d85d9ea85dc2506ea47f52b74ac996cd06dfe 100644 (file)
@@ -31,7 +31,7 @@ import java.util.Date;
  *
  * @since 3.6
  */
-class UpdateConflictResolver {
+public class UpdateConflictResolver {
 
   private static final Logger LOG = LoggerFactory.getLogger(IssueStorage.class);
 
index d0bfa5dcb5e25fbf5217a25aec04d7d533a2473f..ddda3b57f718240645289ca1b7d50ee3e7de0d17 100644 (file)
@@ -58,9 +58,9 @@ public class IssueDtoTest {
     IssueDto dto = new IssueDto()
       .setKee("100")
       .setRuleId(1)
-      .setRuleKey_unit_test_only("squid", "AvoidCycle")
-      .setComponentKey_unit_test_only("org.sonar.sample:Sample")
-      .setRootComponentKey_unit_test_only("org.sonar.sample")
+      .setRuleKey("squid", "AvoidCycle")
+      .setComponentKey("org.sonar.sample:Sample")
+      .setRootComponentKey("org.sonar.sample")
       .setComponentId(1l)
       .setRootComponentId(1l)
       .setStatus(Issue.STATUS_CLOSED)
index 0f696f873106fe26203f775bcc0842141f38c02b..c4b20282ff1a98e361e36b10c202f6018bf76253 100644 (file)
@@ -30,6 +30,7 @@ import org.sonar.api.rules.RuleQuery;
 import org.sonar.api.utils.DateUtils;
 import org.sonar.api.utils.Duration;
 import org.sonar.core.persistence.AbstractDaoTestCase;
+import org.sonar.core.persistence.DbSession;
 import org.sonar.core.persistence.MyBatis;
 
 import java.util.Collection;
@@ -63,7 +64,9 @@ public class IssueStorageTest extends AbstractDaoTestCase {
       .addComment(comment)
       .setCreationDate(date)
       .setUpdateDate(date)
-      .setCloseDate(date);
+      .setCloseDate(date)
+
+      .setComponentKey("struts:Action");
 
     saver.save(issue);
 
@@ -112,54 +115,24 @@ public class IssueStorageTest extends AbstractDaoTestCase {
     checkTables("should_update_issues", new String[]{"id", "created_at", "updated_at", "issue_change_creation_date"}, "issues", "issue_changes");
   }
 
-  @Test
-  public void should_resolve_conflicts_on_updates() throws Exception {
-    setupData("should_resolve_conflicts_on_updates");
-
-    FakeSaver saver = new FakeSaver(getMyBatis(), new FakeRuleFinder());
-
-    Date date = DateUtils.parseDate("2013-05-18");
-    DefaultIssue issue = new DefaultIssue()
-      .setKey("ABCDE")
-      .setNew(false)
-      .setChanged(true)
-      .setCreationDate(DateUtils.parseDate("2005-05-12"))
-      .setUpdateDate(date)
-      .setRuleKey(RuleKey.of("squid", "AvoidCycles"))
-      .setComponentKey("struts:Action")
-
-        // issue in database has been updated in 2013, after the loading by scan
-      .setSelectedAt(DateUtils.parseDate("2005-01-01"))
-
-        // fields to be updated
-      .setLine(444)
-      .setSeverity("BLOCKER")
-      .setChecksum("FFFFF")
-      .setAttribute("JIRA", "http://jira.com")
-
-        // fields overridden by end-user -> do not save
-      .setAssignee("looser")
-      .setResolution(null)
-      .setStatus("REOPEN");
-
-    saver.save(issue);
-
-    checkTables("should_resolve_conflicts_on_updates", new String[]{"id", "created_at", "updated_at", "issue_change_creation_date"}, "issues");
-  }
-
   static class FakeSaver extends IssueStorage {
+
     protected FakeSaver(MyBatis mybatis, RuleFinder ruleFinder) {
       super(mybatis, ruleFinder);
     }
 
     @Override
-    protected long componentId(DefaultIssue issue) {
-      return 100l;
+    protected void doInsert(DbSession session, Date now, DefaultIssue issue) {
+      int ruleId = ruleId(issue);
+      IssueDto dto = IssueDto.toDtoForInsert(issue, 100l, 10l, ruleId, now);
+
+      session.getMapper(IssueMapper.class).insert(dto);
     }
 
     @Override
-    protected long projectId(DefaultIssue issue) {
-      return 10l;
+    protected void doUpdate(DbSession session, Date now, DefaultIssue issue) {
+      IssueDto dto = IssueDto.toDtoForUpdate(issue, 10l, now);
+      session.getMapper(IssueMapper.class).update(dto);
     }
   }
 
index 4dbbcb85c2fab6fb9f812b5dbbc56ab8af68c73e..87e1a5c971e40b33b73e19f8d6fc2d3d41cf7446 100644 (file)
@@ -47,9 +47,9 @@ public class UpdateConflictResolverTest {
       new IssueDto()
         .setKee("ABCDE")
         .setRuleId(10)
-        .setRuleKey_unit_test_only("squid", "AvoidCycles")
+        .setRuleKey("squid", "AvoidCycles")
         .setComponentId(100L)
-        .setComponentKey_unit_test_only("struts:org.apache.struts.Action")
+        .setComponentKey("struts:org.apache.struts.Action")
         .setLine(10)
         .setStatus(Issue.STATUS_OPEN)
 
@@ -93,9 +93,9 @@ public class UpdateConflictResolverTest {
     IssueDto dbIssue = new IssueDto()
       .setKee("ABCDE")
       .setRuleId(10)
-      .setRuleKey_unit_test_only("squid", "AvoidCycles")
+      .setRuleKey("squid", "AvoidCycles")
       .setComponentId(100L)
-      .setComponentKey_unit_test_only("struts:org.apache.struts.Action")
+      .setComponentKey("struts:org.apache.struts.Action")
       .setLine(10)
       .setResolution(Issue.RESOLUTION_FALSE_POSITIVE)
       .setStatus(Issue.STATUS_RESOLVED)