Package org.sonar.plugins.core.issue

Source Code of org.sonar.plugins.core.issue.IssueTrackingDecorator

/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube 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.
*
* SonarQube 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.plugins.core.issue;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
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.batch.SonarIndex;
import org.sonar.api.component.ResourcePerspectives;
import org.sonar.api.issue.Issuable;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.IssueChangeContext;
import org.sonar.api.profiles.RulesProfile;
import org.sonar.api.resources.Project;
import org.sonar.api.resources.Resource;
import org.sonar.api.resources.ResourceUtils;
import org.sonar.api.rules.ActiveRule;
import org.sonar.api.rules.Rule;
import org.sonar.api.rules.RuleFinder;
import org.sonar.api.utils.Duration;
import org.sonar.api.utils.KeyValueFormat;
import org.sonar.batch.issue.IssueCache;
import org.sonar.batch.scan.LastSnapshots;
import org.sonar.core.issue.IssueUpdater;
import org.sonar.core.issue.db.IssueChangeDto;
import org.sonar.core.issue.db.IssueDto;
import org.sonar.core.issue.workflow.IssueWorkflow;

import java.util.Collection;

@DependsUpon(DecoratorBarriers.ISSUES_ADDED)
@DependedUpon(DecoratorBarriers.ISSUES_TRACKED)
public class IssueTrackingDecorator implements Decorator {

  private static final Logger LOG = LoggerFactory.getLogger(IssueTrackingDecorator.class);

  private final IssueCache issueCache;
  private final InitialOpenIssuesStack initialOpenIssues;
  private final IssueTracking tracking;
  private final LastSnapshots lastSnapshots;
  private final SonarIndex index;
  private final IssueHandlers handlers;
  private final IssueWorkflow workflow;
  private final IssueUpdater updater;
  private final IssueChangeContext changeContext;
  private final ResourcePerspectives perspectives;
  private final RulesProfile rulesProfile;
  private final RuleFinder ruleFinder;

  public IssueTrackingDecorator(IssueCache issueCache, InitialOpenIssuesStack initialOpenIssues, IssueTracking tracking,
    LastSnapshots lastSnapshots, SonarIndex index,
    IssueHandlers handlers, IssueWorkflow workflow,
    IssueUpdater updater,
    Project project,
    ResourcePerspectives perspectives,
    RulesProfile rulesProfile,
    RuleFinder ruleFinder) {
    this.issueCache = issueCache;
    this.initialOpenIssues = initialOpenIssues;
    this.tracking = tracking;
    this.lastSnapshots = lastSnapshots;
    this.index = index;
    this.handlers = handlers;
    this.workflow = workflow;
    this.updater = updater;
    this.changeContext = IssueChangeContext.createScan(project.getAnalysisDate());
    this.perspectives = perspectives;
    this.rulesProfile = rulesProfile;
    this.ruleFinder = ruleFinder;
  }

  @Override
  public boolean shouldExecuteOnProject(Project project) {
    return true;
  }

  @Override
  public void decorate(Resource resource, DecoratorContext context) {
    Issuable issuable = perspectives.as(Issuable.class, resource);
    if (issuable != null) {
      doDecorate(resource);
    }
  }

  @VisibleForTesting
  void doDecorate(Resource resource) {
    Collection<DefaultIssue> issues = Lists.newArrayList();
    for (Issue issue : issueCache.byComponent(resource.getEffectiveKey())) {
      issues.add((DefaultIssue) issue);
    }
    issueCache.clear(resource.getEffectiveKey());
    // issues = all the issues created by rule engines during this module scan and not excluded by filters

    // all the issues that are not closed in db before starting this module scan, including manual issues
    Collection<IssueDto> dbOpenIssues = initialOpenIssues.selectAndRemoveIssues(resource.getEffectiveKey());

    SourceHashHolder sourceHashHolder = new SourceHashHolder(index, lastSnapshots, resource);

    IssueTrackingResult trackingResult = tracking.track(sourceHashHolder, dbOpenIssues, issues);

    // unmatched = issues that have been resolved + issues on disabled/removed rules + manual issues
    addUnmatched(trackingResult.unmatched(), sourceHashHolder, issues);

    mergeMatched(trackingResult);

    if (ResourceUtils.isProject(resource)) {
      // issues that relate to deleted components
      addIssuesOnDeletedComponents(issues);
    }

    for (DefaultIssue issue : issues) {
      workflow.doAutomaticTransition(issue, changeContext);
      handlers.execute(issue, changeContext);
      issueCache.put(issue);
    }
  }

  @VisibleForTesting
  protected void mergeMatched(IssueTrackingResult result) {
    for (DefaultIssue issue : result.matched()) {
      IssueDto ref = result.matching(issue);

      // invariant fields
      issue.setKey(ref.getKee());
      issue.setCreationDate(ref.getIssueCreationDate());
      issue.setUpdateDate(ref.getIssueUpdateDate());
      issue.setCloseDate(ref.getIssueCloseDate());

      // non-persisted fields
      issue.setNew(false);
      issue.setEndOfLife(false);
      issue.setOnDisabledRule(false);
      issue.setSelectedAt(ref.getSelectedAt());

      // fields to update with old values
      issue.setActionPlanKey(ref.getActionPlanKey());
      issue.setResolution(ref.getResolution());
      issue.setStatus(ref.getStatus());
      issue.setAssignee(ref.getAssignee());
      issue.setAuthorLogin(ref.getAuthorLogin());

      if (ref.getIssueAttributes() != null) {
        issue.setAttributes(KeyValueFormat.parse(ref.getIssueAttributes()));
      }

      // populate existing changelog
      Collection<IssueChangeDto> issueChangeDtos = initialOpenIssues.selectChangelog(issue.key());
      for (IssueChangeDto issueChangeDto : issueChangeDtos) {
        issue.addChange(issueChangeDto.toFieldDiffs());
      }

      // fields to update with current values
      if (ref.isManualSeverity()) {
        issue.setManualSeverity(true);
        issue.setSeverity(ref.getSeverity());
      } else {
        updater.setPastSeverity(issue, ref.getSeverity(), changeContext);
      }
      updater.setPastLine(issue, ref.getLine());
      updater.setPastMessage(issue, ref.getMessage(), changeContext);
      updater.setPastEffortToFix(issue, ref.getEffortToFix(), changeContext);
      Long debtInMinutes = ref.getDebt();
      Duration previousTechnicalDebt = debtInMinutes != null ? Duration.create(debtInMinutes) : null;
      updater.setPastTechnicalDebt(issue, previousTechnicalDebt, changeContext);
      updater.setPastProject(issue, ref.getProjectKey(), changeContext);
    }
  }

  private void addUnmatched(Collection<IssueDto> unmatchedIssues, SourceHashHolder sourceHashHolder, Collection<DefaultIssue> issues) {
    for (IssueDto unmatchedDto : unmatchedIssues) {
      DefaultIssue unmatched = unmatchedDto.toDefaultIssue();
      if (StringUtils.isNotBlank(unmatchedDto.getReporter()) && !Issue.STATUS_CLOSED.equals(unmatchedDto.getStatus())) {
        relocateManualIssue(unmatched, unmatchedDto, sourceHashHolder);
      }
      updateUnmatchedIssue(unmatched, false /* manual issues can be kept open */);
      issues.add(unmatched);
    }
  }

  private void addIssuesOnDeletedComponents(Collection<DefaultIssue> issues) {
    for (IssueDto deadDto : initialOpenIssues.selectAllIssues()) {
      DefaultIssue dead = deadDto.toDefaultIssue();
      updateUnmatchedIssue(dead, true);
      issues.add(dead);
    }
    initialOpenIssues.clear();
  }

  private void updateUnmatchedIssue(DefaultIssue issue, boolean forceEndOfLife) {
    issue.setNew(false);

    boolean manualIssue = !Strings.isNullOrEmpty(issue.reporter());
    Rule rule = ruleFinder.findByKey(issue.ruleKey());
    if (manualIssue) {
      // Manual rules are not declared in Quality profiles, so no need to check ActiveRule
      boolean isRemovedRule = rule == null || Rule.STATUS_REMOVED.equals(rule.getStatus());
      issue.setEndOfLife(forceEndOfLife || isRemovedRule);
      issue.setOnDisabledRule(isRemovedRule);
    } else {
      ActiveRule activeRule = rulesProfile.getActiveRule(issue.ruleKey().repository(), issue.ruleKey().rule());
      issue.setEndOfLife(true);
      issue.setOnDisabledRule(activeRule == null || rule == null || Rule.STATUS_REMOVED.equals(rule.getStatus()));
    }
  }

  private void relocateManualIssue(DefaultIssue newIssue, IssueDto oldIssue, SourceHashHolder sourceHashHolder) {
    LOG.debug("Trying to relocate manual issue {}", oldIssue.getKee());

    Integer previousLine = oldIssue.getLine();
    if (previousLine == null) {
      LOG.debug("Cannot relocate issue at resource level");
      return;
    }

    Collection<Integer> newLinesWithSameHash = sourceHashHolder.getNewLinesMatching(previousLine);
    LOG.debug("Found the following lines with same hash: {}", newLinesWithSameHash);
    if (newLinesWithSameHash.isEmpty()) {
      if (previousLine > sourceHashHolder.getHashedSource().length()) {
        LOG.debug("Old issue line {} is out of new source, closing and removing line number", previousLine);
        newIssue.setLine(null);
        updater.setStatus(newIssue, Issue.STATUS_CLOSED, changeContext);
        updater.setResolution(newIssue, Issue.RESOLUTION_REMOVED, changeContext);
        updater.setPastLine(newIssue, previousLine);
        updater.setPastMessage(newIssue, oldIssue.getMessage(), changeContext);
        updater.setPastEffortToFix(newIssue, oldIssue.getEffortToFix(), changeContext);
      }
    } else if (newLinesWithSameHash.size() == 1) {
      Integer newLine = newLinesWithSameHash.iterator().next();
      LOG.debug("Relocating issue to line {}", newLine);

      newIssue.setLine(newLine);
      updater.setPastLine(newIssue, previousLine);
      updater.setPastMessage(newIssue, oldIssue.getMessage(), changeContext);
      updater.setPastEffortToFix(newIssue, oldIssue.getEffortToFix(), changeContext);
    }
  }
}
TOP

Related Classes of org.sonar.plugins.core.issue.IssueTrackingDecorator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.