diff options
author | Julien Lancelot <julien.lancelot@gmail.com> | 2013-04-17 13:33:32 +0200 |
---|---|---|
committer | Julien Lancelot <julien.lancelot@gmail.com> | 2013-04-17 13:33:32 +0200 |
commit | cc860e94c272caadb20fb4c9f2b5b2fbe174b3be (patch) | |
tree | 898308f7981025322f2166c0ed9c329e82e5ce39 | |
parent | 9410b7218428ccd2048dce54006d04809b59ffac (diff) | |
download | sonarqube-cc860e94c272caadb20fb4c9f2b5b2fbe174b3be.tar.gz sonarqube-cc860e94c272caadb20fb4c9f2b5b2fbe174b3be.zip |
SONAR-3755 Add IssuesWorkflowDecorator
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 <> '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); + } + } |