From 1414177d9f8a71fdeb524f6720023d36caacc276 Mon Sep 17 00:00:00 2001 From: Simon Brandhof Date: Tue, 20 Jan 2015 23:25:47 +0100 Subject: [PATCH] SONAR-5960 SONAR-5906 guess author and assignee of new issues --- .../computation/AnalysisReportService.java | 18 +- .../computation/ComputationComponents.java | 10 +- .../computation/ComputationService.java | 1 + .../{FinalIssues.java => IssueCache.java} | 8 +- .../computation/issue/IssueComputation.java | 22 ++- .../server/computation/issue/RuleCache.java | 2 +- .../computation/issue/RuleCacheLoader.java | 16 +- .../computation/issue/ScmAccountCache.java | 31 ++++ .../issue/ScmAccountCacheLoader.java | 50 ++++++ .../computation/issue/SourceLinesCache.java | 121 ++++++++++++++ .../computation/step/PersistIssuesStep.java | 10 +- .../step/SendIssueNotificationsStep.java | 10 +- .../index/SourceLineResultSetIterator.java | 36 ++-- .../user/index/UserIndexDefinition.java | 4 +- .../sonar/server/util/cache/MemoryCache.java | 14 +- .../issue/IssueComputationTest.java | 154 ++++++++++++++++++ .../issue/RuleCacheLoaderTest.java | 61 +++++++ .../computation/issue/RuleCacheTest.java | 40 +++++ .../issue/ScmAccountCacheLoaderTest.java | 60 +++++++ .../issue/SourceLinesCacheTest.java | 56 +++++++ .../server/util/cache/MemoryCacheTest.java | 11 +- .../issue/RuleCacheLoaderTest/shared.xml | 14 ++ .../ScmAccountCacheLoaderTest/user1.json | 9 + .../issue/SourceLinesCacheTest/load_data.xml | 8 + .../output/component/ReportComponent.java | 10 ++ .../batch/report/ComponentsPublisher.java | 1 + .../sonar/core/source/db/FileSourceDao.java | 32 ++++ .../sonar/core/source/db/FileSourceDto.java | 18 ++ 28 files changed, 762 insertions(+), 65 deletions(-) rename server/sonar-server/src/main/java/org/sonar/server/computation/issue/{FinalIssues.java => IssueCache.java} (85%) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/issue/ScmAccountCache.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/issue/ScmAccountCacheLoader.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/issue/SourceLinesCache.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/issue/IssueComputationTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleCacheLoaderTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleCacheTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/issue/ScmAccountCacheLoaderTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/issue/SourceLinesCacheTest.java create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/computation/issue/RuleCacheLoaderTest/shared.xml create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/computation/issue/ScmAccountCacheLoaderTest/user1.json create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/computation/issue/SourceLinesCacheTest/load_data.xml diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/AnalysisReportService.java b/server/sonar-server/src/main/java/org/sonar/server/computation/AnalysisReportService.java index eef338fca44..9a48c923812 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/AnalysisReportService.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/AnalysisReportService.java @@ -35,6 +35,8 @@ import org.sonar.batch.protocol.output.component.ReportComponents; import org.sonar.batch.protocol.output.issue.ReportIssue; import org.sonar.server.computation.issue.IssueComputation; +import javax.annotation.Nullable; + import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -76,14 +78,14 @@ public class AnalysisReportService { private void browseComponent(ComputationContext context, ReportHelper helper, ReportComponent component) { Iterable reportIssues = helper.getIssues(component.batchId()); - browseComponentIssues(context, reportIssues); + browseComponentIssues(context, component, reportIssues); for (ReportComponent child : component.children()) { browseComponent(context, helper, child); } } - private void browseComponentIssues(final ComputationContext context, Iterable reportIssues) { - issueComputation.processComponentIssues(Iterables.transform(reportIssues, new Function() { + private void browseComponentIssues(final ComputationContext context, ReportComponent component, Iterable reportIssues) { + issueComputation.processComponentIssues(component.uuid(), Iterables.transform(reportIssues, new Function() { @Override public DefaultIssue apply(ReportIssue input) { return toIssue(context, input); @@ -94,7 +96,8 @@ public class AnalysisReportService { private DefaultIssue toIssue(ComputationContext context, ReportIssue issue) { DefaultIssue defaultIssue = new DefaultIssue(); defaultIssue.setKey(issue.key()); - setComponentId(defaultIssue, context.getComponentByBatchId(issue.componentBatchId())); + ReportComponent component = context.getComponentByBatchId(issue.componentBatchId()); + setComponent(defaultIssue, component); defaultIssue.setRuleKey(RuleKey.of(issue.ruleRepo(), issue.ruleKey())); defaultIssue.setSeverity(issue.severity()); defaultIssue.setManualSeverity(issue.isManualSeverity()); @@ -130,14 +133,15 @@ public class AnalysisReportService { return issue; } - private DefaultIssue setComponentId(DefaultIssue issue, ReportComponent component) { + private DefaultIssue setComponent(DefaultIssue issue, @Nullable ReportComponent component) { if (component != null) { - issue.setComponentId((long) component.id()); + issue.setComponentId((long)component.id()); + issue.setComponentUuid(component.uuid()); } return issue; } - private DefaultIssue setDebt(DefaultIssue issue, Long debt) { + private DefaultIssue setDebt(DefaultIssue issue, @Nullable Long debt) { if (debt != null) { issue.setDebt(Duration.create(debt)); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/ComputationComponents.java b/server/sonar-server/src/main/java/org/sonar/server/computation/ComputationComponents.java index 98397d982d6..46c1e3c82f3 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/ComputationComponents.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/ComputationComponents.java @@ -20,10 +20,13 @@ package org.sonar.server.computation; import org.sonar.core.issue.db.UpdateConflictResolver; +import org.sonar.server.computation.issue.IssueCache; import org.sonar.server.computation.issue.IssueComputation; -import org.sonar.server.computation.issue.FinalIssues; import org.sonar.server.computation.issue.RuleCache; import org.sonar.server.computation.issue.RuleCacheLoader; +import org.sonar.server.computation.issue.ScmAccountCache; +import org.sonar.server.computation.issue.ScmAccountCacheLoader; +import org.sonar.server.computation.issue.SourceLinesCache; import org.sonar.server.computation.step.ComputationSteps; import java.util.Arrays; @@ -42,10 +45,13 @@ public class ComputationComponents { AnalysisReportService.class, // issues + ScmAccountCacheLoader.class, + ScmAccountCache.class, + SourceLinesCache.class, IssueComputation.class, RuleCache.class, RuleCacheLoader.class, - FinalIssues.class, + IssueCache.class, UpdateConflictResolver.class); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/ComputationService.java b/server/sonar-server/src/main/java/org/sonar/server/computation/ComputationService.java index d76f3a130f9..804d69fa628 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/ComputationService.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/ComputationService.java @@ -98,6 +98,7 @@ public class ComputationService implements ServerComponent { try { report.setFinishedAt(System2.INSTANCE.now()); activityService.write(session, Activity.Type.ANALYSIS_REPORT, new AnalysisReportLog(report, project)); + session.commit(); } finally { MyBatis.closeQuietly(session); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/FinalIssues.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueCache.java similarity index 85% rename from server/sonar-server/src/main/java/org/sonar/server/computation/issue/FinalIssues.java rename to server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueCache.java index 03cb9c12466..2a0303d6c92 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/FinalIssues.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueCache.java @@ -24,6 +24,7 @@ import org.sonar.api.utils.System2; import org.sonar.api.utils.TempFolder; import org.sonar.server.util.cache.DiskCache; +import java.io.File; import java.io.IOException; /** @@ -31,10 +32,13 @@ import java.io.IOException; * persisted in database (after issue tracking, auto-assignment, ...) * */ -public class FinalIssues extends DiskCache { +public class IssueCache extends DiskCache { - public FinalIssues(TempFolder tempFolder, System2 system2) throws IOException { + public IssueCache(TempFolder tempFolder, System2 system2) throws IOException { super(tempFolder.newFile("issues", ".dat"), system2); } + IssueCache(File file, System2 system2) { + super(file, system2); + } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueComputation.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueComputation.java index f6f53dd7ac1..01b61913eea 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueComputation.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueComputation.java @@ -27,14 +27,20 @@ import org.sonar.server.util.cache.DiskCache; public class IssueComputation { private final RuleCache ruleCache; + private final ScmAccountCache scmAccountCache; + private final SourceLinesCache linesCache; private final DiskCache.DiskAppender finalIssuesAppender; - public IssueComputation(RuleCache ruleCache, FinalIssues finalIssues) { + public IssueComputation(RuleCache ruleCache, SourceLinesCache linesCache, ScmAccountCache scmAccountCache, + IssueCache issueCache) { this.ruleCache = ruleCache; - this.finalIssuesAppender = finalIssues.newAppender(); + this.linesCache = linesCache; + this.scmAccountCache = scmAccountCache; + this.finalIssuesAppender = issueCache.newAppender(); } - public void processComponentIssues(Iterable issues) { + public void processComponentIssues(String componentUuid, Iterable issues) { + linesCache.init(componentUuid); for (DefaultIssue issue : issues) { if (issue.isNew()) { guessAuthor(issue); @@ -44,6 +50,7 @@ public class IssueComputation { } finalIssuesAppender.append(issue); } + linesCache.clear(); } public void afterReportProcessing() { @@ -51,11 +58,16 @@ public class IssueComputation { } private void guessAuthor(DefaultIssue issue) { - // TODO + if (issue.line() != null) { + issue.setAuthorLogin(linesCache.lineAuthor(issue.line())); + } } private void autoAssign(DefaultIssue issue) { - // TODO + String scmAccount = issue.authorLogin(); + if (scmAccount != null) { + issue.setAssignee(scmAccountCache.getNullable(scmAccount)); + } } private void copyRuleTags(DefaultIssue issue) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleCache.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleCache.java index c739cbef4e7..6b6a07a681d 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleCache.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleCache.java @@ -36,7 +36,7 @@ public class RuleCache extends MemoryCache { @CheckForNull public String ruleName(RuleKey key) { - RuleDto rule = get(key); + RuleDto rule = getNullable(key); return rule != null ? rule.getName() : null; } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleCacheLoader.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleCacheLoader.java index ab467b60584..c6936344a6c 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleCacheLoader.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleCacheLoader.java @@ -22,12 +22,10 @@ package org.sonar.server.computation.issue; import org.sonar.api.rule.RuleKey; import org.sonar.core.persistence.DbSession; import org.sonar.core.rule.RuleDto; -import org.sonar.server.util.cache.CacheLoader; import org.sonar.server.db.DbClient; +import org.sonar.server.util.cache.CacheLoader; import java.util.Collection; -import java.util.HashMap; -import java.util.List; import java.util.Map; public class RuleCacheLoader implements CacheLoader { @@ -50,16 +48,6 @@ public class RuleCacheLoader implements CacheLoader { @Override public Map loadAll(Collection keys) { - Map result = new HashMap<>(); - DbSession session = dbClient.openSession(false); - try { - List dtos = dbClient.ruleDao().getByKeys(session, (Collection) keys); - for (RuleDto dto : dtos) { - result.put(dto.getKey(), dto); - } - return result; - } finally { - session.close(); - } + throw new UnsupportedOperationException("See RuleDao"); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/ScmAccountCache.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/ScmAccountCache.java new file mode 100644 index 00000000000..1170c2dd6a4 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/ScmAccountCache.java @@ -0,0 +1,31 @@ +/* + * 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.server.computation.issue; + +import org.sonar.server.util.cache.MemoryCache; + +/** + * Cache of dictionary {SCM account -> SQ user login} + */ +public class ScmAccountCache extends MemoryCache { + public ScmAccountCache(ScmAccountCacheLoader loader) { + super(loader); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/ScmAccountCacheLoader.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/ScmAccountCacheLoader.java new file mode 100644 index 00000000000..6a74a52e670 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/ScmAccountCacheLoader.java @@ -0,0 +1,50 @@ +/* + * 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.server.computation.issue; + +import org.sonar.server.user.index.UserDoc; +import org.sonar.server.user.index.UserIndex; +import org.sonar.server.util.cache.CacheLoader; + +import java.util.Collection; +import java.util.Map; + +/** + * Loads the association between a SCM account and a SQ user + */ +public class ScmAccountCacheLoader implements CacheLoader { + + private final UserIndex index; + + public ScmAccountCacheLoader(UserIndex index) { + this.index = index; + } + + @Override + public String load(String scmAccount) { + UserDoc user = index.getNullableByScmAccount(scmAccount); + return user != null ? user.login() : null; + } + + @Override + public Map loadAll(Collection scmAccounts) { + throw new UnsupportedOperationException("Loading by multiple scm accounts is not supported yet"); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/SourceLinesCache.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/SourceLinesCache.java new file mode 100644 index 00000000000..09bcbcb516e --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/SourceLinesCache.java @@ -0,0 +1,121 @@ +/* + * 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.server.computation.issue; + +import com.google.common.base.Function; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVParser; +import org.apache.commons.csv.CSVRecord; +import org.apache.commons.io.IOUtils; +import org.sonar.core.source.db.FileSourceDto; +import org.sonar.server.db.DbClient; + +import javax.annotation.CheckForNull; + +import java.io.Reader; +import java.util.ArrayList; +import java.util.List; + +/** + * Cache of the lines of the currently processed file. Only a single file + * is kept in memory at a time. Moreover data is loaded on demand to avoid + * useless db trips. + *

+ * It assumes that db table FILE_SOURCES is up-to-date before using this + * cache. + */ +public class SourceLinesCache { + + private final DbClient dbClient; + private final List authors = new ArrayList<>(); + private final FileDataParser parserFunction = new FileDataParser(); + private boolean loaded = false; + private String currentFileUuid = null; + + public SourceLinesCache(DbClient dbClient) { + this.dbClient = dbClient; + } + + /** + * Marks the currently processed component + */ + void init(String fileUuid) { + loaded = false; + currentFileUuid = fileUuid; + } + + /** + * Last committer of the line, can be null. + * @param lineId starts at 1 + */ + @CheckForNull + public String lineAuthor(int lineId) { + loadIfNeeded(); + if (lineId <= authors.size()) { + return authors.get(lineId - 1); + } + return null; + } + + /** + * Load only on demand, to avoid useless db requests on files without any new issues + */ + private void loadIfNeeded() { + if (!loaded) { + dbClient.fileSourceDao().readDataStream(currentFileUuid, parserFunction); + loaded = true; + } + } + + /** + * Makes cache eligible to GC + */ + public void clear() { + authors.clear(); + } + + /** + * Number of lines in cache of the current file + */ + int countLines() { + return authors.size(); + } + + class FileDataParser implements Function { + @Override + public Void apply(Reader input) { + CSVParser csvParser = null; + try { + csvParser = new CSVParser(input, CSVFormat.DEFAULT); + authors.clear(); + for (CSVRecord csvRecord : csvParser) { + // do not keep all fields in memory + String author = csvRecord.get(FileSourceDto.CSV_INDEX_SCM_AUTHOR); + authors.add(author); + } + return null; + } catch (Exception e) { + throw new IllegalStateException("Fail to parse CSV data", e); + } finally { + IOUtils.closeQuietly(csvParser); + } + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/PersistIssuesStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/PersistIssuesStep.java index 1fd281b67be..e64ce2c590f 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/step/PersistIssuesStep.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/PersistIssuesStep.java @@ -34,7 +34,7 @@ import org.sonar.core.persistence.BatchSession; import org.sonar.core.persistence.DbSession; import org.sonar.core.persistence.MyBatis; import org.sonar.server.computation.ComputationContext; -import org.sonar.server.computation.issue.FinalIssues; +import org.sonar.server.computation.issue.IssueCache; import org.sonar.server.computation.issue.RuleCache; import org.sonar.server.db.DbClient; import org.sonar.server.util.CloseableIterator; @@ -45,15 +45,15 @@ public class PersistIssuesStep implements ComputationStep { private final System2 system2; private final UpdateConflictResolver conflictResolver; private final RuleCache ruleCache; - private final FinalIssues finalIssues; + private final IssueCache issueCache; public PersistIssuesStep(DbClient dbClient, System2 system2, UpdateConflictResolver conflictResolver, - RuleCache ruleCache, FinalIssues finalIssues) { + RuleCache ruleCache, IssueCache issueCache) { this.dbClient = dbClient; this.system2 = system2; this.conflictResolver = conflictResolver; this.ruleCache = ruleCache; - this.finalIssues = finalIssues; + this.issueCache = issueCache; } @Override @@ -63,7 +63,7 @@ public class PersistIssuesStep implements ComputationStep { IssueChangeMapper changeMapper = session.getMapper(IssueChangeMapper.class); int count = 0; - CloseableIterator issues = finalIssues.traverse(); + CloseableIterator issues = issueCache.traverse(); try { while (issues.hasNext()) { DefaultIssue issue = issues.next(); diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/SendIssueNotificationsStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/SendIssueNotificationsStep.java index 3b47050f966..befe904d961 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/step/SendIssueNotificationsStep.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/SendIssueNotificationsStep.java @@ -28,7 +28,7 @@ import org.sonar.api.rule.Severity; import org.sonar.api.utils.DateUtils; import org.sonar.core.component.ComponentDto; import org.sonar.server.computation.ComputationContext; -import org.sonar.server.computation.issue.FinalIssues; +import org.sonar.server.computation.issue.IssueCache; import org.sonar.server.computation.issue.RuleCache; import org.sonar.server.issue.notification.IssueNotifications; import org.sonar.server.util.CloseableIterator; @@ -40,13 +40,13 @@ import org.sonar.server.util.CloseableIterator; */ public class SendIssueNotificationsStep implements ComputationStep { - private final FinalIssues finalIssues; + private final IssueCache issueCache; private final RuleCache rules; private final IssueNotifications service; - public SendIssueNotificationsStep(FinalIssues finalIssues, RuleCache rules, + public SendIssueNotificationsStep(IssueCache issueCache, RuleCache rules, IssueNotifications service) { - this.finalIssues = finalIssues; + this.issueCache = issueCache; this.rules = rules; this.service = service; } @@ -54,7 +54,7 @@ public class SendIssueNotificationsStep implements ComputationStep { @Override public void execute(ComputationContext context) { NewIssuesStatistics newIssuesStatistics = new NewIssuesStatistics(); - CloseableIterator issues = finalIssues.traverse(); + CloseableIterator issues = issueCache.traverse(); try { while (issues.hasNext()) { DefaultIssue issue = issues.next(); diff --git a/server/sonar-server/src/main/java/org/sonar/server/source/index/SourceLineResultSetIterator.java b/server/sonar-server/src/main/java/org/sonar/server/source/index/SourceLineResultSetIterator.java index aaa1650d24d..28c318f95c6 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/source/index/SourceLineResultSetIterator.java +++ b/server/sonar-server/src/main/java/org/sonar/server/source/index/SourceLineResultSetIterator.java @@ -28,6 +28,7 @@ import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.math.NumberUtils; import org.sonar.api.utils.DateUtils; +import org.sonar.core.source.db.FileSourceDto; import org.sonar.server.db.DbClient; import org.sonar.server.db.ResultSetIterator; import org.sonar.server.db.migrations.SqlUtil; @@ -136,26 +137,27 @@ public class SourceLineResultSetIterator extends ResultSetIterator { public V get(K key) { V value = getNullable(key); if (value == null) { - throw new IllegalArgumentException("Not found: " + key); + throw new NotFoundException("Not found: " + key); } return value; } + /** + * Get values associated with keys. All the requested keys are included + * in the Map result. Value is null if the key is not found in cache. + */ public Map getAll(Iterable keys) { List missingKeys = new ArrayList<>(); Map result = new HashMap<>(); @@ -74,6 +80,12 @@ public class MemoryCache { Map missingValues = loader.loadAll(missingKeys); map.putAll(missingValues); result.putAll(missingValues); + for (K missingKey : missingKeys) { + if (!map.containsKey(missingKey)) { + map.put(missingKey, null); + result.put(missingKey, null); + } + } } return result; } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/IssueComputationTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/IssueComputationTest.java new file mode 100644 index 00000000000..fbade0f3bee --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/IssueComputationTest.java @@ -0,0 +1,154 @@ +/* + * 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.server.computation.issue; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterators; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.issue.internal.DefaultIssue; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.utils.System2; +import org.sonar.core.rule.RuleDto; + +import java.io.IOException; +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class IssueComputationTest { + + public static final RuleKey RULE_KEY = RuleKey.of("squid", "R1"); + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + // inputs + RuleCache ruleCache = mock(RuleCache.class); + SourceLinesCache lineCache = mock(SourceLinesCache.class); + ScmAccountCache scmAccountCache = mock(ScmAccountCache.class); + DefaultIssue issue = new DefaultIssue().setRuleKey(RULE_KEY).setKey("ISSUE_A"); + RuleDto rule = new RuleDto().setRepositoryKey(RULE_KEY.repository()).setRuleKey(RULE_KEY.rule()); + + // output + IssueCache issueCache; + + IssueComputation sut; + + @Before + public void setUp() throws IOException { + when(ruleCache.get(RULE_KEY)).thenReturn(rule); + issueCache = new IssueCache(temp.newFile(), System2.INSTANCE); + sut = new IssueComputation(ruleCache, lineCache, scmAccountCache, issueCache); + } + + @Test + public void store_issues_on_disk() throws Exception { + process(); + + assertThat(Iterators.getOnlyElement(issueCache.traverse()).key()).isEqualTo("ISSUE_A"); + } + + @Test + public void copy_rule_tags_on_new_issues() throws Exception { + issue.setNew(true); + rule.setTags(ImmutableSet.of("bug", "performance")); + rule.setSystemTags(ImmutableSet.of("blocker")); + + process(); + + assertThat(Iterators.getOnlyElement(issueCache.traverse()).tags()).containsOnly("blocker", "bug", "performance"); + } + + @Test + public void do_not_copy_rule_tags_on_existing_issues() throws Exception { + issue.setNew(false); + rule.setTags(ImmutableSet.of("bug", "performance")); + rule.setSystemTags(ImmutableSet.of("blocker")); + + process(); + + assertThat(Iterators.getOnlyElement(issueCache.traverse()).tags()).isEmpty(); + } + + @Test + public void guess_author_of_new_issues() throws Exception { + issue.setNew(true); + issue.setLine(3); + when(lineCache.lineAuthor(3)).thenReturn("charlie"); + + process(); + + assertThat(Iterators.getOnlyElement(issueCache.traverse()).authorLogin()).isEqualTo("charlie"); + } + + @Test + public void do_not_fail_if_missing_author_for_new_issues() throws Exception { + issue.setNew(true); + issue.setLine(3); + when(lineCache.lineAuthor(3)).thenReturn(null); + + process(); + + assertThat(Iterators.getOnlyElement(issueCache.traverse()).authorLogin()).isNull(); + } + + @Test + public void do_not_guess_author_of_existing_issues() throws Exception { + issue.setNew(false); + issue.setLine(3); + when(lineCache.lineAuthor(3)).thenReturn("charlie"); + + process(); + + assertThat(Iterators.getOnlyElement(issueCache.traverse()).authorLogin()).isNull(); + } + + @Test + public void auto_assign_new_issues() throws Exception { + issue.setNew(true); + issue.setAuthorLogin("charlie"); + when(scmAccountCache.getNullable("charlie")).thenReturn("char.lie"); + + process(); + + assertThat(Iterators.getOnlyElement(issueCache.traverse()).assignee()).isEqualTo("char.lie"); + } + + @Test + public void do_not_auto_assign_existing_issues() throws Exception { + issue.setNew(false); + issue.setAuthorLogin("charlie"); + when(scmAccountCache.getNullable("charlie")).thenReturn("char.lie"); + + process(); + + assertThat(Iterators.getOnlyElement(issueCache.traverse()).assignee()).isNull(); + } + + private void process() { + sut.processComponentIssues("FILE_A", Arrays.asList(issue)); + sut.afterReportProcessing(); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleCacheLoaderTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleCacheLoaderTest.java new file mode 100644 index 00000000000..22c2d258429 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleCacheLoaderTest.java @@ -0,0 +1,61 @@ +/* + * 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.server.computation.issue; + +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.rule.RuleKey; +import org.sonar.core.persistence.DbTester; +import org.sonar.server.db.DbClient; +import org.sonar.server.rule.db.RuleDao; + +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; + +public class RuleCacheLoaderTest { + + @Rule + public DbTester dbTester = new DbTester(); + + @Test + public void load_by_key() throws Exception { + dbTester.prepareDbUnit(getClass(), "shared.xml"); + DbClient dbClient = new DbClient(dbTester.database(), dbTester.myBatis(), new RuleDao()); + RuleCacheLoader loader = new RuleCacheLoader(dbClient); + + assertThat(loader.load(RuleKey.of("squid", "R001")).getName()).isEqualTo("Rule One"); + assertThat(loader.load(RuleKey.of("squid", "MISSING"))).isNull(); + } + + @Test + public void load_by_keys_is_not_supported() throws Exception { + DbClient dbClient = new DbClient(dbTester.database(), dbTester.myBatis(), new RuleDao()); + RuleCacheLoader loader = new RuleCacheLoader(dbClient); + try { + loader.loadAll(Collections.emptyList()); + fail(); + } catch (UnsupportedOperationException e) { + // see RuleDao#getByKeys() + } + } + +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleCacheTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleCacheTest.java new file mode 100644 index 00000000000..9c94c3ec2c0 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/RuleCacheTest.java @@ -0,0 +1,40 @@ +/* + * 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.server.computation.issue; + +import org.junit.Test; +import org.sonar.api.rule.RuleKey; +import org.sonar.core.rule.RuleDto; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class RuleCacheTest { + + @Test + public void ruleName() throws Exception { + RuleCacheLoader loader = mock(RuleCacheLoader.class); + when(loader.load(RuleKey.of("squid", "R002"))).thenReturn(new RuleDto().setName("Rule Two")); + RuleCache cache = new RuleCache(loader); + assertThat(cache.ruleName(RuleKey.of("squid", "R001"))).isNull(); + assertThat(cache.ruleName(RuleKey.of("squid", "R002"))).isEqualTo("Rule Two"); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/ScmAccountCacheLoaderTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/ScmAccountCacheLoaderTest.java new file mode 100644 index 00000000000..924fa5bc46f --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/ScmAccountCacheLoaderTest.java @@ -0,0 +1,60 @@ +/* + * 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.server.computation.issue; + +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.config.Settings; +import org.sonar.server.es.EsTester; +import org.sonar.server.user.index.UserIndex; +import org.sonar.server.user.index.UserIndexDefinition; + +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; + +public class ScmAccountCacheLoaderTest { + + @Rule + public EsTester esTester = new EsTester().addDefinitions(new UserIndexDefinition(new Settings())); + + @Test + public void load_login_for_scm_account() throws Exception { + esTester.putDocuments("users", "user", getClass(), "user1.json"); + + UserIndex index = new UserIndex(esTester.client()); + ScmAccountCacheLoader loader = new ScmAccountCacheLoader(index); + + assertThat(loader.load("missing")).isNull(); + assertThat(loader.load("jesuis@charlie.com")).isEqualTo("charlie"); + } + + @Test + public void load_by_multiple_scm_accounts_is_not_supported_yet() throws Exception { + UserIndex index = new UserIndex(esTester.client()); + ScmAccountCacheLoader loader = new ScmAccountCacheLoader(index); + try { + loader.loadAll(Collections.emptyList()); + fail(); + } catch (UnsupportedOperationException ignored) { + } + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/SourceLinesCacheTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/SourceLinesCacheTest.java new file mode 100644 index 00000000000..718c4520b38 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/SourceLinesCacheTest.java @@ -0,0 +1,56 @@ +/* + * 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.server.computation.issue; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.sonar.core.persistence.DbTester; +import org.sonar.core.source.db.FileSourceDao; +import org.sonar.server.db.DbClient; +import org.sonar.test.DbTests; + +import static org.assertj.core.api.Assertions.assertThat; + +@Category(DbTests.class) +public class SourceLinesCacheTest { + + @Rule + public DbTester dbTester = new DbTester(); + + @Test + public void load_data() throws Exception { + dbTester.prepareDbUnit(getClass(), "load_data.xml"); + DbClient dbClient = new DbClient(dbTester.database(), dbTester.myBatis(), new FileSourceDao(dbTester.myBatis())); + SourceLinesCache cache = new SourceLinesCache(dbClient); + cache.init("FILE_A"); + + // load data on demand -> still nothing in cache + assertThat(cache.countLines()).isEqualTo(0); + + assertThat(cache.lineAuthor(1)).isEqualTo("charlie"); + assertThat(cache.lineAuthor(2)).isEqualTo("cabu"); + assertThat(cache.lineAuthor(3)).isNull(); + assertThat(cache.countLines()).isEqualTo(2); + + cache.clear(); + assertThat(cache.countLines()).isEqualTo(0); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/util/cache/MemoryCacheTest.java b/server/sonar-server/src/test/java/org/sonar/server/util/cache/MemoryCacheTest.java index c860f4c63c0..56559ba59e9 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/util/cache/MemoryCacheTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/util/cache/MemoryCacheTest.java @@ -21,6 +21,7 @@ package org.sonar.server.util.cache; import com.google.common.collect.ImmutableMap; import org.junit.Test; +import org.sonar.server.exceptions.NotFoundException; import java.util.Arrays; import java.util.HashMap; @@ -62,18 +63,18 @@ public class MemoryCacheTest { try { cache.get("not_exists"); fail(); - } catch (IllegalArgumentException e) { + } catch (NotFoundException e) { assertThat(e).hasMessage("Not found: not_exists"); } } @Test public void getAllNullable() throws Exception { + // ask for 3 keys but only 2 are available in backed (third key is missing) List keys = Arrays.asList("one", "two", "three"); Map values = new HashMap<>(); values.put("one", "un"); values.put("two", "deux"); - values.put("three", null); when(loader.loadAll(keys)).thenReturn(values); assertThat(cache.getAll(keys)) .hasSize(3) @@ -81,11 +82,13 @@ public class MemoryCacheTest { .containsEntry("two", "deux") .containsEntry("three", null); + // ask for 4 keys. Only a single one was never loaded. The 3 others are kept from cache when(loader.loadAll(Arrays.asList("four"))).thenReturn(ImmutableMap.of("four", "quatre")); - assertThat(cache.getAll(Arrays.asList("one", "two", "four"))) - .hasSize(3) + assertThat(cache.getAll(Arrays.asList("one", "two", "three", "four"))) + .hasSize(4) .containsEntry("one", "un") .containsEntry("two", "deux") + .containsEntry("three", null) .containsEntry("four", "quatre"); verify(loader, times(2)).loadAll(anyCollection()); } diff --git a/server/sonar-server/src/test/resources/org/sonar/server/computation/issue/RuleCacheLoaderTest/shared.xml b/server/sonar-server/src/test/resources/org/sonar/server/computation/issue/RuleCacheLoaderTest/shared.xml new file mode 100644 index 00000000000..97ee8157a48 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/computation/issue/RuleCacheLoaderTest/shared.xml @@ -0,0 +1,14 @@ + + + + + + diff --git a/server/sonar-server/src/test/resources/org/sonar/server/computation/issue/ScmAccountCacheLoaderTest/user1.json b/server/sonar-server/src/test/resources/org/sonar/server/computation/issue/ScmAccountCacheLoaderTest/user1.json new file mode 100644 index 00000000000..f509e6b39a5 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/computation/issue/ScmAccountCacheLoaderTest/user1.json @@ -0,0 +1,9 @@ +{ + "login": "charlie", + "name": "Charlie", + "email": "charlie@hebdo.com", + "active": true, + "scmAccounts": ["charlie", "jesuis@charlie.com"], + "createdAt": 1500000000000, + "updatedAt": 1500000000000 +} diff --git a/server/sonar-server/src/test/resources/org/sonar/server/computation/issue/SourceLinesCacheTest/load_data.xml b/server/sonar-server/src/test/resources/org/sonar/server/computation/issue/SourceLinesCacheTest/load_data.xml new file mode 100644 index 00000000000..4079fb9d58d --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/computation/issue/SourceLinesCacheTest/load_data.xml @@ -0,0 +1,8 @@ + + + diff --git a/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/output/component/ReportComponent.java b/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/output/component/ReportComponent.java index 37b64b87e47..37768d82401 100644 --- a/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/output/component/ReportComponent.java +++ b/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/output/component/ReportComponent.java @@ -41,6 +41,7 @@ public class ReportComponent { private int snapshotId; private String path; private String name; + private String uuid; private Type type; // Only for files private Boolean isTest; @@ -84,6 +85,15 @@ public class ReportComponent { return path; } + public ReportComponent setUuid(String s) { + this.uuid = s; + return this; + } + + public String uuid() { + return uuid; + } + public ReportComponent setName(@Nullable String name) { this.name = name; return this; diff --git a/sonar-batch/src/main/java/org/sonar/batch/report/ComponentsPublisher.java b/sonar-batch/src/main/java/org/sonar/batch/report/ComponentsPublisher.java index 2117266659a..5917d9d0d43 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/report/ComponentsPublisher.java +++ b/sonar-batch/src/main/java/org/sonar/batch/report/ComponentsPublisher.java @@ -62,6 +62,7 @@ public class ComponentsPublisher implements ReportPublisher { .setId(r.getId()) .setName(getName(r)) .setPath(r.getPath()) + .setUuid(r.getUuid()) .setType(getType(r)) .setLanguageKey(getLanguageKey(r)) .setTest(isTest(r)); diff --git a/sonar-core/src/main/java/org/sonar/core/source/db/FileSourceDao.java b/sonar-core/src/main/java/org/sonar/core/source/db/FileSourceDao.java index 3e67c040b32..731a4ccd80f 100644 --- a/sonar-core/src/main/java/org/sonar/core/source/db/FileSourceDao.java +++ b/sonar-core/src/main/java/org/sonar/core/source/db/FileSourceDao.java @@ -20,6 +20,9 @@ package org.sonar.core.source.db; +import com.google.common.base.Function; +import org.apache.commons.dbutils.DbUtils; +import org.apache.commons.io.IOUtils; import org.sonar.api.BatchComponent; import org.sonar.api.ServerComponent; import org.sonar.core.persistence.DaoComponent; @@ -28,6 +31,12 @@ import org.sonar.core.persistence.MyBatis; import javax.annotation.CheckForNull; +import java.io.Reader; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + public class FileSourceDao implements BatchComponent, ServerComponent, DaoComponent { private final MyBatis mybatis; @@ -47,6 +56,29 @@ public class FileSourceDao implements BatchComponent, ServerComponent, DaoCompon } } + public void readDataStream(String fileUuid, Function function) { + DbSession dbSession = mybatis.openSession(false); + Connection connection = dbSession.getConnection(); + PreparedStatement pstmt = null; + ResultSet rs = null; + Reader reader = null; + try { + pstmt = connection.prepareStatement("select data from file_sources where file_uuid = ?"); + pstmt.setString(1, fileUuid); + rs = pstmt.executeQuery(); + if (rs.next()) { + reader = rs.getCharacterStream(1); + function.apply(reader); + } + } catch (SQLException e) { + throw new IllegalStateException("Fail to read FILE_SOURCES.DATA of file " + fileUuid, e); + } finally { + IOUtils.closeQuietly(reader); + DbUtils.closeQuietly(connection, pstmt, rs); + MyBatis.closeQuietly(dbSession); + } + } + public void insert(FileSourceDto dto) { DbSession session = mybatis.openSession(false); try { diff --git a/sonar-core/src/main/java/org/sonar/core/source/db/FileSourceDto.java b/sonar-core/src/main/java/org/sonar/core/source/db/FileSourceDto.java index ac965c3170b..bc5d3eacab7 100644 --- a/sonar-core/src/main/java/org/sonar/core/source/db/FileSourceDto.java +++ b/sonar-core/src/main/java/org/sonar/core/source/db/FileSourceDto.java @@ -23,6 +23,24 @@ import javax.annotation.CheckForNull; import javax.annotation.Nullable; public class FileSourceDto { + + public static final int CSV_INDEX_SCM_REVISION = 0; + public static final int CSV_INDEX_SCM_AUTHOR = 1; + public static final int CSV_INDEX_SCM_DATE = 2; + public static final int CSV_INDEX_UT_LINE_HITS = 3; + public static final int CSV_INDEX_UT_CONDITIONS = 4; + public static final int CSV_INDEX_UT_COVERED_CONDITIONS = 5; + public static final int CSV_INDEX_IT_LINE_HITS = 6; + public static final int CSV_INDEX_IT_CONDITIONS = 7; + public static final int CSV_INDEX_IT_COVERED_CONDITIONS = 8; + public static final int CSV_INDEX_OVERALL_LINE_HITS = 9; + public static final int CSV_INDEX_OVERALL_CONDITIONS = 10; + public static final int CSV_INDEX_OVERALL_COVERED_CONDITIONS = 11; + public static final int CSV_INDEX_HIGHLIGHTING = 12; + public static final int CSV_INDEX_SYMBOLS = 13; + public static final int CSV_INDEX_DUPLICATIONS = 14; + public static final int CSV_INDEX_SOURCE = 15; + private Long id; private String projectUuid; private String fileUuid; -- 2.39.5