001/* 002 * Sonar, open source software quality management tool. 003 * Copyright (C) 2008-2012 SonarSource 004 * mailto:contact AT sonarsource DOT com 005 * 006 * Sonar is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 3 of the License, or (at your option) any later version. 010 * 011 * Sonar is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public 017 * License along with Sonar; if not, write to the Free Software 018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 019 */ 020package org.sonar.plugins.core.sensors; 021 022import com.google.common.annotations.VisibleForTesting; 023import com.google.common.base.Function; 024import com.google.common.collect.Collections2; 025import com.google.common.collect.Maps; 026import com.google.common.collect.Sets; 027import org.apache.commons.lang.ObjectUtils; 028import org.apache.commons.lang.StringUtils; 029import org.sonar.api.batch.*; 030import org.sonar.api.database.model.Snapshot; 031import org.sonar.api.resources.Project; 032import org.sonar.api.resources.Resource; 033import org.sonar.api.resources.ResourceUtils; 034import org.sonar.api.rules.Violation; 035import org.sonar.api.violations.ViolationQuery; 036import org.sonar.batch.index.ResourcePersister; 037import org.sonar.core.NotDryRun; 038import org.sonar.core.review.ReviewDao; 039import org.sonar.core.review.ReviewDto; 040 041import javax.annotation.Nullable; 042import java.util.*; 043 044@NotDryRun 045@DependsUpon(DecoratorBarriers.END_OF_VIOLATION_TRACKING) 046@DependedUpon(ReviewWorkflowDecorator.END_OF_REVIEWS_UPDATES) 047public class ReviewWorkflowDecorator implements Decorator { 048 049 public static final String END_OF_REVIEWS_UPDATES = "END_OF_REVIEWS_UPDATES"; 050 051 private ReviewNotifications notifications; 052 private ReviewDao reviewDao; 053 private ResourcePersister resourcePersister; 054 055 public ReviewWorkflowDecorator(ReviewNotifications notifications, ReviewDao reviewDao, ResourcePersister resourcePersister) { 056 this.notifications = notifications; 057 this.reviewDao = reviewDao; 058 this.resourcePersister = resourcePersister; 059 } 060 061 public boolean shouldExecuteOnProject(Project project) { 062 return project.isLatestAnalysis(); 063 } 064 065 public void decorate(Resource resource, DecoratorContext context) { 066 Snapshot snapshot = resourcePersister.getSnapshot(resource); 067 if (snapshot != null) { 068 Collection<ReviewDto> openReviews = reviewDao.selectOpenByResourceId(snapshot.getResourceId()); 069 Set<ReviewDto> updated = Sets.newHashSet(); 070 if (!openReviews.isEmpty()) { 071 List<Violation> violations = context.getViolations(ViolationQuery.create().forResource(resource).setSwitchMode(ViolationQuery.SwitchMode.BOTH)); 072 closeResolvedStandardViolations(openReviews, violations, context.getProject(), resource, updated); 073 closeResolvedManualViolations(openReviews, context.getProject(), resource, updated); 074 reopenUnresolvedViolations(openReviews, context.getProject(), resource, updated); 075 updateReviewInformation(openReviews, violations, updated); 076 } 077 if (ResourceUtils.isRootProject(resource)) { 078 closeReviewsOnDeletedResources((Project) resource, snapshot.getResourceId(), snapshot.getId(), updated); 079 } 080 persistUpdates(updated); 081 } 082 } 083 084 private void persistUpdates(Set<ReviewDto> updated) { 085 if (!updated.isEmpty()) { 086 reviewDao.update(updated); 087 } 088 } 089 090 /** 091 * Close reviews that relate to resources that have been deleted or renamed. 092 */ 093 private void closeReviewsOnDeletedResources(Project project, int rootProjectId, int rootSnapshotId, Set<ReviewDto> updated) { 094 Collection<ReviewDto> reviews = reviewDao.selectOnDeletedResources(rootProjectId, rootSnapshotId); 095 for (ReviewDto review : reviews) { 096 close(review, project, null); 097 updated.add(review); 098 } 099 } 100 101 private void updateReviewInformation(Collection<ReviewDto> openReviews, Collection<Violation> violations, Set<ReviewDto> updated) { 102 Map<Integer, Violation> violationsByPermanentId = Maps.newHashMap(); 103 for (Violation violation : violations) { 104 if (violation.getPermanentId()!=null) { 105 violationsByPermanentId.put(violation.getPermanentId(), violation); 106 } 107 } 108 109 for (ReviewDto review : openReviews) { 110 Violation violation = violationsByPermanentId.get(review.getViolationPermanentId()); 111 if (violation != null && !hasUpToDateInformation(review, violation)) { 112 review.setLine(violation.getLineId()); 113 review.setTitle(violation.getMessage()); 114 updated.add(review); 115 } 116 } 117 } 118 119 @VisibleForTesting 120 static boolean hasUpToDateInformation(ReviewDto review, Violation violation) { 121 return StringUtils.equals(review.getTitle(), violation.getMessage()) && ObjectUtils.equals(review.getLine(), violation.getLineId()); 122 } 123 124 private void closeResolvedManualViolations(Collection<ReviewDto> openReviews, Project project, Resource resource, Set<ReviewDto> updated) { 125 for (ReviewDto openReview : openReviews) { 126 if (openReview.isManualViolation() && ReviewDto.STATUS_RESOLVED.equals(openReview.getStatus())) { 127 close(openReview, project, resource); 128 updated.add(openReview); 129 } 130 } 131 } 132 133 private void closeResolvedStandardViolations(Collection<ReviewDto> openReviews, List<Violation> violations, Project project, Resource resource, Set<ReviewDto> updated) { 134 Set<Integer> violationIds = Sets.newHashSet(Collections2.transform(violations, new ViolationToPermanentIdFunction())); 135 136 for (ReviewDto openReview : openReviews) { 137 if (!openReview.isManualViolation() && !violationIds.contains(openReview.getViolationPermanentId())) { 138 close(openReview, project, resource); 139 updated.add(openReview); 140 } 141 } 142 } 143 144 private void reopenUnresolvedViolations(Collection<ReviewDto> openReviews, Project project, Resource resource, Set<ReviewDto> updated) { 145 for (ReviewDto openReview : openReviews) { 146 if (ReviewDto.STATUS_RESOLVED.equals(openReview.getStatus()) && !ReviewDto.RESOLUTION_FALSE_POSITIVE.equals(openReview.getResolution()) 147 && !openReview.isManualViolation()) { 148 reopen(openReview, project, resource); 149 updated.add(openReview); 150 } 151 } 152 } 153 154 private void close(ReviewDto review, Project project, Resource resource) { 155 notifications.notifyClosed(review, project, resource); 156 review.setStatus(ReviewDto.STATUS_CLOSED); 157 review.setUpdatedAt(new Date()); 158 } 159 160 private void reopen(ReviewDto review, Project project, Resource resource) { 161 notifications.notifyReopened(review, project, resource); 162 review.setStatus(ReviewDto.STATUS_REOPENED); 163 review.setResolution(null); 164 review.setUpdatedAt(new Date()); 165 } 166 167 private static final class ViolationToPermanentIdFunction implements Function<Violation, Integer> { 168 public Integer apply(@Nullable Violation violation) { 169 return (violation != null ? violation.getPermanentId() : null); 170 } 171 } 172}