All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sonar.batch.issue.tracking.LocalIssueTracking Maven / Gradle / Ivy

There is a newer version: 25.1.0.102122
Show newest version
/*
 * SonarQube
 * Copyright (C) 2009-2016 SonarSource SA
 * mailto:contact AT sonarsource DOT com
 *
 * This program 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.
 *
 * This program 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 this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.sonar.batch.issue.tracking;

import com.google.common.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.api.batch.BatchSide;
import org.sonar.api.batch.fs.InputFile.Status;
import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.batch.rule.ActiveRule;
import org.sonar.api.batch.rule.ActiveRules;
import org.sonar.api.resources.ResourceUtils;
import org.sonar.batch.analysis.DefaultAnalysisMode;
import org.sonar.batch.index.BatchComponent;
import org.sonar.batch.issue.IssueTransformer;
import org.sonar.batch.repository.ProjectRepositories;
import org.sonar.core.issue.tracking.Input;
import org.sonar.core.issue.tracking.Tracker;
import org.sonar.core.issue.tracking.Tracking;
import org.sonar.scanner.protocol.output.ScannerReport;

@BatchSide
public class LocalIssueTracking {
  private final Tracker tracker;
  private final ServerLineHashesLoader lastLineHashes;
  private final ActiveRules activeRules;
  private final ServerIssueRepository serverIssueRepository;
  private final DefaultAnalysisMode mode;

  private boolean hasServerAnalysis;

  public LocalIssueTracking(Tracker tracker, ServerLineHashesLoader lastLineHashes,
    ActiveRules activeRules, ServerIssueRepository serverIssueRepository, ProjectRepositories projectRepositories, DefaultAnalysisMode mode) {
    this.tracker = tracker;
    this.lastLineHashes = lastLineHashes;
    this.serverIssueRepository = serverIssueRepository;
    this.mode = mode;
    this.activeRules = activeRules;
    this.hasServerAnalysis = projectRepositories.lastAnalysisDate() != null;
  }

  public void init() {
    if (hasServerAnalysis) {
      serverIssueRepository.load();
    }
  }

  public List trackIssues(BatchComponent component, Collection reportIssues, Date analysisDate) {
    List trackedIssues = new LinkedList<>();
    if (hasServerAnalysis) {
      // all the issues that are not closed in db before starting this module scan, including manual issues
      Collection serverIssues = loadServerIssues(component);

      if (shouldCopyServerIssues(component)) {
        // raw issues should be empty, we just need to deal with server issues (SONAR-6931)
        copyServerIssues(serverIssues, trackedIssues);
      } else {

        SourceHashHolder sourceHashHolder = loadSourceHashes(component);
        Collection rIssues = IssueTransformer.toTrackedIssue(component, reportIssues, sourceHashHolder);

        Input baseIssues = createBaseInput(serverIssues, sourceHashHolder);
        Input rawIssues = createRawInput(rIssues, sourceHashHolder);

        Tracking track = tracker.track(rawIssues, baseIssues);

        addUnmatchedFromServer(track.getUnmatchedBases(), trackedIssues);
        mergeMatched(track, trackedIssues, rIssues);
        addUnmatchedFromReport(track.getUnmatchedRaws(), trackedIssues, analysisDate);
      }
    }

    if (hasServerAnalysis && ResourceUtils.isRootProject(component.resource())) {
      // issues that relate to deleted components
      addIssuesOnDeletedComponents(trackedIssues);
    }

    return trackedIssues;
  }

  private static Input createBaseInput(Collection serverIssues, @Nullable SourceHashHolder sourceHashHolder) {
    List refHashes;

    if (sourceHashHolder != null && sourceHashHolder.getHashedReference() != null) {
      refHashes = Arrays.asList(sourceHashHolder.getHashedReference().hashes());
    } else {
      refHashes = new ArrayList<>(0);
    }

    return new IssueTrackingInput<>(serverIssues, refHashes);
  }

  private static Input createRawInput(Collection rIssues, @Nullable SourceHashHolder sourceHashHolder) {
    List baseHashes;
    if (sourceHashHolder != null && sourceHashHolder.getHashedSource() != null) {
      baseHashes = Arrays.asList(sourceHashHolder.getHashedSource().hashes());
    } else {
      baseHashes = new ArrayList<>(0);
    }

    return new IssueTrackingInput<>(rIssues, baseHashes);
  }

  private boolean shouldCopyServerIssues(BatchComponent component) {
    if (!mode.scanAllFiles() && component.isFile()) {
      DefaultInputFile inputFile = (DefaultInputFile) component.inputComponent();
      if (inputFile.status() == Status.SAME) {
        return true;
      }
    }
    return false;
  }

  private void copyServerIssues(Collection serverIssues, List trackedIssues) {
    for (ServerIssueFromWs serverIssue : serverIssues) {
      org.sonar.scanner.protocol.input.ScannerInput.ServerIssue unmatchedPreviousIssue = serverIssue.getDto();
      TrackedIssue unmatched = IssueTransformer.toTrackedIssue(unmatchedPreviousIssue);

      ActiveRule activeRule = activeRules.find(unmatched.getRuleKey());
      unmatched.setNew(false);

      if (activeRule == null) {
        // rule removed
        IssueTransformer.resolveRemove(unmatched);
      }

      trackedIssues.add(unmatched);
    }
  }

  @CheckForNull
  private SourceHashHolder loadSourceHashes(BatchComponent component) {
    SourceHashHolder sourceHashHolder = null;
    if (component.isFile()) {
      DefaultInputFile file = (DefaultInputFile) component.inputComponent();
      if (file == null) {
        throw new IllegalStateException("Resource " + component.resource() + " was not found in InputPath cache");
      }
      sourceHashHolder = new SourceHashHolder(file, lastLineHashes);
    }
    return sourceHashHolder;
  }

  private Collection loadServerIssues(BatchComponent component) {
    Collection serverIssues = new ArrayList<>();
    for (org.sonar.scanner.protocol.input.ScannerInput.ServerIssue previousIssue : serverIssueRepository.byComponent(component)) {
      serverIssues.add(new ServerIssueFromWs(previousIssue));
    }
    return serverIssues;
  }

  @VisibleForTesting
  protected void mergeMatched(Tracking result, Collection mergeTo, Collection rawIssues) {
    for (Map.Entry e : result.getMatchedRaws().entrySet()) {
      org.sonar.scanner.protocol.input.ScannerInput.ServerIssue dto = e.getValue().getDto();
      TrackedIssue tracked = e.getKey();

      // invariant fields
      tracked.setKey(dto.getKey());

      // non-persisted fields
      tracked.setNew(false);

      // fields to update with old values
      tracked.setResolution(dto.hasResolution() ? dto.getResolution() : null);
      tracked.setStatus(dto.getStatus());
      tracked.setAssignee(dto.hasAssigneeLogin() ? dto.getAssigneeLogin() : null);
      tracked.setCreationDate(new Date(dto.getCreationDate()));

      if (dto.getManualSeverity()) {
        // Severity overriden by user
        tracked.setSeverity(dto.getSeverity().name());
      }
      mergeTo.add(tracked);
    }
  }

  private void addUnmatchedFromServer(Iterable unmatchedIssues, Collection mergeTo) {
    for (ServerIssueFromWs unmatchedIssue : unmatchedIssues) {
      org.sonar.scanner.protocol.input.ScannerInput.ServerIssue unmatchedPreviousIssue = unmatchedIssue.getDto();
      TrackedIssue unmatched = IssueTransformer.toTrackedIssue(unmatchedPreviousIssue);
      updateUnmatchedIssue(unmatched);
      mergeTo.add(unmatched);
    }
  }

  private static void addUnmatchedFromReport(Iterable rawIssues, Collection trackedIssues, Date analysisDate) {
    for (TrackedIssue rawIssue : rawIssues) {
      rawIssue.setCreationDate(analysisDate);
      trackedIssues.add(rawIssue);
    }
  }

  private void addIssuesOnDeletedComponents(Collection issues) {
    for (org.sonar.scanner.protocol.input.ScannerInput.ServerIssue previous : serverIssueRepository.issuesOnMissingComponents()) {
      TrackedIssue dead = IssueTransformer.toTrackedIssue(previous);
      updateUnmatchedIssue(dead);
      issues.add(dead);
    }
  }

  private void updateUnmatchedIssue(TrackedIssue issue) {
    ActiveRule activeRule = activeRules.find(issue.getRuleKey());
    issue.setNew(false);

    boolean isRemovedRule = activeRule == null;

    if (isRemovedRule) {
      IssueTransformer.resolveRemove(issue);
    } else {
      IssueTransformer.close(issue);
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy