/* * Copyright 2013 - 2014 Felix Müller * * This file is part of CodeQ Invest. * * CodeQ Invest is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * CodeQ Invest 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with CodeQ Invest. If not, see <http://www.gnu.org/licenses/>. */ package org.codeqinvest.quality.analysis; import com.google.common.collect.Sets; import lombok.extern.slf4j.Slf4j; import org.codeqinvest.codechanges.CodeChangeProbabilityCalculator; import org.codeqinvest.codechanges.scm.CodeChurnCalculationException; import org.codeqinvest.codechanges.scm.ScmConnectionEncodingException; import org.codeqinvest.codechanges.scm.factory.ScmAvailabilityCheckerServiceFactory; import org.codeqinvest.quality.Artefact; import org.codeqinvest.quality.Project; import org.codeqinvest.quality.QualityViolation; import org.codeqinvest.sonar.ResourceNotFoundException; import org.codeqinvest.sonar.SonarConnectionSettings; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; import java.util.Set; /** * This is the main service of the quality assessment module. It * offers functionalities to analyze a given project. For that, * it collects all the necessary data from Sonar and the * corresponding source code management system. Before it performs * these steps, it checks for the availability of these third-party * systems. The result of an analysis is persisted in the database. * * @author fmueller */ // TODO does this really have to be annotated with Service? and think about the class name @Slf4j @Service class DefaultQualityAnalyzerService implements QualityAnalyzerService { private final ViolationsCalculatorService violationsCalculatorService; private final ScmAvailabilityCheckerServiceFactory scmAvailabilityCheckerServiceFactory; private final CodeChangeProbabilityCalculatorFactory codeChangeProbabilityCalculatorFactory; private final SecureChangeProbabilityCalculator secureChangeProbabilityCalculator; private final QualityViolationCostsCalculator costsCalculator; private final QualityAnalysisRepository qualityAnalysisRepository; @Autowired public DefaultQualityAnalyzerService(ViolationsCalculatorService violationsCalculatorService, ScmAvailabilityCheckerServiceFactory scmAvailabilityCheckerServiceFactory, CodeChangeProbabilityCalculatorFactory codeChangeProbabilityCalculatorFactory, SecureChangeProbabilityCalculator secureChangeProbabilityCalculator, QualityViolationCostsCalculator costsCalculator, QualityAnalysisRepository qualityAnalysisRepository) { this.violationsCalculatorService = violationsCalculatorService; this.scmAvailabilityCheckerServiceFactory = scmAvailabilityCheckerServiceFactory; this.codeChangeProbabilityCalculatorFactory = codeChangeProbabilityCalculatorFactory; this.secureChangeProbabilityCalculator = secureChangeProbabilityCalculator; this.costsCalculator = costsCalculator; this.qualityAnalysisRepository = qualityAnalysisRepository; } // TODO IMPORTANT: refactor this into commmand pattern to get better structure @Override public QualityAnalysis analyzeProject(Project project) { try { ViolationsAnalysisResult violationsAnalysisResult = violationsCalculatorService.calculateAllViolation(project); if (!violationsAnalysisResult.isSuccessful()) { log.error("Quality analysis for project {} failed due '{}'", project.getName(), violationsAnalysisResult.getFailureReason().get()); return qualityAnalysisRepository.save(QualityAnalysis.failed(project, zeroCostsForEachViolation(violationsAnalysisResult), violationsAnalysisResult.getFailureReason().get())); } log.info("Checking the availability of the SCM system {} for project {}", project.getScmSettings(), project.getName()); if (!scmAvailabilityCheckerServiceFactory.create(project.getScmSettings()).isAvailable(project.getScmSettings())) { return qualityAnalysisRepository.save(QualityAnalysis.failed(project, zeroCostsForEachViolation(violationsAnalysisResult), "The scm system is not available.")); } QualityAnalysis qualityAnalysis = addChangeProbabilityToEachArtifact(project, violationsAnalysisResult); if (!qualityAnalysis.isSuccessful()) { return qualityAnalysisRepository.save(qualityAnalysis); } qualityAnalysis = addSecureChangeProbabilityToEachArtifact(project, qualityAnalysis); log.info("Quality analysis succeeded for project {} with {} violations.", project.getName(), violationsAnalysisResult.getViolations().size()); return qualityAnalysisRepository.save(qualityAnalysis); } catch (Exception e) { String errorMessage = "Unexpected error occured during quality analysis!"; log.error(errorMessage, e); return QualityAnalysis.failed(project, new ArrayList<QualityViolation>(), errorMessage); } } private QualityAnalysis addChangeProbabilityToEachArtifact(Project project, ViolationsAnalysisResult violationsAnalysisResult) { log.info("Starting calculation of change probability for each artefact of project {}", project.getName()); CodeChangeProbabilityCalculator codeChangeProbabilityCalculator = codeChangeProbabilityCalculatorFactory.create(project.getCodeChangeSettings()); Set<String> computedArtefacts = Sets.newHashSet(); for (ViolationOccurence violation : violationsAnalysisResult.getViolations()) { Artefact artefact = violation.getArtefact(); if (!computedArtefacts.contains(artefact.getSonarIdentifier())) { final double changeProbability; try { changeProbability = codeChangeProbabilityCalculator.calculateCodeChangeProbability(project.getScmSettings(), artefact.getFilename()); artefact.setChangeProbability(changeProbability); computedArtefacts.add(artefact.getSonarIdentifier()); } catch (CodeChurnCalculationException e) { logFailedAnalysis(project, e); return QualityAnalysis.failed(project, zeroCostsForEachViolation(violationsAnalysisResult), "Error during calculating the code churn for " + violation.getArtefact().getName()); } catch (ScmConnectionEncodingException e) { logFailedAnalysis(project, e); return QualityAnalysis.failed(project, zeroCostsForEachViolation(violationsAnalysisResult), "Error with supplied scm connection encoding."); } } } try { return QualityAnalysis.success(project, calculateCostsForEachViolation(project.getSonarConnectionSettings(), violationsAnalysisResult)); } catch (ResourceNotFoundException e) { logFailedAnalysis(project, e); return QualityAnalysis.failed(project, zeroCostsForEachViolation(violationsAnalysisResult), "Resource not found during costs calculation."); } } private QualityAnalysis addSecureChangeProbabilityToEachArtifact(Project project, QualityAnalysis qualityAnalysis) { log.info("Starting calculation of secure change probability for each artefact of project {}", project.getName()); Set<String> computedArtefacts = Sets.newHashSet(); for (QualityViolation violation : qualityAnalysis.getViolations()) { Artefact artefact = violation.getArtefact(); if (!computedArtefacts.contains(artefact.getSonarIdentifier())) { try { double secureChangeProbability = secureChangeProbabilityCalculator.calculateSecureChangeProbability(project.getProfile(), project.getSonarConnectionSettings(), artefact); artefact.setSecureChangeProbability(secureChangeProbability); computedArtefacts.add(artefact.getSonarIdentifier()); } catch (ResourceNotFoundException e) { logFailedAnalysis(project, e); return QualityAnalysis.failed(project, qualityAnalysis.getViolations(), "Resource not found during secure change calculation"); } } } log.info("Finished calculation of secure change probability for each artefact of project {}", project.getName()); return qualityAnalysis; } private void logFailedAnalysis(Project project, Exception e) { log.error("Quality analysis for project " + project.getName() + " failed!", e); } private List<QualityViolation> calculateCostsForEachViolation(SonarConnectionSettings sonarConnectionSettings, ViolationsAnalysisResult violationsAnalysisResult) throws ResourceNotFoundException { List<QualityViolation> qualityViolations = new ArrayList<QualityViolation>(violationsAnalysisResult.getViolations().size()); for (ViolationOccurence violation : violationsAnalysisResult.getViolations()) { int remediationCosts = costsCalculator.calculateRemediationCosts(sonarConnectionSettings, violation); int nonRemediationCosts = costsCalculator.calculateNonRemediationCosts(sonarConnectionSettings, violation); qualityViolations.add(new QualityViolation(violation.getArtefact(), violation.getRequirement(), remediationCosts, nonRemediationCosts, violation.getWeightingMetricValue(), violation.getRequirement().getWeightingMetricIdentifier())); } return qualityViolations; } private List<QualityViolation> zeroCostsForEachViolation(ViolationsAnalysisResult violationsAnalysisResult) { List<QualityViolation> qualityViolations = new ArrayList<QualityViolation>(violationsAnalysisResult.getViolations().size()); for (ViolationOccurence violation : violationsAnalysisResult.getViolations()) { qualityViolations.add(new QualityViolation(violation.getArtefact(), violation.getRequirement(), 0, 0, 0, violation.getRequirement().getWeightingMetricIdentifier())); } return qualityViolations; } }