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 */ 020 package org.sonar.plugins.core.sensors; 021 022 import com.google.common.annotations.VisibleForTesting; 023 import com.google.common.base.Function; 024 import com.google.common.collect.Collections2; 025 import com.google.common.collect.Maps; 026 import com.google.common.collect.Sets; 027 import org.apache.commons.lang.ObjectUtils; 028 import org.apache.commons.lang.StringUtils; 029 import org.sonar.api.batch.*; 030 import org.sonar.api.database.model.Snapshot; 031 import org.sonar.api.resources.Project; 032 import org.sonar.api.resources.Resource; 033 import org.sonar.api.resources.ResourceUtils; 034 import org.sonar.api.rules.Violation; 035 import org.sonar.api.violations.ViolationQuery; 036 import org.sonar.batch.index.ResourcePersister; 037 import org.sonar.core.NotDryRun; 038 import org.sonar.core.review.ReviewDao; 039 import org.sonar.core.review.ReviewDto; 040 041 import java.util.*; 042 043 @NotDryRun 044 @DependsUpon(DecoratorBarriers.END_OF_VIOLATION_TRACKING) 045 @DependedUpon(ReviewWorkflowDecorator.END_OF_REVIEWS_UPDATES) 046 public class ReviewWorkflowDecorator implements Decorator { 047 048 public static final String END_OF_REVIEWS_UPDATES = "END_OF_REVIEWS_UPDATES"; 049 050 private ReviewNotifications notifications; 051 private ReviewDao reviewDao; 052 private ResourcePersister resourcePersister; 053 054 public ReviewWorkflowDecorator(ReviewNotifications notifications, ReviewDao reviewDao, ResourcePersister resourcePersister) { 055 this.notifications = notifications; 056 this.reviewDao = reviewDao; 057 this.resourcePersister = resourcePersister; 058 } 059 060 public boolean shouldExecuteOnProject(Project project) { 061 return project.isLatestAnalysis(); 062 } 063 064 public void decorate(Resource resource, DecoratorContext context) { 065 Snapshot snapshot = resourcePersister.getSnapshot(resource); 066 if (snapshot != null) { 067 Collection<ReviewDto> openReviews = reviewDao.selectOpenByResourceId(snapshot.getResourceId()); 068 Set<ReviewDto> updated = Sets.newHashSet(); 069 if (!openReviews.isEmpty()) { 070 List<Violation> violations = context.getViolations(ViolationQuery.create().forResource(resource).setSwitchMode(ViolationQuery.SwitchMode.BOTH)); 071 closeResolvedStandardViolations(openReviews, violations, context.getProject(), resource, updated); 072 closeResolvedManualViolations(openReviews, context.getProject(), resource, updated); 073 reopenUnresolvedViolations(openReviews, context.getProject(), resource, updated); 074 updateReviewInformation(openReviews, violations, updated); 075 } 076 if (ResourceUtils.isRootProject(resource)) { 077 closeReviewsOnDeletedResources((Project) resource, snapshot.getResourceId(), snapshot.getId(), updated); 078 } 079 persistUpdates(updated); 080 } 081 } 082 083 private void persistUpdates(Set<ReviewDto> updated) { 084 if (!updated.isEmpty()) { 085 reviewDao.update(updated); 086 } 087 } 088 089 /** 090 * Close reviews that relate to resources that have been deleted or renamed. 091 */ 092 private void closeReviewsOnDeletedResources(Project project, int rootProjectId, int rootSnapshotId, Set<ReviewDto> updated) { 093 Collection<ReviewDto> reviews = reviewDao.selectOnDeletedResources(rootProjectId, rootSnapshotId); 094 for (ReviewDto review : reviews) { 095 close(review, project, null); 096 updated.add(review); 097 } 098 } 099 100 private void updateReviewInformation(Collection<ReviewDto> openReviews, Collection<Violation> violations, Set<ReviewDto> updated) { 101 Map<Integer, Violation> violationsByPermanentId = Maps.newHashMap(); 102 for (Violation violation : violations) { 103 if (violation.getPermanentId()!=null) { 104 violationsByPermanentId.put(violation.getPermanentId(), violation); 105 } 106 } 107 108 for (ReviewDto review : openReviews) { 109 Violation violation = violationsByPermanentId.get(review.getViolationPermanentId()); 110 if (violation != null && !hasUpToDateInformation(review, violation)) { 111 review.setLine(violation.getLineId()); 112 review.setTitle(violation.getMessage()); 113 updated.add(review); 114 } 115 } 116 } 117 118 @VisibleForTesting 119 static boolean hasUpToDateInformation(ReviewDto review, Violation violation) { 120 return StringUtils.equals(review.getTitle(), violation.getMessage()) && ObjectUtils.equals(review.getLine(), violation.getLineId()); 121 } 122 123 private void closeResolvedManualViolations(Collection<ReviewDto> openReviews, Project project, Resource resource, Set<ReviewDto> updated) { 124 for (ReviewDto openReview : openReviews) { 125 if (openReview.isManualViolation() && ReviewDto.STATUS_RESOLVED.equals(openReview.getStatus())) { 126 close(openReview, project, resource); 127 updated.add(openReview); 128 } 129 } 130 } 131 132 private void closeResolvedStandardViolations(Collection<ReviewDto> openReviews, List<Violation> violations, Project project, Resource resource, Set<ReviewDto> updated) { 133 Set<Integer> violationIds = Sets.newHashSet(Collections2.transform(violations, new ViolationToPermanentIdFunction())); 134 135 for (ReviewDto openReview : openReviews) { 136 if (!openReview.isManualViolation() && !violationIds.contains(openReview.getViolationPermanentId())) { 137 close(openReview, project, resource); 138 updated.add(openReview); 139 } 140 } 141 } 142 143 private void reopenUnresolvedViolations(Collection<ReviewDto> openReviews, Project project, Resource resource, Set<ReviewDto> updated) { 144 for (ReviewDto openReview : openReviews) { 145 if (ReviewDto.STATUS_RESOLVED.equals(openReview.getStatus()) && !ReviewDto.RESOLUTION_FALSE_POSITIVE.equals(openReview.getResolution()) 146 && !openReview.isManualViolation()) { 147 reopen(openReview, project, resource); 148 updated.add(openReview); 149 } 150 } 151 } 152 153 private void close(ReviewDto review, Project project, Resource resource) { 154 notifications.notifyClosed(review, project, resource); 155 review.setStatus(ReviewDto.STATUS_CLOSED); 156 review.setUpdatedAt(new Date()); 157 } 158 159 private void reopen(ReviewDto review, Project project, Resource resource) { 160 notifications.notifyReopened(review, project, resource); 161 review.setStatus(ReviewDto.STATUS_REOPENED); 162 review.setResolution(null); 163 review.setUpdatedAt(new Date()); 164 } 165 166 private static final class ViolationToPermanentIdFunction implements Function<Violation, Integer> { 167 public Integer apply(Violation violation) { 168 return violation.getPermanentId(); 169 } 170 } 171 }