001    /*
002     * Sonar, open source software quality management tool.
003     * Copyright (C) 2008-2011 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 java.util.Date;
023    import java.util.List;
024    import java.util.Map;
025    
026    import javax.persistence.Query;
027    
028    import org.sonar.api.batch.Decorator;
029    import org.sonar.api.batch.DecoratorContext;
030    import org.sonar.api.batch.DependsUpon;
031    import org.sonar.api.database.DatabaseSession;
032    import org.sonar.api.database.model.RuleFailureModel;
033    import org.sonar.api.database.model.Snapshot;
034    import org.sonar.api.resources.Project;
035    import org.sonar.api.resources.Resource;
036    import org.sonar.api.rules.Violation;
037    import org.sonar.batch.index.ResourcePersister;
038    import org.sonar.core.NotDryRun;
039    import org.sonar.jpa.entity.Review;
040    import org.sonar.plugins.core.timemachine.ViolationTrackingDecorator;
041    
042    import com.google.common.collect.Maps;
043    
044    /**
045     * Decorator that updates reviews that are linked to violations for which the message and the line number have changed. In this case, the
046     * message of the review and its corresponding line number must change.
047     */
048    @NotDryRun
049    @DependsUpon(CloseReviewsDecorator.REVIEW_LIFECYCLE_BARRIER)
050    public class UpdateReviewsDecorator implements Decorator {
051    
052      private ResourcePersister resourcePersister;
053      private DatabaseSession databaseSession;
054      private ViolationTrackingDecorator violationTrackingDecorator;
055      private Query updateReviewQuery;
056      private Query updateReviewQueryForNullLine;
057      private Map<Integer, Violation> violationsPerPermanentId;
058    
059      public UpdateReviewsDecorator(ResourcePersister resourcePersister, DatabaseSession databaseSession,
060          ViolationTrackingDecorator violationTrackingDecorator) {
061        this.resourcePersister = resourcePersister;
062        this.databaseSession = databaseSession;
063        this.violationTrackingDecorator = violationTrackingDecorator;
064        violationsPerPermanentId = Maps.newHashMap();
065      }
066    
067      public boolean shouldExecuteOnProject(Project project) {
068        return project.isLatestAnalysis();
069      }
070    
071      @SuppressWarnings({ "rawtypes" })
072      public void decorate(Resource resource, DecoratorContext context) {
073        Snapshot currentSnapshot = resourcePersister.getSnapshot(resource);
074        if (currentSnapshot != null) {
075          Date currentDate = new Date();
076          // prepare the map of rule failures by permanent_id
077          for (Violation violation : context.getViolations()) {
078            RuleFailureModel ruleFailure = violationTrackingDecorator.getReferenceViolation(violation);
079            if (ruleFailure != null) {
080              violationsPerPermanentId.put(ruleFailure.getPermanentId(), violation);
081            }
082          }
083          // and run the update
084          updateReviews(currentSnapshot.getResourceId(), currentDate);
085    
086          databaseSession.commit();
087        }
088      }
089    
090      @SuppressWarnings({ "unchecked" })
091      protected void updateReviews(int resourceId, Date currentDate) {
092        // prepare the DB native queries
093        updateReviewQuery = databaseSession
094            .createNativeQuery("UPDATE reviews SET title=?, resource_line=?, updated_at=CURRENT_TIMESTAMP WHERE id=?");
095        updateReviewQueryForNullLine = databaseSession
096            .createNativeQuery("UPDATE reviews SET title=?, resource_line=NULL, updated_at=CURRENT_TIMESTAMP WHERE id=?");
097        Query searchReviewsQuery = databaseSession.getEntityManager().createNativeQuery(
098            "SELECT * FROM reviews WHERE status!='CLOSED' AND resource_id=?", Review.class);
099        // and iterate over the reviews that we find
100        List<Review> reviews = searchReviewsQuery.setParameter(1, resourceId).getResultList();
101        for (Review review : reviews) {
102          checkReviewForUpdate(review, currentDate);
103        }
104      }
105    
106      protected void checkReviewForUpdate(Review review, Date currentDate) {
107        Violation violation = violationsPerPermanentId.get(review.getRuleFailurePermamentId());
108        if (violation != null) {
109          String message = violation.getMessage();
110          Integer line = violation.getLineId();
111          if ( !review.getTitle().equals(message) || (review.getResourceLine() == null && line != null)
112              || (review.getResourceLine() != null && !review.getResourceLine().equals(line))) {
113            if (line == null) {
114              updateReviewQueryForNullLine.setParameter(1, message).setParameter(2, review.getId()).executeUpdate();
115            } else {
116              updateReviewQuery.setParameter(1, message).setParameter(2, line).setParameter(3, review.getId()).executeUpdate();
117            }
118          }
119        }
120      }
121    
122    }