aboutsummaryrefslogtreecommitdiffstats
path: root/plugins/sonar-core-plugin
diff options
context:
space:
mode:
authorFabrice Bellingard <bellingard@gmail.com>2011-11-23 16:50:38 +0100
committerFabrice Bellingard <bellingard@gmail.com>2011-11-23 16:50:38 +0100
commit9161f2e0766721d627b1cbd040b8fbefd8ab0ac1 (patch)
treee25167fa72fcacbab93aa47c9b081d105d9c309b /plugins/sonar-core-plugin
parent39807884c88aacab9061cc3c7d08bce40a21e253 (diff)
downloadsonarqube-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')
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java1
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/CloseReviewsDecorator.java5
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/UpdateReviewsDecorator.java115
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/ViolationTrackingDecorator.java2
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/UpdateReviewsDecoratorTest.java109
-rw-r--r--plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/sensors/UpdateReviewsDecoratorTest/fixture.xml37
-rw-r--r--plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/sensors/UpdateReviewsDecoratorTest/shouldUpdateReviews-result.xml36
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