summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJulien Lancelot <julien.lancelot@gmail.com>2013-04-17 13:33:32 +0200
committerJulien Lancelot <julien.lancelot@gmail.com>2013-04-17 13:33:32 +0200
commitcc860e94c272caadb20fb4c9f2b5b2fbe174b3be (patch)
tree898308f7981025322f2166c0ed9c329e82e5ce39
parent9410b7218428ccd2048dce54006d04809b59ffac (diff)
downloadsonarqube-cc860e94c272caadb20fb4c9f2b5b2fbe174b3be.tar.gz
sonarqube-cc860e94c272caadb20fb4c9f2b5b2fbe174b3be.zip
SONAR-3755 Add IssuesWorkflowDecorator
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java11
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTracking.java (renamed from plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTrackingDecorator.java)53
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssuesWorkflowDecorator.java181
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingTest.java (renamed from plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingDecoratorTest.java)6
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssuesWorkflowDecoratorTest.java124
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/issue/InitialOpenIssuesSensor.java52
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/issue/InitialOpenIssuesStack.java57
-rw-r--r--sonar-core/src/main/java/org/sonar/core/issue/IssueDao.java9
-rw-r--r--sonar-core/src/main/resources/org/sonar/core/issue/IssueMapper.xml9
-rw-r--r--sonar-core/src/test/java/org/sonar/core/issue/IssueDaoTest.java7
10 files changed, 455 insertions, 54 deletions
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
index b3c8d9dc188..e64ae10fdfd 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
@@ -25,10 +25,7 @@ import org.sonar.api.*;
import org.sonar.api.checks.NoSonarFilter;
import org.sonar.api.notifications.NotificationDispatcherMetadata;
import org.sonar.api.resources.Java;
-import org.sonar.batch.issue.IssuesDecorator;
-import org.sonar.batch.issue.IssuesDensityDecorator;
-import org.sonar.batch.issue.NewIssuesDecorator;
-import org.sonar.batch.issue.WeightedIssuesDecorator;
+import org.sonar.batch.issue.*;
import org.sonar.core.timemachine.Periods;
import org.sonar.plugins.core.batch.IndexProjectPostJob;
import org.sonar.plugins.core.charts.DistributionAreaChart;
@@ -36,7 +33,7 @@ import org.sonar.plugins.core.charts.DistributionBarChart;
import org.sonar.plugins.core.charts.XradarChart;
import org.sonar.plugins.core.colorizers.JavaColorizerFormat;
import org.sonar.plugins.core.dashboards.*;
-import org.sonar.plugins.core.issue.IssueTrackingDecorator;
+import org.sonar.plugins.core.issue.IssueTracking;
import org.sonar.plugins.core.measurefilters.MyFavouritesFilter;
import org.sonar.plugins.core.measurefilters.ProjectFilter;
import org.sonar.plugins.core.notifications.alerts.NewAlerts;
@@ -398,6 +395,8 @@ public final class CorePlugin extends SonarPlugin {
CheckAlertThresholds.class,
GenerateAlertEvents.class,
ViolationsDecorator.class,
+ InitialOpenIssuesSensor.class,
+ InitialOpenIssuesStack.class,
IssuesDecorator.class,
WeightedViolationsDecorator.class,
WeightedIssuesDecorator.class,
@@ -429,7 +428,7 @@ public final class CorePlugin extends SonarPlugin {
TendencyDecorator.class,
VariationDecorator.class,
ViolationTrackingDecorator.class,
- IssueTrackingDecorator.class,
+ IssueTracking.class,
ViolationPersisterDecorator.class,
NewViolationsDecorator.class,
NewIssuesDecorator.class,
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTrackingDecorator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTracking.java
index 301aa72dc88..c4685345ca5 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTrackingDecorator.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTracking.java
@@ -21,20 +21,17 @@
package org.sonar.plugins.core.issue;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.collect.*;
-import org.sonar.api.batch.*;
+import org.sonar.api.BatchExtension;
+import org.sonar.api.batch.SonarIndex;
import org.sonar.api.component.ResourcePerspectives;
import org.sonar.api.issue.Issuable;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.issue.IssueQuery;
import org.sonar.api.resources.Project;
import org.sonar.api.resources.Resource;
import org.sonar.api.rules.RuleFinder;
import org.sonar.batch.scan.LastSnapshots;
import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.IssueDao;
import org.sonar.core.issue.IssueDto;
import org.sonar.plugins.core.timemachine.SourceChecksum;
import org.sonar.plugins.core.timemachine.ViolationTrackingBlocksRecognizer;
@@ -44,12 +41,7 @@ import javax.annotation.Nullable;
import java.util.*;
-import static com.google.common.collect.Lists.newArrayList;
-
-@DependsUpon({DecoratorBarriers.END_OF_VIOLATIONS_GENERATION, DecoratorBarriers.START_VIOLATION_TRACKING})
-@DependedUpon(DecoratorBarriers.END_OF_VIOLATION_TRACKING)
-
-public class IssueTrackingDecorator implements Decorator {
+public class IssueTracking implements BatchExtension {
private static final Comparator<LinePair> LINE_PAIR_COMPARATOR = new Comparator<LinePair>() {
public int compare(LinePair o1, LinePair o2) {
@@ -58,7 +50,6 @@ public class IssueTrackingDecorator implements Decorator {
};
private final Project project;
private final ResourcePerspectives perspectives;
- private final IssueDao issueDao;
private final RuleFinder ruleFinder;
private final LastSnapshots lastSnapshots;
private final SonarIndex index;
@@ -71,36 +62,25 @@ public class IssueTrackingDecorator implements Decorator {
*/
private Map<DefaultIssue, IssueDto> referenceIssuesMap = Maps.newIdentityHashMap();
- public IssueTrackingDecorator(Project project, ResourcePerspectives perspectives, IssueDao issueDao, RuleFinder ruleFinder, LastSnapshots lastSnapshots, SonarIndex index) {
+ public IssueTracking(Project project, ResourcePerspectives perspectives, RuleFinder ruleFinder, LastSnapshots lastSnapshots, SonarIndex index) {
this.project = project;
this.perspectives = perspectives;
- this.issueDao = issueDao;
this.ruleFinder = ruleFinder;
this.lastSnapshots = lastSnapshots;
this.index = index;
}
- @Override
- public boolean shouldExecuteOnProject(Project project) {
- return true;
- }
-
- @Override
- public void decorate(Resource resource, DecoratorContext context) {
+ public void track(Resource resource, Collection<IssueDto> referenceIssues, List<DefaultIssue> newIssues) {
referenceIssuesMap.clear();
// Load new issues
Issuable issuable = perspectives.as(Issuable.class, resource);
- if (issuable == null || issuable.issues().isEmpty()){
+ if (issuable == null || issuable.issues().isEmpty()) {
return;
}
- List<DefaultIssue> newIssues = toDefaultIssues(issuable.issues());
String source = index.getSource(resource);
setChecksumOnNewIssues(newIssues, source);
- // Load existing issues
- Collection<IssueDto> referenceIssues = loadExistingOpenIssues(resource);
-
// Map new issues with old ones
mapIssues(newIssues, referenceIssues, source, resource);
}
@@ -112,14 +92,6 @@ public class IssueTrackingDecorator implements Decorator {
}
}
- private Collection<IssueDto> loadExistingOpenIssues(Resource resource) {
- IssueQuery query = IssueQuery.builder()
- .components(Lists.newArrayList(resource.getEffectiveKey()))
- .statuses(Lists.newArrayList(Issue.STATUS_OPEN, Issue.STATUS_REOPENED, Issue.STATUS_RESOLVED))
- .build();
- return issueDao.select(query);
- }
-
@VisibleForTesting
Map<DefaultIssue, IssueDto> mapIssues(List<DefaultIssue> newIssues, @Nullable List<IssueDto> lastIssues) {
return mapIssues(newIssues, lastIssues, null, null);
@@ -174,7 +146,7 @@ public class IssueTrackingDecorator implements Decorator {
}
}
- private void mapNewissues(String referenceSource, List<DefaultIssue> newIssues, Multimap<Integer, IssueDto> lastIssuesByRule, String source){
+ private void mapNewissues(String referenceSource, List<DefaultIssue> newIssues, Multimap<Integer, IssueDto> lastIssuesByRule, String source) {
HashedSequence<StringText> hashedReference = HashedSequence.wrap(new StringText(referenceSource), StringTextComparator.IGNORE_WHITESPACE);
HashedSequence<StringText> hashedSource = HashedSequence.wrap(new StringText(source), StringTextComparator.IGNORE_WHITESPACE);
HashedSequenceComparator<StringText> hashedComparator = new HashedSequenceComparator<StringText>(StringTextComparator.IGNORE_WHITESPACE);
@@ -239,7 +211,7 @@ public class IssueTrackingDecorator implements Decorator {
}
}
- private void mapIssuesOnSameRule(List<DefaultIssue> newIssues, Multimap<Integer, IssueDto> lastIssuesByRule){
+ private void mapIssuesOnSameRule(List<DefaultIssue> newIssues, Multimap<Integer, IssueDto> lastIssuesByRule) {
// Try then to match issues on same rule with same message and with same checksum
for (DefaultIssue newIssue : newIssues) {
if (isNotAlreadyMapped(newIssue)) {
@@ -274,15 +246,6 @@ public class IssueTrackingDecorator implements Decorator {
return referenceIssuesMap.get(issue);
}
- private List<DefaultIssue> toDefaultIssues(Collection<Issue> issues) {
- return newArrayList(Iterables.transform(issues, new Function<Issue, DefaultIssue>() {
- @Override
- public DefaultIssue apply(Issue issue) {
- return (DefaultIssue) issue;
- }
- }));
- }
-
private Integer getRule(DefaultIssue issue) {
return ruleFinder.findByKey(issue.ruleRepositoryKey(), issue.ruleKey()).getId();
}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssuesWorkflowDecorator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssuesWorkflowDecorator.java
new file mode 100644
index 00000000000..a10ed742763
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssuesWorkflowDecorator.java
@@ -0,0 +1,181 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.plugins.core.issue;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.Sets;
+import org.sonar.api.batch.*;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.resources.ResourceUtils;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RuleFinder;
+import org.sonar.api.utils.KeyValueFormat;
+import org.sonar.batch.issue.InitialOpenIssuesStack;
+import org.sonar.batch.issue.ModuleIssues;
+import org.sonar.core.DryRunIncompatible;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.IssueDto;
+
+import javax.annotation.Nullable;
+
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+@DryRunIncompatible
+@DependsUpon(DecoratorBarriers.END_OF_VIOLATION_TRACKING)
+@DependedUpon(IssuesWorkflowDecorator.END_OF_ISSUES_UPDATES)
+public class IssuesWorkflowDecorator implements Decorator {
+
+ public static final String END_OF_ISSUES_UPDATES = "END_OF_ISSUES_UPDATES";
+
+ private final ModuleIssues moduleIssues;
+ private final InitialOpenIssuesStack initialOpenIssuesStack;
+ private final IssueTracking issueTracking;
+ private final RuleFinder ruleFinder;
+
+ public IssuesWorkflowDecorator(ModuleIssues moduleIssues, InitialOpenIssuesStack initialOpenIssuesStack, IssueTracking issueTracking, RuleFinder ruleFinder) {
+ this.moduleIssues = moduleIssues;
+ this.initialOpenIssuesStack = initialOpenIssuesStack;
+ this.issueTracking = issueTracking;
+ this.ruleFinder = ruleFinder;
+ }
+
+ public boolean shouldExecuteOnProject(Project project) {
+ return project.isLatestAnalysis();
+ }
+
+ public void decorate(Resource resource, DecoratorContext context) {
+ Collection<Issue> newIssues = moduleIssues.issues(resource.getEffectiveKey());
+ Collection<IssueDto> openIssues = initialOpenIssuesStack.selectAndRemove(resource.getId());
+ if (!openIssues.isEmpty()) {
+ issueTracking.track(resource, openIssues, (List) newIssues);
+ updateIssues(newIssues);
+
+ addManualIssues(openIssues);
+ closeResolvedStandardIssues(openIssues, newIssues, resource);
+ closeResolvedManualIssues(openIssues, resource);
+ reopenUnresolvedIssues(openIssues, resource);
+ }
+ if (ResourceUtils.isRootProject(resource)) {
+ closeIssuesOnDeletedResources(openIssues, resource);
+ }
+ }
+
+ private void updateIssues(Collection<Issue> newIssues){
+ for (Issue issue : newIssues){
+ moduleIssues.addOrUpdate((DefaultIssue)issue);
+ }
+ }
+
+ private void addManualIssues(Collection<IssueDto> openIssues) {
+ for (IssueDto openIssue : openIssues) {
+ if (openIssue.isManualIssue()) {
+ DefaultIssue newIssue = new DefaultIssue();
+ moduleIssues.addOrUpdate(newIssue);
+ }
+ }
+ }
+
+ /**
+ * Close issues that relate to resources that have been deleted or renamed.
+ */
+ private void closeIssuesOnDeletedResources(Collection<IssueDto> openIssues, Resource resource) {
+ for (IssueDto openIssue : openIssues) {
+ close(openIssue, resource);
+ }
+ }
+
+ private void closeResolvedManualIssues(Collection<IssueDto> openIssues, Resource resource) {
+ for (IssueDto openIssue : openIssues) {
+ if (openIssue.isManualIssue() && Issue.STATUS_RESOLVED.equals(openIssue.getStatus())) {
+ close(openIssue, resource);
+ }
+ }
+ }
+
+ private void closeResolvedStandardIssues(Collection<IssueDto> openIssues, Collection<Issue> issues, Resource resource) {
+ Set<String> issueKeys = Sets.newHashSet(Collections2.transform(issues, new IssueToKeyfunction()));
+
+ for (IssueDto openIssue : openIssues) {
+ if (!openIssue.isManualIssue() && !issueKeys.contains(openIssue.getUuid())) {
+ close(openIssue, resource);
+ }
+ }
+ }
+
+ private void reopenUnresolvedIssues(Collection<IssueDto> openIssues, Resource resource) {
+ for (IssueDto openIssue : openIssues) {
+ if (Issue.STATUS_RESOLVED.equals(openIssue.getStatus()) && !Issue.RESOLUTION_FALSE_POSITIVE.equals(openIssue.getResolution())
+ && !openIssue.isManualIssue()) {
+ reopen(openIssue, resource);
+ }
+ }
+ }
+
+ private void close(IssueDto openIssue, Resource resource) {
+ DefaultIssue issue = toIssue(openIssue, resource);
+ issue.setStatus(Issue.STATUS_CLOSED);
+ issue.setUpdatedAt(new Date());
+ moduleIssues.addOrUpdate(issue);
+ }
+
+ private void reopen(IssueDto openIssue, Resource resource) {
+ DefaultIssue issue = toIssue(openIssue, resource);
+ issue.setStatus(Issue.STATUS_REOPENED);
+ issue.setResolution(null);
+ issue.setUpdatedAt(new Date());
+ moduleIssues.addOrUpdate(issue);
+ }
+
+ private DefaultIssue toIssue(IssueDto dto, Resource resource) {
+ DefaultIssue issue = new DefaultIssue();
+ issue.setKey(dto.getUuid());
+ issue.setStatus(dto.getStatus());
+ issue.setResolution(dto.getResolution());
+ issue.setMessage(dto.getMessage());
+ issue.setTitle(dto.getTitle());
+ issue.setCost(dto.getCost());
+ issue.setLine(dto.getLine());
+ issue.setSeverity(dto.getSeverity());
+ issue.setUserLogin(dto.getUserLogin());
+ issue.setAssigneeLogin(dto.getAssigneeLogin());
+ issue.setCreatedAt(dto.getCreatedAt());
+ issue.setUpdatedAt(dto.getUpdatedAt());
+ issue.setClosedAt(dto.getClosedAt());
+ issue.setAttributes(KeyValueFormat.parse(dto.getData()));
+ issue.setComponentKey(resource.getKey());
+
+ Rule rule = ruleFinder.findById(dto.getRuleId());
+ issue.setRuleKey(rule.getKey());
+ issue.setRuleRepositoryKey(rule.getRepositoryKey());
+ return issue;
+ }
+
+ private static final class IssueToKeyfunction implements Function<Issue, String> {
+ public String apply(@Nullable Issue issue) {
+ return (issue != null ? issue.key() : null);
+ }
+ }
+}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingDecoratorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingTest.java
index f5ac91b5292..693c0967b64 100644
--- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingDecoratorTest.java
+++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingTest.java
@@ -38,10 +38,10 @@ import static org.fest.assertions.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-public class IssueTrackingDecoratorTest {
+public class IssueTrackingTest {
private final Date analysisDate = DateUtils.parseDate("2013-04-11");
- private IssueTrackingDecorator decorator;
+ private IssueTracking decorator;
private long violationId = 0;
@Before
@@ -59,7 +59,7 @@ public class IssueTrackingDecoratorTest {
Project project = mock(Project.class);
when(project.getAnalysisDate()).thenReturn(analysisDate);
- decorator = new IssueTrackingDecorator(project, null, null, ruleFinder, null, null);
+ decorator = new IssueTracking(project, null, ruleFinder, null, null);
}
@Test
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssuesWorkflowDecoratorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssuesWorkflowDecoratorTest.java
new file mode 100644
index 00000000000..d6eb183d690
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssuesWorkflowDecoratorTest.java
@@ -0,0 +1,124 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.plugins.core.issue;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.resources.JavaFile;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RuleFinder;
+import org.sonar.api.rules.Violation;
+import org.sonar.api.violations.ViolationQuery;
+import org.sonar.batch.issue.InitialOpenIssuesStack;
+import org.sonar.batch.issue.ModuleIssues;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.IssueDto;
+import org.sonar.core.persistence.AbstractDaoTestCase;
+
+import java.util.Collections;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class IssuesWorkflowDecoratorTest extends AbstractDaoTestCase {
+
+ private IssuesWorkflowDecorator decorator;
+
+ private ModuleIssues moduleIssues;
+ private InitialOpenIssuesStack initialOpenIssuesStack;
+ private IssueTracking issueTracking;
+ private RuleFinder ruleFinder;
+
+ @Before
+ public void init() {
+ moduleIssues = mock(ModuleIssues.class);
+ initialOpenIssuesStack = mock(InitialOpenIssuesStack.class);
+ issueTracking = mock(IssueTracking.class);
+ ruleFinder = mock(RuleFinder.class);
+
+ decorator = new IssuesWorkflowDecorator(moduleIssues, initialOpenIssuesStack, issueTracking, ruleFinder);
+ }
+
+ @Test
+ public void should_execute_on_project() {
+ Project project = mock(Project.class);
+ when(project.isLatestAnalysis()).thenReturn(true);
+ assertTrue(decorator.shouldExecuteOnProject(project));
+ }
+
+ @Test
+ public void should_execute_on_project_not_if_past_inspection() {
+ Project project = mock(Project.class);
+ when(project.isLatestAnalysis()).thenReturn(false);
+ assertFalse(decorator.shouldExecuteOnProject(project));
+ }
+
+ @Test
+ @Ignore
+ public void shouldCloseIssuesOnResolvedViolations() {
+ //setupData("shouldCloseReviewsOnResolvedViolations");
+
+ when(moduleIssues.issues(anyString())).thenReturn(newArrayList((Issue) new DefaultIssue().setKey("111").setComponentKey("key")));
+ when(initialOpenIssuesStack.selectAndRemove(anyInt())).thenReturn(newArrayList(new IssueDto().setUuid("111").setResourceId(1)));
+
+ Resource resource = new JavaFile("key");
+ decorator.decorate(resource, null);
+
+ //checkTables("shouldCloseReviewsOnResolvedViolations", new String[] {"updated_at"}, "reviews");
+ }
+
+ @Test
+ @Ignore
+ public void shouldCloseResolvedManualViolations() {
+ setupData("shouldCloseResolvedManualViolations");
+ DecoratorContext context = mock(DecoratorContext.class);
+ when(context.getViolations(any(ViolationQuery.class))).thenReturn(Collections.<Violation>emptyList());
+
+ Resource resource = new JavaFile("org.foo.Bar");
+ decorator.decorate(resource, context);
+
+ checkTables("shouldCloseResolvedManualViolations", new String[] {"updated_at"}, "reviews");
+ }
+
+ @Test
+ @Ignore
+ public void shouldReopenViolations() {
+ setupData("shouldReopenViolations");
+ DecoratorContext context = mock(DecoratorContext.class);
+ Violation violation = new Violation(new Rule());
+ violation.setPermanentId(1000);
+ when(context.getViolations(any(ViolationQuery.class))).thenReturn(newArrayList(violation));
+
+ Resource resource = new JavaFile("org.foo.Bar");
+ decorator.decorate(resource, context);
+
+ checkTables("shouldReopenViolations", new String[] {"updated_at"}, "reviews");
+ }
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/InitialOpenIssuesSensor.java b/sonar-batch/src/main/java/org/sonar/batch/issue/InitialOpenIssuesSensor.java
new file mode 100644
index 00000000000..0c6338aa0e8
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/issue/InitialOpenIssuesSensor.java
@@ -0,0 +1,52 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+
+package org.sonar.batch.issue;
+
+import org.sonar.api.batch.Sensor;
+import org.sonar.api.batch.SensorContext;
+import org.sonar.api.resources.Project;
+import org.sonar.core.issue.IssueDao;
+import org.sonar.core.issue.IssueDto;
+
+import java.util.List;
+
+public class InitialOpenIssuesSensor implements Sensor {
+
+ private final InitialOpenIssuesStack initialOpenIssuesStack;
+ private final IssueDao issueDao;
+
+ public InitialOpenIssuesSensor(InitialOpenIssuesStack initialOpenIssuesStack, IssueDao issueDao) {
+ this.initialOpenIssuesStack = initialOpenIssuesStack;
+ this.issueDao = issueDao;
+ }
+
+ @Override
+ public boolean shouldExecuteOnProject(Project project) {
+ return true;
+ }
+
+ @Override
+ public void analyse(Project project, SensorContext context) {
+ List<IssueDto> issuesDto = issueDao.selectOpenIssues(project.getId());
+ initialOpenIssuesStack.setIssues(issuesDto);
+ }
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/InitialOpenIssuesStack.java b/sonar-batch/src/main/java/org/sonar/batch/issue/InitialOpenIssuesStack.java
new file mode 100644
index 00000000000..54c57c02cb4
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/issue/InitialOpenIssuesStack.java
@@ -0,0 +1,57 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+
+package org.sonar.batch.issue;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import org.sonar.api.BatchExtension;
+import org.sonar.core.issue.IssueDto;
+
+import java.util.List;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+public class InitialOpenIssuesStack implements BatchExtension {
+
+ private List<IssueDto> issues;
+
+ public InitialOpenIssuesStack() {
+
+ }
+
+ public void setIssues(List<IssueDto> issues){
+ this.issues = issues;
+ }
+
+ public List<IssueDto> selectAndRemove(final Integer resourceId){
+ Predicate resourcePredicate = new Predicate<IssueDto>() {
+ @Override
+ public boolean apply(IssueDto issueDto) {
+ return issueDto.getResourceId().equals(resourceId);
+ }
+ };
+
+ List<IssueDto> issuesDto = newArrayList(Iterables.find(issues, resourcePredicate));
+ Iterables.removeIf(issuesDto, resourcePredicate);
+
+ return issuesDto;
+ }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/IssueDao.java b/sonar-core/src/main/java/org/sonar/core/issue/IssueDao.java
index c1691a7a3f9..7b3e20385ba 100644
--- a/sonar-core/src/main/java/org/sonar/core/issue/IssueDao.java
+++ b/sonar-core/src/main/java/org/sonar/core/issue/IssueDao.java
@@ -85,6 +85,15 @@ public class IssueDao implements BatchComponent, ServerComponent {
}
}
+ public List<IssueDto> selectOpenIssues(Integer componentId) {
+ SqlSession session = mybatis.openSession();
+ try {
+ return session.selectList("org.sonar.core.issue.IssueMapper.selectOpenIssues", componentId);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
public List<IssueDto> select(IssueQuery query) {
SqlSession session = mybatis.openSession();
try {
diff --git a/sonar-core/src/main/resources/org/sonar/core/issue/IssueMapper.xml b/sonar-core/src/main/resources/org/sonar/core/issue/IssueMapper.xml
index 117db37a925..3e527c0ef8e 100644
--- a/sonar-core/src/main/resources/org/sonar/core/issue/IssueMapper.xml
+++ b/sonar-core/src/main/resources/org/sonar/core/issue/IssueMapper.xml
@@ -84,6 +84,15 @@
where i.uuid=#{uuid}
</select>
+ <select id="selectOpenIssues" parameterType="String" resultType="Issue">
+ select distinct
+ <include refid="issueColumns"/>
+ from issues i, projects p
+ where i.status &lt;&gt; 'CLOSED'
+ and p.root_id=#{componentId} or (p.root_id is null and p.id=#{componentId})
+ and i.resource_id=p.id
+ </select>
+
<select id="select" parameterType="map" resultType="Issue">
select
<include refid="issueColumns"/>
diff --git a/sonar-core/src/test/java/org/sonar/core/issue/IssueDaoTest.java b/sonar-core/src/test/java/org/sonar/core/issue/IssueDaoTest.java
index aea9b603599..ad16cffd099 100644
--- a/sonar-core/src/test/java/org/sonar/core/issue/IssueDaoTest.java
+++ b/sonar-core/src/test/java/org/sonar/core/issue/IssueDaoTest.java
@@ -195,4 +195,11 @@ public class IssueDaoTest extends AbstractDaoTestCase {
assertThat(dao.select(issueQuery)).hasSize(5);
}
+ @Test
+ public void should_select_open_issues() {
+ setupData("select-open-issues");
+
+ assertThat(dao.selectOpenIssues(400)).hasSize(2);
+ }
+
}