diff options
author | Fabrice Bellingard <bellingard@gmail.com> | 2011-11-23 16:50:38 +0100 |
---|---|---|
committer | Fabrice Bellingard <bellingard@gmail.com> | 2011-11-23 16:50:38 +0100 |
commit | 9161f2e0766721d627b1cbd040b8fbefd8ab0ac1 (patch) | |
tree | e25167fa72fcacbab93aa47c9b081d105d9c309b /plugins/sonar-core-plugin | |
parent | 39807884c88aacab9061cc3c7d08bce40a21e253 (diff) | |
download | sonarqube-9161f2e0766721d627b1cbd040b8fbefd8ab0ac1.tar.gz sonarqube-9161f2e0766721d627b1cbd040b8fbefd8ab0ac1.zip |
SONAR-2945 Synchronize review title&line with corresponding violation
The message and the line of a violation linked to a review are not
updated in the review detail when the violation changes.
Diffstat (limited to 'plugins/sonar-core-plugin')
7 files changed, 303 insertions, 2 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 9cf9ab27cc3..7165e9edf56 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 @@ -281,6 +281,7 @@ public class CorePlugin extends SonarPlugin { extensions.add(CloseReviewsDecorator.class); extensions.add(ReferenceAnalysis.class); extensions.add(ManualMeasureDecorator.class); + extensions.add(UpdateReviewsDecorator.class); // time machine extensions.add(TendencyDecorator.class); diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/CloseReviewsDecorator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/CloseReviewsDecorator.java index f49b4edd8c9..86e849d2c0a 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/CloseReviewsDecorator.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/CloseReviewsDecorator.java @@ -26,6 +26,7 @@ import org.slf4j.LoggerFactory; import org.sonar.api.batch.Decorator; import org.sonar.api.batch.DecoratorBarriers; import org.sonar.api.batch.DecoratorContext; +import org.sonar.api.batch.DependedUpon; import org.sonar.api.batch.DependsUpon; import org.sonar.api.database.DatabaseSession; import org.sonar.api.database.model.Snapshot; @@ -41,13 +42,15 @@ import org.sonar.core.NotDryRun; import org.sonar.jpa.entity.Review; /** - * Decorator that currently only closes a review when its corresponding violation has been fixed. + * Decorator that handles the life cycle of a review (for instance, closes a review when its corresponding violation has been fixed). */ @NotDryRun @DependsUpon(DecoratorBarriers.END_OF_VIOLATION_TRACKING) +@DependedUpon(CloseReviewsDecorator.REVIEW_LIFECYCLE_BARRIER) public class CloseReviewsDecorator implements Decorator { private static final Logger LOG = LoggerFactory.getLogger(CloseReviewsDecorator.class); + public static final String REVIEW_LIFECYCLE_BARRIER = "REVIEW_LIFECYCLE_BARRIER"; private Project project; private ResourcePersister resourcePersister; diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/UpdateReviewsDecorator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/UpdateReviewsDecorator.java new file mode 100644 index 00000000000..4422a1904f7 --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/UpdateReviewsDecorator.java @@ -0,0 +1,115 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 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.sensors; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +import javax.persistence.Query; + +import org.sonar.api.batch.Decorator; +import org.sonar.api.batch.DecoratorContext; +import org.sonar.api.batch.DependsUpon; +import org.sonar.api.database.DatabaseSession; +import org.sonar.api.database.model.RuleFailureModel; +import org.sonar.api.database.model.Snapshot; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Resource; +import org.sonar.api.rules.Violation; +import org.sonar.batch.index.ResourcePersister; +import org.sonar.core.NotDryRun; +import org.sonar.jpa.entity.Review; +import org.sonar.plugins.core.timemachine.ViolationTrackingDecorator; + +import com.google.common.collect.Maps; + +/** + * Decorator that updates reviews that are linked to violations for which the message and the line number have changed. In this case, the + * message of the review and its corresponding line number must change. + */ +@NotDryRun +@DependsUpon(CloseReviewsDecorator.REVIEW_LIFECYCLE_BARRIER) +public class UpdateReviewsDecorator implements Decorator { + + private ResourcePersister resourcePersister; + private DatabaseSession databaseSession; + private ViolationTrackingDecorator violationTrackingDecorator; + private Query searchReviewsQuery; + private Query updateReviewQuery; + private Map<Integer, Violation> violationsPerPermanentId; + + public UpdateReviewsDecorator(ResourcePersister resourcePersister, DatabaseSession databaseSession, + ViolationTrackingDecorator violationTrackingDecorator) { + this.resourcePersister = resourcePersister; + this.databaseSession = databaseSession; + this.violationTrackingDecorator = violationTrackingDecorator; + violationsPerPermanentId = Maps.newHashMap(); + } + + public boolean shouldExecuteOnProject(Project project) { + return project.isLatestAnalysis(); + } + + @SuppressWarnings({ "rawtypes" }) + public void decorate(Resource resource, DecoratorContext context) { + Snapshot currentSnapshot = resourcePersister.getSnapshot(resource); + if (currentSnapshot != null) { + Date currentDate = new Date(); + // prepare the map of rule failures by permanent_id + for (Violation violation : context.getViolations()) { + RuleFailureModel ruleFailure = violationTrackingDecorator.getReferenceViolation(violation); + if (ruleFailure != null) { + violationsPerPermanentId.put(ruleFailure.getPermanentId(), violation); + } + } + // and run the update + updateReviews(currentSnapshot.getResourceId(), currentDate); + + databaseSession.commit(); + } + } + + @SuppressWarnings({ "unchecked" }) + protected void updateReviews(int resourceId, Date currentDate) { + // prepare the DB native queries + updateReviewQuery = databaseSession + .createNativeQuery("UPDATE reviews SET title=?, resource_line=?, updated_at=CURRENT_TIMESTAMP WHERE id=?"); + searchReviewsQuery = databaseSession.getEntityManager().createNativeQuery( + "SELECT * FROM reviews WHERE status!='CLOSED' AND resource_id=?", Review.class); + // and iterate over the reviews that we find + List<Review> reviews = searchReviewsQuery.setParameter(1, resourceId).getResultList(); + for (Review review : reviews) { + checkReviewForUpdate(review, currentDate); + } + } + + protected void checkReviewForUpdate(Review review, Date currentDate) { + Violation violation = violationsPerPermanentId.get(review.getRuleFailurePermamentId()); + if (violation != null) { + String message = violation.getMessage(); + Integer line = violation.getLineId(); + if ( !review.getTitle().equals(message) || !review.getResourceLine().equals(line)) { + updateReviewQuery.setParameter(1, message).setParameter(2, line).setParameter(3, review.getId()).executeUpdate(); + } + } + } + +} diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/ViolationTrackingDecorator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/ViolationTrackingDecorator.java index d766618d6be..93347cd2f17 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/ViolationTrackingDecorator.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/ViolationTrackingDecorator.java @@ -80,7 +80,7 @@ public class ViolationTrackingDecorator implements Decorator { return result; } - RuleFailureModel getReferenceViolation(Violation violation) { + public RuleFailureModel getReferenceViolation(Violation violation) { return referenceViolationsMap.get(violation); } diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/UpdateReviewsDecoratorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/UpdateReviewsDecoratorTest.java new file mode 100644 index 00000000000..82d3da81083 --- /dev/null +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/UpdateReviewsDecoratorTest.java @@ -0,0 +1,109 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 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.sensors; + +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Date; + +import org.junit.Before; +import org.junit.Test; +import org.sonar.api.batch.DecoratorContext; +import org.sonar.api.database.model.RuleFailureModel; +import org.sonar.api.database.model.Snapshot; +import org.sonar.api.resources.File; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Resource; +import org.sonar.api.rules.Violation; +import org.sonar.batch.index.ResourcePersister; +import org.sonar.jpa.test.AbstractDbUnitTestCase; +import org.sonar.plugins.core.timemachine.ViolationTrackingDecorator; + +import com.google.common.collect.Lists; + +public class UpdateReviewsDecoratorTest extends AbstractDbUnitTestCase { + + private UpdateReviewsDecorator reviewsDecorator; + private ViolationTrackingDecorator violationTrackingDecorator; + private Resource<?> resource; + + @Before + public void setUp() { + resource = mock(File.class); + ResourcePersister resourcePersister = mock(ResourcePersister.class); + Snapshot snapshot = new Snapshot(); + snapshot.setResourceId(1); + when(resourcePersister.getSnapshot(resource)).thenReturn(snapshot); + + Project project = mock(Project.class); + when(project.getRoot()).thenReturn(project); + + violationTrackingDecorator = mock(ViolationTrackingDecorator.class); + + reviewsDecorator = new UpdateReviewsDecorator(resourcePersister, getSession(), violationTrackingDecorator); + } + + @Test + public void testShouldExecuteOnProject() throws Exception { + Project project = mock(Project.class); + when(project.isLatestAnalysis()).thenReturn(true); + assertTrue(reviewsDecorator.shouldExecuteOnProject(project)); + } + + @Test + public void shouldCloseReviewWithoutCorrespondingViolation() throws Exception { + Violation v1 = mock(Violation.class); + when(v1.getMessage()).thenReturn("message 1"); + when(v1.getLineId()).thenReturn(1); + Violation v2 = mock(Violation.class); + when(v2.getMessage()).thenReturn("message 2"); + when(v2.getLineId()).thenReturn(2); + Violation v3 = mock(Violation.class); + when(v3.getMessage()).thenReturn("message 3"); + when(v3.getLineId()).thenReturn(3); + Violation v4 = mock(Violation.class); + when(v4.getMessage()).thenReturn("message 4"); + when(v4.getLineId()).thenReturn(4); + DecoratorContext context = mock(DecoratorContext.class); + when(context.getViolations()).thenReturn(Lists.newArrayList(v1,v2,v3,v4)); + + RuleFailureModel rf1 = mock(RuleFailureModel.class); + when(rf1.getPermanentId()).thenReturn(1); + when(violationTrackingDecorator.getReferenceViolation(v1)).thenReturn(rf1); + RuleFailureModel rf2 = mock(RuleFailureModel.class); + when(rf2.getPermanentId()).thenReturn(2); + when(violationTrackingDecorator.getReferenceViolation(v2)).thenReturn(rf2); + RuleFailureModel rf3 = mock(RuleFailureModel.class); + when(rf3.getPermanentId()).thenReturn(3); + when(violationTrackingDecorator.getReferenceViolation(v3)).thenReturn(rf3); + RuleFailureModel rf4 = mock(RuleFailureModel.class); + when(rf4.getPermanentId()).thenReturn(4); + when(violationTrackingDecorator.getReferenceViolation(v4)).thenReturn(rf4); + + setupData("fixture"); + + reviewsDecorator.decorate(resource, context); + + + checkTables("shouldUpdateReviews", new String[] { "updated_at" }, new String[] { "reviews" }); + } +} diff --git a/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/sensors/UpdateReviewsDecoratorTest/fixture.xml b/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/sensors/UpdateReviewsDecoratorTest/fixture.xml new file mode 100644 index 00000000000..af23713d884 --- /dev/null +++ b/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/sensors/UpdateReviewsDecoratorTest/fixture.xml @@ -0,0 +1,37 @@ +<dataset> + <reviews + id="1" + status="OPEN" + rule_failure_permanent_id="1" + resource_id="1" + title="message OLD" + resource_line="0" + resolution="[null]" + created_at="[null]" + updated_at="[null]" + project_id="[null]" + severity="[null]" + user_id="[null]"/> + <reviews + id="2" + status="OPEN" + rule_failure_permanent_id="2" + resource_id="1" + title="message 2" + resource_line="2"/> + <reviews + id="3" + status="OPEN" + rule_failure_permanent_id="3" + resource_id="1" + title="message 3" + resource_line="0"/> + <reviews + id="4" + status="OPEN" + rule_failure_permanent_id="4" + resource_id="1" + title="message OLD" + resource_line="4"/> + +</dataset>
\ No newline at end of file diff --git a/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/sensors/UpdateReviewsDecoratorTest/shouldUpdateReviews-result.xml b/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/sensors/UpdateReviewsDecoratorTest/shouldUpdateReviews-result.xml new file mode 100644 index 00000000000..bd7f8d30cc2 --- /dev/null +++ b/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/sensors/UpdateReviewsDecoratorTest/shouldUpdateReviews-result.xml @@ -0,0 +1,36 @@ +<dataset> + + <reviews + id="1" + status="OPEN" + rule_failure_permanent_id="1" + resource_id="1" + title="message 1" + resource_line="1" + created_at="[null]" user_id="[null]" assignee_id="[null]" resolution="[null]" severity="[null]" project_id="[null]"/> + <reviews + id="2" + status="OPEN" + rule_failure_permanent_id="2" + resource_id="1" + title="message 2" + resource_line="2" + created_at="[null]" user_id="[null]" assignee_id="[null]" resolution="[null]" severity="[null]" project_id="[null]"/> + <reviews + id="3" + status="OPEN" + rule_failure_permanent_id="3" + resource_id="1" + title="message 3" + resource_line="3" + created_at="[null]" user_id="[null]" assignee_id="[null]" resolution="[null]" severity="[null]" project_id="[null]"/> + <reviews + id="4" + status="OPEN" + rule_failure_permanent_id="4" + resource_id="1" + title="message 4" + resource_line="4" + created_at="[null]" user_id="[null]" assignee_id="[null]" resolution="[null]" severity="[null]" project_id="[null]"/> + +</dataset>
\ No newline at end of file |