Browse Source

SONAR-6623 extract issue tracking algorithm from batch

tags/5.2-RC1
Simon Brandhof 9 years ago
parent
commit
bf4118d6a9
100 changed files with 2051 additions and 4726 deletions
  1. 0
    2
      server/sonar-server/src/main/java/org/sonar/server/computation/batch/BatchReportReader.java
  2. 0
    5
      server/sonar-server/src/main/java/org/sonar/server/computation/batch/BatchReportReaderImpl.java
  3. 11
    1
      server/sonar-server/src/main/java/org/sonar/server/computation/component/ComponentVisitor.java
  4. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/computation/component/ProjectSettingsRepository.java
  5. 26
    8
      server/sonar-server/src/main/java/org/sonar/server/computation/container/ComputeEngineContainerImpl.java
  6. 82
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/BaseIssuesLoader.java
  7. 33
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/DebtCalculator.java
  8. 73
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/DefaultAssignee.java
  9. 149
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/IntegrateIssuesStep.java
  10. 64
    76
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueAssigner.java
  11. 3
    4
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueCache.java
  12. 0
    166
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueComputation.java
  13. 81
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueLifecycle.java
  14. 16
    19
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueListener.java
  15. 57
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueListeners.java
  16. 32
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/NewDebtCalculator.java
  17. 17
    21
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleTagsCopier.java
  18. 4
    3
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/ScmAccountToUser.java
  19. 4
    4
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/ScmAccountToUserLoader.java
  20. 70
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/TrackerBaseInputFactory.java
  21. 43
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/TrackerExecution.java
  22. 125
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/TrackerRawInputFactory.java
  23. 2
    3
      server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java
  24. 0
    100
      server/sonar-server/src/main/java/org/sonar/server/computation/step/ParseReportStep.java
  25. 3
    3
      server/sonar-server/src/main/java/org/sonar/server/computation/step/PersistIssuesStep.java
  26. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/computation/step/SendIssueNotificationsStep.java
  27. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/issue/AbstractChangeTagsAction.java
  28. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/issue/Action.java
  29. 2
    2
      server/sonar-server/src/main/java/org/sonar/server/issue/ActionService.java
  30. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/issue/AssignAction.java
  31. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/issue/CommentAction.java
  32. 3
    3
      server/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java
  33. 2
    2
      server/sonar-server/src/main/java/org/sonar/server/issue/IssueBulkChangeService.java
  34. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/issue/IssueChangelog.java
  35. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/issue/IssueChangelogFormatter.java
  36. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/issue/IssueChangelogService.java
  37. 3
    3
      server/sonar-server/src/main/java/org/sonar/server/issue/IssueCommentService.java
  38. 2
    2
      server/sonar-server/src/main/java/org/sonar/server/issue/IssueService.java
  39. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/issue/PlanAction.java
  40. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/issue/ServerIssueStorage.java
  41. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/issue/SetSeverityAction.java
  42. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/issue/TransitionAction.java
  43. 2
    2
      server/sonar-server/src/main/java/org/sonar/server/issue/actionplan/ActionPlanService.java
  44. 5
    10
      server/sonar-server/src/main/java/org/sonar/server/issue/db/IssueDao.java
  45. 2
    2
      server/sonar-server/src/main/java/org/sonar/server/issue/notification/IssueChangeNotification.java
  46. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueJsonWriter.java
  47. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java
  48. 2
    2
      server/sonar-server/src/main/java/org/sonar/server/issue/ws/ShowAction.java
  49. 23
    0
      server/sonar-server/src/main/java/org/sonar/server/source/db/FileSourceDao.java
  50. 1
    20
      server/sonar-server/src/test/java/org/sonar/server/computation/batch/BatchReportReaderImplTest.java
  51. 1
    17
      server/sonar-server/src/test/java/org/sonar/server/computation/batch/BatchReportReaderRule.java
  52. 226
    207
      server/sonar-server/src/test/java/org/sonar/server/computation/issue/IssueComputationTest.java
  53. 5
    6
      server/sonar-server/src/test/java/org/sonar/server/computation/issue/ScmAccountToUserLoaderTest.java
  54. 137
    0
      server/sonar-server/src/test/java/org/sonar/server/computation/issue/SourceAuthorsHolderTest.java
  55. 0
    118
      server/sonar-server/src/test/java/org/sonar/server/computation/issue/SourceLinesCacheTest.java
  56. 0
    122
      server/sonar-server/src/test/java/org/sonar/server/computation/step/ParseReportStepTest.java
  57. 3
    3
      server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistIssuesStepTest.java
  58. 1
    1
      server/sonar-server/src/test/java/org/sonar/server/computation/step/SendIssueNotificationsStepTest.java
  59. 2
    2
      server/sonar-server/src/test/java/org/sonar/server/issue/ActionServiceTest.java
  60. 2
    2
      server/sonar-server/src/test/java/org/sonar/server/issue/AddTagsActionTest.java
  61. 2
    2
      server/sonar-server/src/test/java/org/sonar/server/issue/AssignActionTest.java
  62. 2
    2
      server/sonar-server/src/test/java/org/sonar/server/issue/CommentActionTest.java
  63. 2
    2
      server/sonar-server/src/test/java/org/sonar/server/issue/InternalRubyIssueServiceTest.java
  64. 1
    1
      server/sonar-server/src/test/java/org/sonar/server/issue/IssueChangelogFormatterTest.java
  65. 2
    2
      server/sonar-server/src/test/java/org/sonar/server/issue/IssueChangelogServiceTest.java
  66. 1
    1
      server/sonar-server/src/test/java/org/sonar/server/issue/IssueCommentServiceMediumTest.java
  67. 3
    3
      server/sonar-server/src/test/java/org/sonar/server/issue/IssueCommentServiceTest.java
  68. 2
    2
      server/sonar-server/src/test/java/org/sonar/server/issue/PlanActionTest.java
  69. 2
    2
      server/sonar-server/src/test/java/org/sonar/server/issue/RemoveTagsActionTest.java
  70. 3
    3
      server/sonar-server/src/test/java/org/sonar/server/issue/ServerIssueStorageTest.java
  71. 2
    2
      server/sonar-server/src/test/java/org/sonar/server/issue/SetSeverityActionTest.java
  72. 2
    2
      server/sonar-server/src/test/java/org/sonar/server/issue/TransitionActionTest.java
  73. 2
    2
      server/sonar-server/src/test/java/org/sonar/server/issue/actionplan/ActionPlanServiceTest.java
  74. 2
    2
      server/sonar-server/src/test/java/org/sonar/server/issue/notification/IssueChangeNotificationTest.java
  75. 1
    1
      server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationTest.java
  76. 1
    1
      server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesStatisticsTest.java
  77. 1
    1
      server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueActionsWriterTest.java
  78. 3
    3
      server/sonar-server/src/test/java/org/sonar/server/issue/ws/ShowActionTest.java
  79. 663
    3025
      sonar-batch-protocol/src/main/gen-java/org/sonar/batch/protocol/output/BatchReport.java
  80. 0
    9
      sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/output/BatchReportReader.java
  81. 0
    14
      sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/output/BatchReportWriter.java
  82. 4
    27
      sonar-batch-protocol/src/main/protobuf/batch_report.proto
  83. 1
    14
      sonar-batch-protocol/src/test/java/org/sonar/batch/protocol/output/BatchReportReaderTest.java
  84. 0
    25
      sonar-batch-protocol/src/test/java/org/sonar/batch/protocol/output/BatchReportWriterTest.java
  85. 0
    8
      sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java
  86. 1
    1
      sonar-batch/src/main/java/org/sonar/batch/debt/DebtDecorator.java
  87. 2
    2
      sonar-batch/src/main/java/org/sonar/batch/debt/IssueChangelogDebtCalculator.java
  88. 1
    1
      sonar-batch/src/main/java/org/sonar/batch/issue/DefaultIssuable.java
  89. 1
    1
      sonar-batch/src/main/java/org/sonar/batch/issue/DefaultProjectIssues.java
  90. 1
    1
      sonar-batch/src/main/java/org/sonar/batch/issue/IssueCache.java
  91. 1
    1
      sonar-batch/src/main/java/org/sonar/batch/issue/IssueFilters.java
  92. 1
    1
      sonar-batch/src/main/java/org/sonar/batch/issue/ModuleIssues.java
  93. 0
    93
      sonar-batch/src/main/java/org/sonar/batch/issue/tracking/InitialOpenIssuesSensor.java
  94. 0
    86
      sonar-batch/src/main/java/org/sonar/batch/issue/tracking/InitialOpenIssuesStack.java
  95. 0
    141
      sonar-batch/src/main/java/org/sonar/batch/issue/tracking/IssueHandlers.java
  96. 1
    1
      sonar-batch/src/main/java/org/sonar/batch/issue/tracking/IssueTracking.java
  97. 0
    279
      sonar-batch/src/main/java/org/sonar/batch/issue/tracking/IssueTrackingDecorator.java
  98. 1
    1
      sonar-batch/src/main/java/org/sonar/batch/issue/tracking/IssueTrackingResult.java
  99. 5
    5
      sonar-batch/src/main/java/org/sonar/batch/issue/tracking/LocalIssueTracking.java
  100. 0
    0
      sonar-batch/src/main/java/org/sonar/batch/mediumtest/TaskResult.java

+ 0
- 2
server/sonar-server/src/main/java/org/sonar/server/computation/batch/BatchReportReader.java View File

@@ -36,8 +36,6 @@ public interface BatchReportReader {

List<BatchReport.Issue> readComponentIssues(int componentRef);

BatchReport.Issues readDeletedComponentIssues(int deletedComponentRef);

List<BatchReport.Duplication> readComponentDuplications(int componentRef);

List<BatchReport.Symbols.Symbol> readComponentSymbols(int componentRef);

+ 0
- 5
server/sonar-server/src/main/java/org/sonar/server/computation/batch/BatchReportReaderImpl.java View File

@@ -73,11 +73,6 @@ public class BatchReportReaderImpl implements BatchReportReader {
return delegate.readComponentIssues(componentRef);
}

@Override
public BatchReport.Issues readDeletedComponentIssues(int deletedComponentRef) {
return delegate.readDeletedComponentIssues(deletedComponentRef);
}

@Override
public List<BatchReport.Duplication> readComponentDuplications(int componentRef) {
return delegate.readComponentDuplications(componentRef);

+ 11
- 1
server/sonar-server/src/main/java/org/sonar/server/computation/component/ComponentVisitor.java View File

@@ -23,6 +23,16 @@ public interface ComponentVisitor {
void visit(Component tree);

enum Order {
PRE_ORDER, POST_ORDER
/**
* Each component is visited BEFORE its children. Top-down traversal of
* tree of components.
*/
PRE_ORDER,

/**
* Each component is visited AFTER its children. Bottom-up traversal of
* tree of components.
*/
POST_ORDER
}
}

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/computation/component/ProjectSettingsRepository.java View File

@@ -26,7 +26,7 @@ import org.sonar.server.properties.ProjectSettingsFactory;
/**
* Lazy loading of project settings.
*
* Should be refactored to be able to load settings from any components.
* TODO Should be refactored to be able to load settings from any components.
*/
public class ProjectSettingsRepository {


+ 26
- 8
server/sonar-server/src/main/java/org/sonar/server/computation/container/ComputeEngineContainerImpl.java View File

@@ -31,6 +31,7 @@ import org.sonar.api.utils.log.Loggers;
import org.sonar.api.utils.log.Profiler;
import org.sonar.core.component.Module;
import org.sonar.core.issue.db.UpdateConflictResolver;
import org.sonar.core.issue.tracking.Tracker;
import org.sonar.core.platform.ComponentContainer;
import org.sonar.server.computation.ComputationService;
import org.sonar.server.computation.ReportQueue;
@@ -43,13 +44,21 @@ import org.sonar.server.computation.component.TreeRootHolderImpl;
import org.sonar.server.computation.debt.DebtModelHolderImpl;
import org.sonar.server.computation.event.EventRepositoryImpl;
import org.sonar.server.computation.formula.CoreFormulaRepositoryImpl;
import org.sonar.server.computation.issue.BaseIssuesLoader;
import org.sonar.server.computation.issue.DebtCalculator;
import org.sonar.server.computation.issue.DefaultAssignee;
import org.sonar.server.computation.issue.IssueAssigner;
import org.sonar.server.computation.issue.IssueCache;
import org.sonar.server.computation.issue.IssueComputation;
import org.sonar.server.computation.issue.IssueLifecycle;
import org.sonar.server.computation.issue.IssueListeners;
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.issue.RuleTagsCopier;
import org.sonar.server.computation.issue.ScmAccountToUser;
import org.sonar.server.computation.issue.ScmAccountToUserLoader;
import org.sonar.server.computation.issue.TrackerBaseInputFactory;
import org.sonar.server.computation.issue.TrackerExecution;
import org.sonar.server.computation.issue.TrackerRawInputFactory;
import org.sonar.server.computation.language.LanguageRepositoryImpl;
import org.sonar.server.computation.measure.MeasureRepositoryImpl;
import org.sonar.server.computation.measure.newcoverage.NewCoverageMetricKeysModule;
@@ -156,14 +165,23 @@ public class ComputeEngineContainerImpl extends ComponentContainer implements Co
NewCoverageMetricKeysModule.class,

// issues
ScmAccountCacheLoader.class,
ScmAccountCache.class,
SourceLinesCache.class,
IssueComputation.class,
ScmAccountToUserLoader.class,
ScmAccountToUser.class,
IssueAssigner.class,
RuleTagsCopier.class,
RuleCache.class,
RuleCacheLoader.class,
IssueCache.class,
DefaultAssignee.class,
DebtCalculator.class,
IssueListeners.class,
IssueLifecycle.class,
UpdateConflictResolver.class,
TrackerBaseInputFactory.class,
TrackerRawInputFactory.class,
Tracker.class,
TrackerExecution.class,
BaseIssuesLoader.class,

// views
ViewIndex.class,

+ 82
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/issue/BaseIssuesLoader.java View File

@@ -0,0 +1,82 @@
/*
* 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.ImmutableMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.ibatis.session.ResultContext;
import org.apache.ibatis.session.ResultHandler;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.db.IssueDto;
import org.sonar.core.issue.db.IssueMapper;
import org.sonar.core.persistence.DbSession;
import org.sonar.core.persistence.MyBatis;
import org.sonar.server.computation.component.TreeRootHolder;
import org.sonar.server.db.DbClient;

/**
* Loads all the project open issues from database, including manual issues.
*/
public class BaseIssuesLoader {

private final TreeRootHolder treeRootHolder;
private final DbClient dbClient;
private final RuleCache ruleCache;

public BaseIssuesLoader(TreeRootHolder treeRootHolder, DbClient dbClient, RuleCache ruleCache) {
this.treeRootHolder = treeRootHolder;
this.dbClient = dbClient;
this.ruleCache = ruleCache;
}

public List<DefaultIssue> loadForComponentUuid(String componentUuid) {
DbSession session = dbClient.openSession(false);
final List<DefaultIssue> result = new ArrayList<>();
try {
Map<String, String> params = ImmutableMap.of("componentUuid", componentUuid);
session.select(IssueMapper.class.getName() + ".selectOpenByComponentUuid", params, new ResultHandler() {
@Override
public void handleResult(ResultContext resultContext) {
DefaultIssue issue = ((IssueDto) resultContext.getResultObject()).toDefaultIssue();
issue.setOnDisabledRule(ruleCache.getNullable(issue.ruleKey()) == null);
result.add(issue);
}
});
return result;
} finally {
MyBatis.closeQuietly(session);
}
}

/**
* Uuids of all the components that have open issues on this project.
*/
public Set<String> loadComponentUuids() {
DbSession session = dbClient.openSession(false);
try {
return dbClient.issueDao().selectComponentUuidsOfOpenIssuesForProjectUuid(session, treeRootHolder.getRoot().getUuid());
} finally {
MyBatis.closeQuietly(session);
}
}
}

+ 33
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/issue/DebtCalculator.java View File

@@ -0,0 +1,33 @@
/*
* 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.core.issue.DefaultIssue;
import org.sonar.server.computation.component.Component;

public class DebtCalculator extends IssueListener {

@Override
public void beforeIssue(Component component, DefaultIssue issue) {
if (issue.resolution() == null) {
// TODO
}
}
}

+ 73
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/issue/DefaultAssignee.java View File

@@ -0,0 +1,73 @@
/*
* 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.Strings;
import javax.annotation.CheckForNull;
import org.sonar.api.CoreProperties;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.server.computation.component.ProjectSettingsRepository;
import org.sonar.server.computation.component.TreeRootHolder;
import org.sonar.server.user.index.UserDoc;
import org.sonar.server.user.index.UserIndex;

/**
* The user who is optionally declared as being the assignee
* of all the issues which SCM author is not associated with any SonarQube user.
*/
public class DefaultAssignee {

private static final Logger LOG = Loggers.get(DefaultAssignee.class);

private final TreeRootHolder treeRootHolder;
private final UserIndex userIndex;
private final ProjectSettingsRepository settings;

private boolean loaded = false;
private String login = null;

public DefaultAssignee(TreeRootHolder treeRootHolder, UserIndex userIndex, ProjectSettingsRepository settings) {
this.treeRootHolder = treeRootHolder;
this.userIndex = userIndex;
this.settings = settings;
}

@CheckForNull
public String getLogin() {
if (!loaded) {
String configuredLogin = settings.getProjectSettings(treeRootHolder.getRoot().getKey()).getString(CoreProperties.DEFAULT_ISSUE_ASSIGNEE);
if (!Strings.isNullOrEmpty(configuredLogin) && isValidLogin(configuredLogin)) {
this.login = configuredLogin;
}
loaded = true;
}
return login;
}

private boolean isValidLogin(String s) {
UserDoc user = userIndex.getNullableByLogin(s);
if (user == null) {
LOG.info("the {} property was set with an unknown login: {}", CoreProperties.DEFAULT_ISSUE_ASSIGNEE, s);
return false;
}
return true;
}
}

+ 149
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/issue/IntegrateIssuesStep.java View File

@@ -0,0 +1,149 @@
/*
* 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.Sets;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.tracking.Tracking;
import org.sonar.server.computation.component.Component;
import org.sonar.server.computation.component.DepthTraversalTypeAwareVisitor;
import org.sonar.server.computation.component.TreeRootHolder;
import org.sonar.server.computation.step.ComputationStep;
import org.sonar.server.util.cache.DiskCache;

import static org.sonar.server.computation.component.DepthTraversalTypeAwareVisitor.Order.POST_ORDER;

public class IntegrateIssuesStep implements ComputationStep {

private final TreeRootHolder treeRootHolder;
private final TrackerExecution tracker;
private final IssueCache issueCache;
private final BaseIssuesLoader baseIssuesLoader;
private final IssueLifecycle issueLifecycle;
private final IssueListeners issueListeners;

public IntegrateIssuesStep(TreeRootHolder treeRootHolder, TrackerExecution tracker, IssueCache issueCache,
BaseIssuesLoader baseIssuesLoader, IssueLifecycle issueLifecycle,
IssueListeners issueListeners) {
this.treeRootHolder = treeRootHolder;
this.tracker = tracker;
this.issueCache = issueCache;
this.baseIssuesLoader = baseIssuesLoader;
this.issueLifecycle = issueLifecycle;
this.issueListeners = issueListeners;
}

@Override
public void execute() {
// all the components that had issues before this analysis
final Set<String> unprocessedComponentUuids = Sets.newHashSet(baseIssuesLoader.loadComponentUuids());

new DepthTraversalTypeAwareVisitor(Component.Type.FILE, POST_ORDER) {
@Override
public void visitAny(Component component) {
processIssues(component);
unprocessedComponentUuids.remove(component.getUuid());
}
}.visit(treeRootHolder.getRoot());

closeIssuesForDeletedComponentUuids(unprocessedComponentUuids);
}

private void processIssues(Component component) {
Tracking<DefaultIssue, DefaultIssue> tracking = tracker.track(component);
DiskCache<DefaultIssue>.DiskAppender cacheAppender = issueCache.newAppender();
try {
issueListeners.beforeComponent(component, tracking);
fillNewOpenIssues(component, tracking, cacheAppender);
fillExistingOpenIssues(component, tracking, cacheAppender);
closeUnmatchedBaseIssues(component, tracking, cacheAppender);
issueListeners.afterComponent(component);
} finally {
cacheAppender.close();
}
}

private void fillNewOpenIssues(Component component, Tracking<DefaultIssue, DefaultIssue> tracking, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
Set<DefaultIssue> issues = tracking.getUnmatchedRaws();
for (DefaultIssue issue : issues) {
issueLifecycle.initNewOpenIssue(issue);
issueListeners.beforeIssue(component, issue);
process(component, issue, cacheAppender);
}
}

private void fillExistingOpenIssues(Component component, Tracking<DefaultIssue, DefaultIssue> tracking, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
for (Map.Entry<DefaultIssue, DefaultIssue> entry : tracking.getMatchedRaws().entrySet()) {
DefaultIssue raw = entry.getKey();
DefaultIssue base = entry.getValue();
issueListeners.beforeIssue(component, raw);
issueLifecycle.mergeExistingOpenIssue(raw, base);
process(component, raw, cacheAppender);
}
for (Map.Entry<Integer, DefaultIssue> entry : tracking.getOpenManualIssuesByLine().entries()) {
int line = entry.getKey();
DefaultIssue manualIssue = entry.getValue();
manualIssue.setLine(line == 0 ? null : line);
issueListeners.beforeIssue(component, manualIssue);
process(component, manualIssue, cacheAppender);
}
}

private void closeUnmatchedBaseIssues(Component component, Tracking<DefaultIssue, DefaultIssue> tracking, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
for (DefaultIssue issue : tracking.getUnmatchedBases()) {
// TODO should replace flag "beingClosed" by express call to transition "automaticClose"
issue.setBeingClosed(true);
// TODO manual issues -> was updater.setResolution(newIssue, Issue.RESOLUTION_REMOVED, changeContext);. Is it a problem ?
process(component, issue, cacheAppender);
}
}

private void process(Component component, DefaultIssue issue, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
issueLifecycle.doAutomaticTransition(issue);
issueListeners.onIssue(component, issue);
cacheAppender.append(issue);
}

private void closeIssuesForDeletedComponentUuids(Set<String> deletedComponentUuids) {
DiskCache<DefaultIssue>.DiskAppender cacheAppender = issueCache.newAppender();
try {
for (String deletedComponentUuid : deletedComponentUuids) {
List<DefaultIssue> issues = baseIssuesLoader.loadForComponentUuid(deletedComponentUuid);
for (DefaultIssue issue : issues) {
issue.setBeingClosed(true);
issueLifecycle.doAutomaticTransition(issue);
// TODO execute listeners ? Component is currently missing.
cacheAppender.append(issue);
}
}
} finally {
cacheAppender.close();
}
}

@Override
public String getDescription() {
return "Integrate issues";
}

}

server/sonar-server/src/main/java/org/sonar/server/computation/issue/SourceLinesCache.java → server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueAssigner.java View File

@@ -27,102 +27,103 @@ import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.apache.commons.lang.StringUtils;
import org.sonar.batch.protocol.output.BatchReport;
import org.sonar.batch.protocol.output.BatchReport.Changesets.Changeset;
import org.sonar.batch.protocol.output.BatchReport.Changesets.Changeset.Builder;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.tracking.Tracking;
import org.sonar.server.computation.batch.BatchReportReader;
import org.sonar.server.computation.component.Component;
import org.sonar.server.source.index.SourceLineDoc;
import org.sonar.server.source.index.SourceLineIndex;

/**
* Cache of the lines of the currently processed file. Only a <strong>single</strong> file
* is kept in memory at a time. Data is loaded <strong>on demand</strong> (to avoid non necessary
* loading).<br />
* Detect the SCM author and SQ assignee.
* <p/>
* It relies on:
* <ul>
* <li>the SCM information sent in the report for modified files</li>
* <li>the source line index for non-modified files</li>
* <li>the Elasticsearch index of source lines for non-modified files</li>
* </ul>
*
*/
public class SourceLinesCache {

private final SourceLineIndex index;
private BatchReportReader reportReader;
public class IssueAssigner extends IssueListener {

private boolean loaded = false;
private BatchReport.Changesets scm;
private String currentFileUuid;
private Integer currentFileReportRef;
private final SourceLineIndex sourceLineIndex;
private final BatchReportReader reportReader;
private final DefaultAssignee defaultAssigne;
private final ScmAccountToUser scmAccountToUser;

private long lastCommitDate = 0L;
private String lastCommitAuthor = null;
private BatchReport.Changesets scmChangesets = null;

public SourceLinesCache(SourceLineIndex index) {
this.index = index;
public IssueAssigner(SourceLineIndex sourceLineIndex, BatchReportReader reportReader,
ScmAccountToUser scmAccountToUser, DefaultAssignee defaultAssigne) {
this.sourceLineIndex = sourceLineIndex;
this.reportReader = reportReader;
this.scmAccountToUser = scmAccountToUser;
this.defaultAssigne = defaultAssigne;
}

/**
* Marks the currently processed component
*/
void init(String fileUuid, @Nullable Integer fileReportRef, BatchReportReader reportReader) {
this.loaded = false;
this.currentFileUuid = fileUuid;
this.currentFileReportRef = fileReportRef;
this.lastCommitDate = 0L;
this.lastCommitAuthor = null;
this.reportReader = reportReader;
clear();
@Override
public void beforeComponent(Component component, Tracking tracking) {
// optimization - do not load data if there are no new issues
if (!tracking.getUnmatchedRaws().isEmpty()) {
scmChangesets = loadScmChangesetsFromReport(component);
if (scmChangesets == null) {
scmChangesets = loadScmChangesetsFromIndex(component);
}
computeLastCommitDateAndAuthor();
}
}

@Override
public void onIssue(Component component, DefaultIssue issue) {
if (issue.isNew()) {
String scmAuthor = guessScmAuthor(issue.line());
issue.setAuthorLogin(scmAuthor);
if (scmAuthor != null) {
String assigneeLogin = scmAccountToUser.getNullable(scmAuthor);
if (assigneeLogin == null) {
issue.setAssignee(defaultAssigne.getLogin());
} else {
issue.setAssignee(assigneeLogin);
}
}
}
}

@Override
public void afterComponent(Component component) {
lastCommitDate = 0L;
lastCommitAuthor = null;
scmChangesets = null;
}

/**
* Last committer of the line, can be null.
* Get the SCM login of the last committer on the line. When line is zero,
* then get the last committer on the file.
*/
@CheckForNull
public String lineAuthor(@Nullable Integer lineNumber) {
loadIfNeeded();

if (lineNumber == null) {
// issue on file, approximately estimate that author is the last committer on the file
return lastCommitAuthor;
}
private String guessScmAuthor(@Nullable Integer line) {
String author = null;
if (lineNumber <= scm.getChangesetIndexByLineCount()) {
BatchReport.Changesets.Changeset changeset = scm.getChangeset(scm.getChangesetIndexByLine(lineNumber - 1));
if (line != null && line <= scmChangesets.getChangesetIndexByLineCount()) {
BatchReport.Changesets.Changeset changeset = scmChangesets.getChangeset(scmChangesets.getChangesetIndexByLine(line - 1));
author = changeset.hasAuthor() ? changeset.getAuthor() : null;
}

return StringUtils.defaultIfEmpty(author, lastCommitAuthor);
}

private void loadIfNeeded() {
checkState();

if (!loaded) {
scm = loadScmFromReport();
loaded = scm != null;
}

if (!loaded) {
scm = loadLinesFromIndexAndBuildScm();
loaded = true;
}

computeLastCommitDateAndAuthor();
}

private BatchReport.Changesets loadScmFromReport() {
return reportReader.readChangesets(currentFileReportRef);
private BatchReport.Changesets loadScmChangesetsFromReport(Component component) {
return reportReader.readChangesets(component.getRef());
}

private BatchReport.Changesets loadLinesFromIndexAndBuildScm() {
List<SourceLineDoc> lines = index.getLines(currentFileUuid);
private BatchReport.Changesets loadScmChangesetsFromIndex(Component component) {
List<SourceLineDoc> lines = sourceLineIndex.getLines(component.getUuid());
Map<String, BatchReport.Changesets.Changeset> changesetByRevision = new HashMap<>();
BatchReport.Changesets.Builder scmBuilder = BatchReport.Changesets.newBuilder()
.setComponentRef(currentFileReportRef);
.setComponentRef(component.getRef());
for (SourceLineDoc sourceLine : lines) {
String scmRevision = sourceLine.scmRevision();
if (scmRevision == null || changesetByRevision.get(scmRevision) == null) {
Builder changeSetBuilder = BatchReport.Changesets.Changeset.newBuilder();
BatchReport.Changesets.Changeset.Builder changeSetBuilder = BatchReport.Changesets.Changeset.newBuilder();
String scmAuthor = sourceLine.scmAuthor();
if (scmAuthor != null) {
changeSetBuilder.setAuthor(scmAuthor);
@@ -135,7 +136,7 @@ public class SourceLinesCache {
changeSetBuilder.setRevision(scmRevision);
}

Changeset changeset = changeSetBuilder.build();
BatchReport.Changesets.Changeset changeset = changeSetBuilder.build();
scmBuilder.addChangeset(changeset);
scmBuilder.addChangesetIndexByLine(scmBuilder.getChangesetCount() - 1);
if (scmRevision != null) {
@@ -149,24 +150,11 @@ public class SourceLinesCache {
}

private void computeLastCommitDateAndAuthor() {
for (BatchReport.Changesets.Changeset changeset : scm.getChangesetList()) {
for (BatchReport.Changesets.Changeset changeset : scmChangesets.getChangesetList()) {
if (changeset.hasAuthor() && changeset.hasDate() && changeset.getDate() > lastCommitDate) {
lastCommitDate = changeset.getDate();
lastCommitAuthor = changeset.getAuthor();
}
}
}

private void checkState() {
if (currentFileReportRef == null) {
throw new IllegalStateException("Report component reference must not be null to use the cache");
}
}

/**
* Makes cache eligible to GC
*/
public void clear() {
scm = null;
}
}

+ 3
- 4
server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueCache.java View File

@@ -19,14 +19,13 @@
*/
package org.sonar.server.computation.issue;

import org.sonar.api.issue.internal.DefaultIssue;
import java.io.File;
import java.io.IOException;
import org.sonar.api.utils.System2;
import org.sonar.api.utils.TempFolder;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.server.util.cache.DiskCache;

import java.io.File;
import java.io.IOException;

/**
* Cache of all the issues involved in the analysis. Their state is as it will be
* persisted in database (after issue tracking, auto-assignment, ...)

+ 0
- 166
server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueComputation.java View File

@@ -1,166 +0,0 @@
/*
* 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.Strings;
import com.google.common.collect.Sets;
import java.util.Date;
import javax.annotation.Nullable;
import org.sonar.api.CoreProperties;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.FieldDiffs;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.utils.Duration;
import org.sonar.api.utils.KeyValueFormat;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.batch.protocol.output.BatchReport;
import org.sonar.core.rule.RuleDto;
import org.sonar.server.computation.batch.BatchReportReader;
import org.sonar.server.computation.component.ProjectSettingsRepository;
import org.sonar.server.user.index.UserDoc;
import org.sonar.server.user.index.UserIndex;
import org.sonar.server.util.cache.DiskCache;

public class IssueComputation {

private static final Logger LOG = Loggers.get(IssueComputation.class);

private final RuleCache ruleCache;
private final ScmAccountCache scmAccountCache;
private final SourceLinesCache linesCache;
private final DiskCache<DefaultIssue>.DiskAppender diskIssuesAppender;
private final UserIndex userIndex;
private final ProjectSettingsRepository projectSettingsRepository;
private final BatchReportReader reportReader;
private boolean hasAssigneeBeenComputed = false;
private String defaultAssignee = null;

public IssueComputation(RuleCache ruleCache, SourceLinesCache linesCache, ScmAccountCache scmAccountCache,
IssueCache issueCache, UserIndex userIndex, ProjectSettingsRepository projectSettingsRepository, BatchReportReader reportReader) {
this.ruleCache = ruleCache;
this.linesCache = linesCache;
this.scmAccountCache = scmAccountCache;
this.userIndex = userIndex;
this.reportReader = reportReader;
this.projectSettingsRepository = projectSettingsRepository;
this.diskIssuesAppender = issueCache.newAppender();
}

public void processComponentIssues(Iterable<BatchReport.Issue> issues, String componentUuid, @Nullable Integer componentReportRef,
String projectKey, String projectUuid) {
linesCache.init(componentUuid, componentReportRef, reportReader);
computeDefaultAssignee(projectSettingsRepository.getProjectSettings(projectKey).getString(CoreProperties.DEFAULT_ISSUE_ASSIGNEE));
for (BatchReport.Issue reportIssue : issues) {
DefaultIssue issue = toDefaultIssue(componentUuid, reportIssue, projectKey, projectUuid);
if (issue.isNew()) {
guessAuthor(issue);
autoAssign(issue, defaultAssignee);
copyRuleTags(issue);
}
diskIssuesAppender.append(issue);
}
linesCache.clear();
}

private DefaultIssue toDefaultIssue(String componentUuid, BatchReport.Issue issue, String projectKey, String projectUuid) {
DefaultIssue target = new DefaultIssue();
target.setKey(issue.getUuid());
target.setComponentUuid(componentUuid);
target.setRuleKey(RuleKey.of(issue.getRuleRepository(), issue.getRuleKey()));
target.setSeverity(issue.getSeverity().name());
target.setManualSeverity(issue.getManualSeverity());
target.setMessage(issue.hasMsg() ? issue.getMsg() : null);
target.setLine(issue.hasLine() ? issue.getLine() : null);
target.setProjectUuid(projectUuid);
target.setProjectKey(projectKey);
target.setEffortToFix(issue.hasEffortToFix() ? issue.getEffortToFix() : null);
target.setDebt(issue.hasDebtInMinutes() ? Duration.create(issue.getDebtInMinutes()) : null);
if (issue.hasDiffFields()) {
FieldDiffs fieldDiffs = FieldDiffs.parse(issue.getDiffFields());
fieldDiffs.setCreationDate(new Date(reportReader.readMetadata().getAnalysisDate()));
target.setCurrentChange(fieldDiffs);
}
target.setStatus(issue.getStatus());
target.setTags(issue.getTagList());
target.setResolution(issue.hasResolution() ? issue.getResolution() : null);
target.setReporter(issue.hasReporter() ? issue.getReporter() : null);
target.setAssignee(issue.hasAssignee() ? issue.getAssignee() : null);
target.setChecksum(issue.hasChecksum() ? issue.getChecksum() : null);
target.setAttributes(issue.hasAttributes() ? KeyValueFormat.parse(issue.getAttributes()) : null);
target.setAuthorLogin(issue.hasAuthorLogin() ? issue.getAuthorLogin() : null);
target.setActionPlanKey(issue.hasActionPlanKey() ? issue.getActionPlanKey() : null);
target.setCreationDate(issue.hasCreationDate() ? new Date(issue.getCreationDate()) : null);
target.setUpdateDate(issue.hasUpdateDate() ? new Date(issue.getUpdateDate()) : null);
target.setCloseDate(issue.hasCloseDate() ? new Date(issue.getCloseDate()) : null);
target.setChanged(issue.getIsChanged());
target.setNew(issue.getIsNew());
target.setSelectedAt(issue.hasSelectedAt() ? issue.getSelectedAt() : null);
target.setSendNotifications(issue.getMustSendNotification());
return target;
}

public void afterReportProcessing() {
diskIssuesAppender.close();
}

private void guessAuthor(DefaultIssue issue) {
// issue.authorLogin() can be not-null when old developer cockpit plugin (or other plugin)
// is still installed and executed during analysis
if (issue.authorLogin() == null) {
issue.setAuthorLogin(linesCache.lineAuthor(issue.line()));
}
}

private void autoAssign(DefaultIssue issue, @Nullable String defaultAssignee) {
// issue.assignee() can be not-null if the issue-assign-plugin is
// still installed and executed during analysis
if (issue.assignee() == null) {
String scmAccount = issue.authorLogin();
if (scmAccount != null) {
issue.setAssignee(scmAccountCache.getNullable(scmAccount));
}
if (issue.assignee() == null && defaultAssignee != null) {
issue.setAssignee(defaultAssignee);
}
}
}

private void copyRuleTags(DefaultIssue issue) {
RuleDto rule = ruleCache.get(issue.ruleKey());
issue.setTags(Sets.union(rule.getTags(), rule.getSystemTags()));
}

private void computeDefaultAssignee(@Nullable String login) {
if (hasAssigneeBeenComputed) {
return;
}

hasAssigneeBeenComputed = true;
if (!Strings.isNullOrEmpty(login)) {
UserDoc user = userIndex.getNullableByLogin(login);
if (user == null) {
LOG.info("the {} property was set with an unknown login: {}", CoreProperties.DEFAULT_ISSUE_ASSIGNEE, login);
} else {
defaultAssignee = login;
}
}
}
}

+ 81
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueLifecycle.java View File

@@ -0,0 +1,81 @@
/*
* 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 java.util.Date;
import org.sonar.api.issue.Issue;
import org.sonar.api.utils.internal.Uuids;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.IssueChangeContext;
import org.sonar.core.issue.IssueUpdater;
import org.sonar.core.issue.workflow.IssueWorkflow;
import org.sonar.server.computation.batch.BatchReportReader;

public class IssueLifecycle {

private final IssueWorkflow workflow;
private final IssueChangeContext changeContext;
private final IssueUpdater updater;

public IssueLifecycle(BatchReportReader reportReader, IssueWorkflow workflow, IssueUpdater updater) {
this.workflow = workflow;
this.updater = updater;
this.changeContext = IssueChangeContext.createScan(new Date(reportReader.readMetadata().getAnalysisDate()));
}

public void initNewOpenIssue(DefaultIssue issue) {
issue.setKey(Uuids.create());
issue.setCreationDate(changeContext.date());
issue.setUpdateDate(changeContext.date());
issue.setStatus(Issue.STATUS_OPEN);
}

public void mergeExistingOpenIssue(DefaultIssue raw, DefaultIssue base) {
raw.setNew(false);
raw.setKey(base.key());
raw.setCreationDate(base.creationDate());
raw.setUpdateDate(base.updateDate());
raw.setCloseDate(base.closeDate());
raw.setActionPlanKey(base.actionPlanKey());
raw.setResolution(base.resolution());
raw.setStatus(base.status());
raw.setAssignee(base.assignee());
raw.setAuthorLogin(base.authorLogin());
raw.setTags(base.tags());
if (base.manualSeverity()) {
raw.setManualSeverity(true);
raw.setSeverity(base.severity());
} else {
updater.setPastSeverity(raw, base.severity(), changeContext);
}

// TODO attributes + changelog

// fields coming from raw
updater.setPastLine(raw, base.getLine());
updater.setPastMessage(raw, base.getMessage(), changeContext);
updater.setPastEffortToFix(raw, base.effortToFix(), changeContext);
updater.setPastTechnicalDebt(raw, base.debt(), changeContext);
}

public void doAutomaticTransition(DefaultIssue issue) {
workflow.doAutomaticTransition(issue, changeContext);
}
}

sonar-batch/src/main/java/org/sonar/batch/phases/event/PersistersPhaseHandler.java → server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueListener.java View File

@@ -17,34 +17,31 @@
* 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.phases.event;
package org.sonar.server.computation.issue;

import org.sonar.api.batch.events.EventHandler;
import org.sonar.batch.index.ScanPersister;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.tracking.Tracking;
import org.sonar.server.computation.component.Component;

import java.util.List;
public abstract class IssueListener {

public interface PersistersPhaseHandler extends EventHandler {
public void beforeComponent(Component component, Tracking tracking) {

/**
* This interface is not intended to be implemented by clients.
*/
interface PersistersPhaseEvent {

/**
* @return list of Persisters in the order of execution
*/
List<ScanPersister> getPersisters();

boolean isStart();
}

boolean isEnd();
public void beforeIssue(Component component, DefaultIssue issue) {

}

/**
* Called before and after execution of all {@link ScanPersister}s.
* This method is called when tracking is done and issue is initialized. That means that the following fields
* are set: resolution, status, line, creation date, uuid and all the fields merged from base issues.
*/
void onPersistersPhase(PersistersPhaseEvent event);
public void onIssue(Component component, DefaultIssue issue) {

}

public void afterComponent(Component component) {

}
}

+ 57
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueListeners.java View File

@@ -0,0 +1,57 @@
/*
* 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.core.issue.DefaultIssue;
import org.sonar.core.issue.tracking.Tracking;
import org.sonar.server.computation.component.Component;

public class IssueListeners {

private final IssueListener[] listeners;

public IssueListeners(IssueListener[] listeners) {
this.listeners = listeners;
}

public void beforeComponent(Component component, Tracking tracking) {
for (IssueListener listener : listeners) {
listener.beforeComponent(component, tracking);
}
}

public void beforeIssue(Component component, DefaultIssue issue) {
for (IssueListener listener : listeners) {
listener.beforeIssue(component, issue);
}
}

public void onIssue(Component component, DefaultIssue issue) {
for (IssueListener listener : listeners) {
listener.onIssue(component, issue);
}
}

public void afterComponent(Component component) {
for (IssueListener listener : listeners) {
listener.afterComponent(component);
}
}
}

+ 32
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/issue/NewDebtCalculator.java View File

@@ -0,0 +1,32 @@
/*
* 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.core.issue.DefaultIssue;
import org.sonar.server.computation.component.Component;

public class NewDebtCalculator extends IssueListener {

@Override
public void onIssue(Component component, DefaultIssue issue) {
// TODO
}

}

sonar-batch/src/main/java/org/sonar/batch/phases/PersisterExecutionEvent.java → server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleTagsCopier.java View File

@@ -17,34 +17,30 @@
* 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.phases;
package org.sonar.server.computation.issue;

import org.sonar.batch.index.ScanPersister;
import org.sonar.batch.phases.event.PersisterExecutionHandler;
import java.util.Set;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.rule.RuleDto;
import org.sonar.server.computation.component.Component;

class PersisterExecutionEvent extends AbstractPhaseEvent<PersisterExecutionHandler>
implements PersisterExecutionHandler.PersisterExecutionEvent {
import static com.google.common.collect.Sets.union;

private final ScanPersister persister;
public class RuleTagsCopier extends IssueListener {

PersisterExecutionEvent(ScanPersister persister, boolean start) {
super(start);
this.persister = persister;
}

@Override
public ScanPersister getPersister() {
return persister;
}
private final RuleCache ruleCache;

@Override
public void dispatch(PersisterExecutionHandler handler) {
handler.onPersisterExecution(this);
public RuleTagsCopier(RuleCache ruleCache) {
this.ruleCache = ruleCache;
}

@Override
public Class getType() {
return PersisterExecutionHandler.class;
public void onIssue(Component component, DefaultIssue issue) {
if (issue.isNew()) {
// analyzer can provide some tags. They must be merged with rule tags
RuleDto rule = ruleCache.get(issue.ruleKey());
Set<String> ruleTags = union(rule.getTags(), rule.getSystemTags());
issue.setTags(union(issue.tags(), ruleTags));
}
}

}

server/sonar-server/src/main/java/org/sonar/server/computation/issue/ScmAccountCache.java → server/sonar-server/src/main/java/org/sonar/server/computation/issue/ScmAccountToUser.java View File

@@ -22,10 +22,11 @@ package org.sonar.server.computation.issue;
import org.sonar.server.util.cache.MemoryCache;

/**
* Cache of dictionary {SCM account -> SQ user login}
* Cache of dictionary {SCM account -> SQ user login}. Kept in memory
* during the execution of Compute Engine.
*/
public class ScmAccountCache extends MemoryCache<String,String> {
public ScmAccountCache(ScmAccountCacheLoader loader) {
public class ScmAccountToUser extends MemoryCache<String,String> {
public ScmAccountToUser(ScmAccountToUserLoader loader) {
super(loader);
}
}

server/sonar-server/src/main/java/org/sonar/server/computation/issue/ScmAccountCacheLoader.java → server/sonar-server/src/main/java/org/sonar/server/computation/issue/ScmAccountToUserLoader.java View File

@@ -36,17 +36,17 @@ import java.util.Map;
/**
* Loads the association between a SCM account and a SQ user
*/
public class ScmAccountCacheLoader implements CacheLoader<String, String> {
public class ScmAccountToUserLoader implements CacheLoader<String, String> {

private final Logger log;
private final UserIndex index;

public ScmAccountCacheLoader(UserIndex index) {
this(index, Loggers.get(ScmAccountCacheLoader.class));
public ScmAccountToUserLoader(UserIndex index) {
this(index, Loggers.get(ScmAccountToUserLoader.class));
}

@VisibleForTesting
ScmAccountCacheLoader(UserIndex index, Logger log) {
ScmAccountToUserLoader(UserIndex index, Logger log) {
this.log = log;
this.index = index;
}

+ 70
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/issue/TrackerBaseInputFactory.java View File

@@ -0,0 +1,70 @@
/*
* 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 java.util.List;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.tracking.Input;
import org.sonar.core.issue.tracking.LazyInput;
import org.sonar.core.persistence.DbSession;
import org.sonar.core.persistence.MyBatis;
import org.sonar.server.computation.component.Component;
import org.sonar.server.db.DbClient;

/**
* Factory of {@link Input} of base data for issue tracking. Data are lazy-loaded.
*/
public class TrackerBaseInputFactory {

private final BaseIssuesLoader baseIssuesLoader;
private final DbClient dbClient;

public TrackerBaseInputFactory(BaseIssuesLoader baseIssuesLoader, DbClient dbClient) {
this.baseIssuesLoader = baseIssuesLoader;
this.dbClient = dbClient;
}

public Input<DefaultIssue> create(Component component) {
return new BaseLazyInput(component);
}

private class BaseLazyInput extends LazyInput<DefaultIssue> {
private final Component component;

private BaseLazyInput(Component component) {
this.component = component;
}

@Override
protected Iterable<String> loadSourceLines() {
DbSession session = dbClient.openSession(false);
try {
return dbClient.fileSourceDao().selectLineHashes(session, component.getUuid());
} finally {
MyBatis.closeQuietly(session);
}
}

@Override
protected List<DefaultIssue> loadIssues() {
return baseIssuesLoader.loadForComponentUuid(component.getUuid());
}
}
}

+ 43
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/issue/TrackerExecution.java View File

@@ -0,0 +1,43 @@
/*
* 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.core.issue.DefaultIssue;
import org.sonar.core.issue.tracking.Tracker;
import org.sonar.core.issue.tracking.Tracking;
import org.sonar.server.computation.component.Component;

public class TrackerExecution {

private final TrackerBaseInputFactory baseInputFactory;
private final TrackerRawInputFactory rawInputFactory;
private final Tracker<DefaultIssue, DefaultIssue> tracker;

public TrackerExecution(TrackerBaseInputFactory baseInputFactory, TrackerRawInputFactory rawInputFactory,
Tracker<DefaultIssue, DefaultIssue> tracker) {
this.baseInputFactory = baseInputFactory;
this.rawInputFactory = rawInputFactory;
this.tracker = tracker;
}

public Tracking<DefaultIssue, DefaultIssue> track(Component component) {
return tracker.track(rawInputFactory.create(component), baseInputFactory.create(component));
}
}

+ 125
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/issue/TrackerRawInputFactory.java View File

@@ -0,0 +1,125 @@
/*
* 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.Lists;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.List;
import org.sonar.api.issue.Issue;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.utils.Duration;
import org.sonar.batch.protocol.output.BatchReport;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.tracking.Input;
import org.sonar.core.issue.tracking.LazyInput;
import org.sonar.core.issue.tracking.LineHashSequence;
import org.sonar.server.computation.batch.BatchReportReader;
import org.sonar.server.computation.component.Component;
import org.sonar.server.computation.component.TreeRootHolder;

public class TrackerRawInputFactory {

private final TreeRootHolder treeRootHolder;
private final BatchReportReader reportReader;
private final RuleCache ruleCache;

public TrackerRawInputFactory(TreeRootHolder treeRootHolder, BatchReportReader reportReader, RuleCache ruleCache) {
this.treeRootHolder = treeRootHolder;
this.reportReader = reportReader;
this.ruleCache = ruleCache;
}

public Input<DefaultIssue> create(Component component) {
return new RawLazyInput(component);
}

private class RawLazyInput extends LazyInput<DefaultIssue> {
private final Component component;

private RawLazyInput(Component component) {
this.component = component;
}

@Override
protected Iterable<String> loadSourceLines() {
return Lists.newArrayList(reportReader.readFileSource(component.getRef()));
}

@Override
protected List<DefaultIssue> loadIssues() {
List<BatchReport.Issue> reportIssues = reportReader.readComponentIssues(component.getRef());
List<DefaultIssue> issues = new ArrayList<>();
if (!reportIssues.isEmpty()) {
// optimization - do not load line hashes if there are no issues
LineHashSequence lineHashSeq = getLineHashSequence();
for (BatchReport.Issue reportIssue : reportIssues) {
DefaultIssue issue = toIssue(lineHashSeq, reportIssue);
if (isValid(issue, lineHashSeq)) {
issues.add(issue);
}
}
}
return issues;
}

private DefaultIssue toIssue(LineHashSequence lineHashSeq, BatchReport.Issue reportIssue) {
DefaultIssue issue = new DefaultIssue();
issue.setRuleKey(RuleKey.of(reportIssue.getRuleRepository(), reportIssue.getRuleKey()));
issue.setStatus(Issue.STATUS_OPEN);
issue.setComponentUuid(component.getUuid());
issue.setComponentKey(component.getKey());
issue.setProjectUuid(treeRootHolder.getRoot().getUuid());

if (reportIssue.hasLine()) {
issue.setLine(reportIssue.getLine());
issue.setChecksum(lineHashSeq.getHashForLine(reportIssue.getLine()));
} else {
issue.setChecksum("");
}
if (reportIssue.hasMsg()) {
issue.setMessage(reportIssue.getMsg());
}
if (reportIssue.hasSeverity()) {
issue.setSeverity(reportIssue.getSeverity().name());
}
if (reportIssue.hasEffortToFix()) {
issue.setEffortToFix(reportIssue.getEffortToFix());
}
if (reportIssue.hasDebtInMinutes()) {
issue.setDebt(Duration.create(reportIssue.getDebtInMinutes()));
}
issue.setTags(Sets.newHashSet(reportIssue.getTagList()));
// TODO issue attributes
return issue;
}
}

private boolean isValid(DefaultIssue issue, LineHashSequence lineHashSeq) {
// TODO log debug when invalid ?
if (ruleCache.getNullable(issue.ruleKey()) == null) {
return false;
}
if (issue.getLine() != null && !lineHashSeq.hasLine(issue.getLine())) {
return false;
}
return true;
}
}

+ 2
- 3
server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java View File

@@ -24,6 +24,7 @@ import com.google.common.collect.Iterables;
import java.util.Arrays;
import java.util.List;
import org.sonar.server.computation.container.ComputeEngineContainer;
import org.sonar.server.computation.issue.IntegrateIssuesStep;

/**
* Ordered list of steps to be executed
@@ -46,10 +47,8 @@ public class ComputationSteps {

FeedDebtModelStep.class,

// Read report
ParseReportStep.class,

// load project related stuffs
IntegrateIssuesStep.class,
QualityGateLoadingStep.class,
FeedPeriodsStep.class,


+ 0
- 100
server/sonar-server/src/main/java/org/sonar/server/computation/step/ParseReportStep.java View File

@@ -1,100 +0,0 @@
/*
* 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.step;

import java.util.List;
import org.sonar.batch.protocol.output.BatchReport;
import org.sonar.server.computation.batch.BatchReportReader;
import org.sonar.server.computation.component.Component;
import org.sonar.server.computation.component.DepthTraversalTypeAwareVisitor;
import org.sonar.server.computation.component.TreeRootHolder;
import org.sonar.server.computation.issue.IssueComputation;

public class ParseReportStep implements ComputationStep {

private final IssueComputation issueComputation;
private final BatchReportReader reportReader;
private final TreeRootHolder treeRootHolder;

public ParseReportStep(IssueComputation issueComputation, BatchReportReader reportReader, TreeRootHolder treeRootHolder) {
this.issueComputation = issueComputation;
this.reportReader = reportReader;
this.treeRootHolder = treeRootHolder;
}

@Override
public void execute() {
IssueDepthTraversalTypeAwareVisitor visitor = new IssueDepthTraversalTypeAwareVisitor();
visitor.visit(treeRootHolder.getRoot());
processDeletedComponents(visitor);
issueComputation.afterReportProcessing();
}

private void processDeletedComponents(IssueDepthTraversalTypeAwareVisitor visitor) {
int deletedComponentsCount = reportReader.readMetadata().getDeletedComponentsCount();
for (int componentRef = 1; componentRef <= deletedComponentsCount; componentRef++) {
BatchReport.Issues issues = reportReader.readDeletedComponentIssues(componentRef);
issueComputation.processComponentIssues(issues.getIssueList(), issues.getComponentUuid(), null, visitor.projectKey, visitor.projectUuid);
}
}

@Override
public String getDescription() {
return "Digest analysis report";
}

private class IssueDepthTraversalTypeAwareVisitor extends DepthTraversalTypeAwareVisitor {
private String projectKey;
private String projectUuid;

public IssueDepthTraversalTypeAwareVisitor() {
super(Component.Type.FILE, Order.PRE_ORDER);
}

@Override
public void visitProject(Component tree) {
projectKey = tree.getKey();
projectUuid = tree.getUuid();
executeForComponent(tree);
}

@Override
public void visitModule(Component module) {
executeForComponent(module);
}

@Override
public void visitDirectory(Component directory) {
executeForComponent(directory);
}

@Override
public void visitFile(Component file) {
executeForComponent(file);
}

private void executeForComponent(Component component) {
int componentRef = component.getRef();
List<BatchReport.Issue> issues = reportReader.readComponentIssues(componentRef);
issueComputation.processComponentIssues(issues, component.getUuid(), componentRef, projectKey, projectUuid);
}
}
}

+ 3
- 3
server/sonar-server/src/main/java/org/sonar/server/computation/step/PersistIssuesStep.java View File

@@ -21,10 +21,10 @@ package org.sonar.server.computation.step;

import org.sonar.api.issue.Issue;
import org.sonar.api.issue.IssueComment;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.DefaultIssueComment;
import org.sonar.api.issue.internal.FieldDiffs;
import org.sonar.api.utils.System2;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.DefaultIssueComment;
import org.sonar.core.issue.FieldDiffs;
import org.sonar.core.issue.db.IssueChangeDto;
import org.sonar.core.issue.db.IssueChangeMapper;
import org.sonar.core.issue.db.IssueDto;

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/computation/step/SendIssueNotificationsStep.java View File

@@ -23,7 +23,7 @@ import com.google.common.collect.ImmutableSet;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.server.computation.batch.BatchReportReader;
import org.sonar.server.computation.component.Component;
import org.sonar.server.computation.component.TreeRootHolder;

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/issue/AbstractChangeTagsAction.java View File

@@ -26,7 +26,7 @@ import com.google.common.collect.Sets;
import org.sonar.api.server.ServerSide;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.condition.IsUnResolved;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.api.server.rule.RuleTagFormat;
import org.sonar.core.issue.IssueUpdater;
import org.sonar.server.user.UserSession;

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/issue/Action.java View File

@@ -26,7 +26,7 @@ import com.google.common.collect.ImmutableList;
import org.sonar.api.server.ServerSide;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.condition.Condition;
import org.sonar.api.issue.internal.IssueChangeContext;
import org.sonar.core.issue.IssueChangeContext;
import org.sonar.server.user.UserSession;

import java.util.Collection;

+ 2
- 2
server/sonar-server/src/main/java/org/sonar/server/issue/ActionService.java View File

@@ -31,8 +31,8 @@ import org.sonar.api.issue.Issue;
import org.sonar.api.issue.action.Action;
import org.sonar.api.issue.action.Actions;
import org.sonar.api.issue.action.Function;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.IssueChangeContext;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.IssueChangeContext;
import org.sonar.core.issue.IssueUpdater;
import org.sonar.core.issue.db.IssueStorage;
import org.sonar.core.persistence.DbSession;

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/issue/AssignAction.java View File

@@ -24,7 +24,7 @@ import com.google.common.base.Strings;
import org.sonar.api.server.ServerSide;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.condition.IsUnResolved;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.api.user.User;
import org.sonar.api.user.UserFinder;
import org.sonar.core.issue.IssueUpdater;

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/issue/CommentAction.java View File

@@ -24,7 +24,7 @@ import com.google.common.base.Strings;
import java.util.Collection;
import java.util.Map;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.api.server.ServerSide;
import org.sonar.core.issue.IssueUpdater;
import org.sonar.server.user.UserSession;

+ 3
- 3
server/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java View File

@@ -45,9 +45,9 @@ import org.sonar.api.issue.ActionPlan;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.IssueComment;
import org.sonar.api.issue.action.Action;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.DefaultIssueComment;
import org.sonar.api.issue.internal.FieldDiffs;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.DefaultIssueComment;
import org.sonar.core.issue.FieldDiffs;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.server.ServerSide;
import org.sonar.api.user.User;

+ 2
- 2
server/sonar-server/src/main/java/org/sonar/server/issue/IssueBulkChangeService.java View File

@@ -35,8 +35,8 @@ import java.util.Set;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.IssueChangeContext;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.IssueChangeContext;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.Rule;
import org.sonar.api.utils.log.Logger;

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/issue/IssueChangelog.java View File

@@ -20,7 +20,7 @@
package org.sonar.server.issue;

import com.google.common.collect.Maps;
import org.sonar.api.issue.internal.FieldDiffs;
import org.sonar.core.issue.FieldDiffs;
import org.sonar.api.user.User;

import javax.annotation.CheckForNull;

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/issue/IssueChangelogFormatter.java View File

@@ -25,7 +25,7 @@ import java.util.Locale;
import java.util.Map;
import org.sonar.api.server.ServerSide;
import org.sonar.api.i18n.I18n;
import org.sonar.api.issue.internal.FieldDiffs;
import org.sonar.core.issue.FieldDiffs;
import org.sonar.api.utils.Duration;
import org.sonar.api.utils.Durations;
import org.sonar.core.issue.IssueUpdater;

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/issue/IssueChangelogService.java View File

@@ -23,7 +23,7 @@ import java.util.Collection;
import java.util.List;
import org.sonar.api.server.ServerSide;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.internal.FieldDiffs;
import org.sonar.core.issue.FieldDiffs;
import org.sonar.api.user.User;
import org.sonar.api.user.UserFinder;
import org.sonar.core.issue.db.IssueChangeDao;

+ 3
- 3
server/sonar-server/src/main/java/org/sonar/server/issue/IssueCommentService.java View File

@@ -24,9 +24,9 @@ import com.google.common.base.Strings;
import org.apache.commons.lang.StringUtils;
import org.sonar.api.server.ServerSide;
import org.sonar.api.issue.IssueComment;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.DefaultIssueComment;
import org.sonar.api.issue.internal.IssueChangeContext;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.DefaultIssueComment;
import org.sonar.core.issue.IssueChangeContext;
import org.sonar.api.utils.System2;
import org.sonar.core.issue.IssueUpdater;
import org.sonar.core.issue.db.IssueChangeDto;

+ 2
- 2
server/sonar-server/src/main/java/org/sonar/server/issue/IssueService.java View File

@@ -24,8 +24,8 @@ import com.google.common.base.Strings;
import org.apache.commons.lang.StringUtils;
import org.sonar.api.issue.ActionPlan;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.IssueChangeContext;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.IssueChangeContext;
import org.sonar.server.notification.NotificationManager;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.Severity;

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/issue/PlanAction.java View File

@@ -25,7 +25,7 @@ import org.sonar.api.server.ServerSide;
import org.sonar.api.issue.ActionPlan;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.condition.IsUnResolved;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.IssueUpdater;
import org.sonar.server.issue.actionplan.ActionPlanService;
import org.sonar.server.user.UserSession;

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/issue/ServerIssueStorage.java View File

@@ -20,7 +20,7 @@
package org.sonar.server.issue;

import org.sonar.api.server.ServerSide;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.api.rules.RuleFinder;
import org.sonar.core.component.ComponentDto;
import org.sonar.core.issue.db.IssueDto;

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/issue/SetSeverityAction.java View File

@@ -26,7 +26,7 @@ import java.util.Map;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.condition.Condition;
import org.sonar.api.issue.condition.IsUnResolved;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.api.server.ServerSide;
import org.sonar.api.web.UserRole;
import org.sonar.core.issue.IssueUpdater;

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/issue/TransitionAction.java View File

@@ -27,7 +27,7 @@ import java.util.Collection;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.api.server.ServerSide;
import org.sonar.core.issue.workflow.IssueWorkflow;
import org.sonar.core.issue.workflow.Transition;

+ 2
- 2
server/sonar-server/src/main/java/org/sonar/server/issue/actionplan/ActionPlanService.java View File

@@ -28,8 +28,8 @@ import java.util.Date;
import java.util.List;
import javax.annotation.CheckForNull;
import org.sonar.api.issue.ActionPlan;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.IssueChangeContext;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.IssueChangeContext;
import org.sonar.api.server.ServerSide;
import org.sonar.api.web.UserRole;
import org.sonar.core.issue.ActionPlanDeadlineComparator;

+ 5
- 10
server/sonar-server/src/main/java/org/sonar/server/issue/db/IssueDao.java View File

@@ -19,7 +19,9 @@
*/
package org.sonar.server.issue.db;

import org.apache.ibatis.session.ResultHandler;
import java.util.List;
import java.util.Set;
import javax.annotation.CheckForNull;
import org.sonar.core.issue.db.IssueDto;
import org.sonar.core.issue.db.IssueMapper;
import org.sonar.core.persistence.DaoComponent;
@@ -27,9 +29,6 @@ import org.sonar.core.persistence.DbSession;
import org.sonar.core.persistence.MyBatis;
import org.sonar.server.exceptions.NotFoundException;

import javax.annotation.CheckForNull;
import java.util.List;

public class IssueDao extends org.sonar.core.issue.db.IssueDao implements DaoComponent {

public IssueDao(MyBatis mybatis) {
@@ -57,12 +56,8 @@ public class IssueDao extends org.sonar.core.issue.db.IssueDao implements DaoCom
return mapper(session).selectByKeys(keys);
}

public void selectNonClosedIssuesByModuleUuid(DbSession session, String moduleUuid, ResultHandler handler) {
session.select("org.sonar.core.issue.db.IssueMapper.selectNonClosedIssuesByModuleUuid", moduleUuid, handler);
}

public void selectNonClosedIssuesByProjectUuid(DbSession session, String projectUuid, ResultHandler handler) {
session.select("org.sonar.core.issue.db.IssueMapper.selectNonClosedIssuesByProjectUuid", projectUuid, handler);
public Set<String> selectComponentUuidsOfOpenIssuesForProjectUuid(DbSession session, String projectUuid) {
return mapper(session).selectComponentUuidsOfOpenIssuesForProjectUuid(projectUuid);
}

public void insert(DbSession session, IssueDto dto) {

+ 2
- 2
server/sonar-server/src/main/java/org/sonar/server/issue/notification/IssueChangeNotification.java View File

@@ -21,8 +21,8 @@ package org.sonar.server.issue.notification;

import com.google.common.base.Strings;
import org.sonar.api.component.Component;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.FieldDiffs;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.FieldDiffs;
import org.sonar.api.notifications.Notification;

import javax.annotation.CheckForNull;

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueJsonWriter.java View File

@@ -35,13 +35,13 @@ import javax.annotation.Nullable;
import org.sonar.api.issue.ActionPlan;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.IssueComment;
import org.sonar.api.issue.internal.DefaultIssueComment;
import org.sonar.api.user.User;
import org.sonar.api.utils.DateUtils;
import org.sonar.api.utils.Duration;
import org.sonar.api.utils.Durations;
import org.sonar.api.utils.text.JsonWriter;
import org.sonar.core.component.ComponentDto;
import org.sonar.core.issue.DefaultIssueComment;
import org.sonar.markdown.Markdown;
import org.sonar.server.user.UserSession;
import org.sonar.server.ws.JsonWriterUtils;

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java View File

@@ -38,7 +38,6 @@ import org.apache.commons.lang.BooleanUtils;
import org.sonar.api.i18n.I18n;
import org.sonar.api.issue.ActionPlan;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.internal.DefaultIssueComment;
import org.sonar.api.resources.Language;
import org.sonar.api.resources.Languages;
import org.sonar.api.rule.RuleKey;
@@ -52,6 +51,7 @@ import org.sonar.api.user.UserFinder;
import org.sonar.api.utils.DateUtils;
import org.sonar.api.utils.text.JsonWriter;
import org.sonar.core.component.ComponentDto;
import org.sonar.core.issue.DefaultIssueComment;
import org.sonar.core.persistence.DbSession;
import org.sonar.server.component.ws.ComponentJsonWriter;
import org.sonar.server.db.DbClient;

+ 2
- 2
server/sonar-server/src/main/java/org/sonar/server/issue/ws/ShowAction.java View File

@@ -29,8 +29,8 @@ import org.sonar.api.i18n.I18n;
import org.sonar.api.issue.ActionPlan;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.IssueComment;
import org.sonar.api.issue.internal.DefaultIssueComment;
import org.sonar.api.issue.internal.FieldDiffs;
import org.sonar.core.issue.DefaultIssueComment;
import org.sonar.core.issue.FieldDiffs;
import org.sonar.api.server.debt.DebtCharacteristic;
import org.sonar.api.server.debt.internal.DefaultDebtCharacteristic;
import org.sonar.api.server.ws.Request;

+ 23
- 0
server/sonar-server/src/main/java/org/sonar/server/source/db/FileSourceDao.java View File

@@ -21,6 +21,7 @@
package org.sonar.server.source.db;

import com.google.common.base.Function;
import com.google.common.base.Splitter;
import java.io.Reader;
import java.sql.Connection;
import java.sql.PreparedStatement;
@@ -40,6 +41,7 @@ import org.sonar.core.source.db.FileSourceMapper;
@ServerSide
public class FileSourceDao implements DaoComponent {

private static final Splitter END_OF_LINE_SPLITTER = Splitter.on('\n');
private final MyBatis mybatis;

public FileSourceDao(MyBatis myBatis) {
@@ -66,6 +68,27 @@ public class FileSourceDao implements DaoComponent {
}
}

@CheckForNull
public Iterable<String> selectLineHashes(DbSession dbSession, String fileUuid) {
Connection connection = dbSession.getConnection();
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
pstmt = connection.prepareStatement("SELECT line_hashes FROM file_sources WHERE file_uuid=? AND data_type=?");
pstmt.setString(1, fileUuid);
pstmt.setString(2, Type.SOURCE);
rs = pstmt.executeQuery();
if (rs.next()) {
return END_OF_LINE_SPLITTER.split(rs.getString(1));
}
return null;
} catch (SQLException e) {
throw new IllegalStateException("Fail to read FILE_SOURCES.LINE_HASHES of file " + fileUuid, e);
} finally {
DbUtils.closeQuietly(connection, pstmt, rs);
}
}

public <T> void readLineHashesStream(DbSession dbSession, String fileUuid, Function<Reader, T> function) {
Connection connection = dbSession.getConnection();
PreparedStatement pstmt = null;

+ 1
- 20
server/sonar-server/src/test/java/org/sonar/server/computation/batch/BatchReportReaderImplTest.java View File

@@ -42,7 +42,7 @@ public class BatchReportReaderImplTest {
private static final BatchReport.Measure MEASURE = BatchReport.Measure.newBuilder().build();
private static final BatchReport.Component COMPONENT = BatchReport.Component.newBuilder().setRef(COMPONENT_REF).build();
private static final BatchReport.Issue ISSUE = BatchReport.Issue.newBuilder().build();
private static final BatchReport.Issues ISSUES = BatchReport.Issues.newBuilder().setComponentRef(COMPONENT_REF).setComponentUuid(COMPONENT_UUID).addIssue(ISSUE).build();
private static final BatchReport.Issues ISSUES = BatchReport.Issues.newBuilder().setComponentRef(COMPONENT_REF).addIssue(ISSUE).build();
private static final BatchReport.Duplication DUPLICATION = BatchReport.Duplication.newBuilder().build();
private static final BatchReport.Symbols.Symbol SYMBOL = BatchReport.Symbols.Symbol.newBuilder().build();
private static final BatchReport.SyntaxHighlighting SYNTAX_HIGHLIGHTING_1 = BatchReport.SyntaxHighlighting.newBuilder().build();
@@ -166,25 +166,6 @@ public class BatchReportReaderImplTest {
assertThat(underTest.readComponentIssues(COMPONENT_REF)).isNotSameAs(underTest.readComponentIssues(COMPONENT_REF));
}

@Test(expected = IllegalStateException.class)
public void readDeletedComponentIssues_throws_ISE_if_file_does_not_exist() {
underTest.readDeletedComponentIssues(COMPONENT_REF);
}

@Test
public void verify_readDeletedComponentIssues_returns_Issues() {
writer.writeDeletedComponentIssues(COMPONENT_REF, COMPONENT_UUID, of(ISSUE));

assertThat(underTest.readDeletedComponentIssues(COMPONENT_REF)).isEqualTo(ISSUES);
}

@Test
public void readDeletedComponentIssues_it_not_cached() {
writer.writeDeletedComponentIssues(COMPONENT_REF, COMPONENT_UUID, of(ISSUE));

assertThat(underTest.readDeletedComponentIssues(COMPONENT_REF)).isNotSameAs(underTest.readDeletedComponentIssues(COMPONENT_REF));
}

@Test
public void readComponentDuplications_returns_empty_list_if_file_does_not_exist() {
assertThat(underTest.readComponentDuplications(COMPONENT_REF)).isEmpty();

+ 1
- 17
server/sonar-server/src/test/java/org/sonar/server/computation/batch/BatchReportReaderRule.java View File

@@ -39,7 +39,6 @@ public class BatchReportReaderRule implements TestRule, BatchReportReader {
private Map<Integer, BatchReport.Changesets> changesets = new HashMap<>();
private Map<Integer, BatchReport.Component> components = new HashMap<>();
private Map<Integer, List<BatchReport.Issue>> issues = new HashMap<>();
private Map<Integer, BatchReport.Issues> deletedIssues = new HashMap<>();
private Map<Integer, List<BatchReport.Duplication>> duplications = new HashMap<>();
private Map<Integer, List<BatchReport.Symbols.Symbol>> symbols = new HashMap<>();
private Map<Integer, List<BatchReport.SyntaxHighlighting>> syntaxHighlightings = new HashMap<>();
@@ -55,8 +54,7 @@ public class BatchReportReaderRule implements TestRule, BatchReportReader {
public void evaluate() throws Throwable {
try {
statement.evaluate();
}
finally {
} finally {
clear();
}
}
@@ -69,7 +67,6 @@ public class BatchReportReaderRule implements TestRule, BatchReportReader {
this.changesets.clear();
this.components.clear();
this.issues.clear();
this.deletedIssues.clear();
this.duplications.clear();
this.symbols.clear();
this.syntaxHighlightings.clear();
@@ -132,19 +129,6 @@ public class BatchReportReaderRule implements TestRule, BatchReportReader {
this.issues.put(componentRef, issue);
}

@Override
public BatchReport.Issues readDeletedComponentIssues(int deletedComponentRef) {
BatchReport.Issues issues = this.deletedIssues.get(deletedComponentRef);
if (issues == null) {
throw new IllegalStateException("Unable to issues for deleted component #" + deletedComponentRef);
}
return issues;
}

public void putDeletedIssues(int componentRef, BatchReport.Issues issues) {
this.deletedIssues.put(componentRef, issues);
}

@Override
public List<BatchReport.Duplication> readComponentDuplications(int componentRef) {
return nonNull(this.duplications.get(componentRef));

+ 226
- 207
server/sonar-server/src/test/java/org/sonar/server/computation/issue/IssueComputationTest.java View File

@@ -17,210 +17,229 @@
* 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 java.io.IOException;
import java.util.Arrays;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.sonar.api.CoreProperties;
import org.sonar.api.config.Settings;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.utils.System2;
import org.sonar.api.utils.log.LogTester;
import org.sonar.batch.protocol.output.BatchReport;
import org.sonar.core.rule.RuleDto;
import org.sonar.server.computation.batch.BatchReportReaderRule;
import org.sonar.server.computation.component.ProjectSettingsRepository;
import org.sonar.server.user.index.UserDoc;
import org.sonar.server.user.index.UserIndex;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;

public class IssueComputationTest {

private static final RuleKey RULE_KEY = RuleKey.of("squid", "R1");
private static final String PROJECT_KEY = "PROJECT_KEY";

@Rule
public TemporaryFolder temp = new TemporaryFolder();
@Rule
public LogTester logTester = new LogTester();
@Rule
public BatchReportReaderRule reportReader = new BatchReportReaderRule();

IssueComputation sut;

// inputs
RuleCache ruleCache = mock(RuleCache.class);
SourceLinesCache lineCache = mock(SourceLinesCache.class);
ScmAccountCache scmAccountCache = mock(ScmAccountCache.class);
RuleDto rule = new RuleDto().setRepositoryKey(RULE_KEY.repository()).setRuleKey(RULE_KEY.rule());
BatchReport.Issue.Builder inputIssue = BatchReport.Issue.newBuilder()
.setUuid("ISSUE_A")
.setRuleRepository(RULE_KEY.repository())
.setRuleKey(RULE_KEY.rule())
.setStatus(Issue.STATUS_OPEN);
Settings projectSettings;
ProjectSettingsRepository projectSettingsRepository = mock(ProjectSettingsRepository.class);
UserIndex userIndex = mock(UserIndex.class);

// output
IssueCache outputIssues;

@Before
public void setUp() throws IOException {
when(ruleCache.get(RULE_KEY)).thenReturn(rule);
outputIssues = new IssueCache(temp.newFile(), System2.INSTANCE);
projectSettings = new Settings();
when(projectSettingsRepository.getProjectSettings(PROJECT_KEY)).thenReturn(projectSettings);
sut = new IssueComputation(ruleCache, lineCache, scmAccountCache, outputIssues, userIndex, projectSettingsRepository, reportReader);
}

@After
public void after() {
sut.afterReportProcessing();
}

@Test
public void store_issues_on_disk() {
process();

assertThat(Iterators.getOnlyElement(outputIssues.traverse()).key()).isEqualTo("ISSUE_A");
}

@Test
public void copy_rule_tags_on_new_issues() {
inputIssue.setIsNew(true);
rule.setTags(ImmutableSet.of("bug", "performance"));
rule.setSystemTags(ImmutableSet.of("blocker"));

process();

assertThat(Iterators.getOnlyElement(outputIssues.traverse()).tags()).containsOnly("blocker", "bug", "performance");
}

@Test
public void do_not_copy_rule_tags_on_existing_issues() {
inputIssue.setIsNew(false);
rule.setTags(ImmutableSet.of("bug", "performance"));
rule.setSystemTags(ImmutableSet.of("blocker"));

process();

assertThat(Iterators.getOnlyElement(outputIssues.traverse()).tags()).isEmpty();
}

@Test
public void guess_author_of_new_issues() {
inputIssue.setIsNew(true);
inputIssue.setLine(3);
when(lineCache.lineAuthor(3)).thenReturn("charlie");

process();

assertThat(Iterators.getOnlyElement(outputIssues.traverse()).authorLogin()).isEqualTo("charlie");
}

@Test
public void do_not_fail_if_missing_author_for_new_issues() {
inputIssue.setIsNew(true);
inputIssue.setLine(3);
when(lineCache.lineAuthor(3)).thenReturn(null);

process();

assertThat(Iterators.getOnlyElement(outputIssues.traverse()).authorLogin()).isNull();
}

@Test
public void do_not_guess_author_of_existing_issues() {
inputIssue.setIsNew(false);
inputIssue.setLine(3);
when(lineCache.lineAuthor(3)).thenReturn("charlie");

process();

assertThat(Iterators.getOnlyElement(outputIssues.traverse()).authorLogin()).isNull();
}

@Test
public void auto_assign_new_issues() {
inputIssue.setIsNew(true);
inputIssue.setAuthorLogin("charlie");
when(scmAccountCache.getNullable("charlie")).thenReturn("char.lie");

process();

assertThat(Iterators.getOnlyElement(outputIssues.traverse()).assignee()).isEqualTo("char.lie");
}

@Test
public void do_not_auto_assign_existing_issues() {
inputIssue.setIsNew(false);
inputIssue.setAuthorLogin("charlie");
when(scmAccountCache.getNullable("charlie")).thenReturn("char.lie");

process();

assertThat(Iterators.getOnlyElement(outputIssues.traverse()).assignee()).isNull();
}

@Test
public void do_not_override_author_and_assignee_set_by_old_batch_plugins() {
inputIssue.setIsNew(true);

// these fields were provided during project analysis, for instance
// by developer cockpit or issue-assign plugins
inputIssue.setAuthorLogin("charlie");
inputIssue.setAssignee("cabu");

process();

// keep the values, without trying to update them
DefaultIssue cachedIssue = Iterators.getOnlyElement(outputIssues.traverse());
assertThat(cachedIssue.assignee()).isEqualTo("cabu");
assertThat(cachedIssue.authorLogin()).isEqualTo("charlie");
verifyZeroInteractions(scmAccountCache);
}

@Test
public void assign_default_assignee_when_available() {
inputIssue.setIsNew(true);
String wolinski = "wolinski";
projectSettings.setProperty(CoreProperties.DEFAULT_ISSUE_ASSIGNEE, wolinski);
when(userIndex.getNullableByLogin(wolinski)).thenReturn(new UserDoc());

process();

assertThat(Iterators.getOnlyElement(outputIssues.traverse()).assignee()).isEqualTo(wolinski);
assertThat(logTester.logs()).doesNotContain(String.format("the %s property was set with an unknown login: %s", CoreProperties.DEFAULT_ISSUE_ASSIGNEE, wolinski));
}

@Test
public void do_not_assign_default_assignee_when_not_found_in_index() {
inputIssue.setIsNew(true);
String wolinski = "wolinski";
projectSettings.setProperty(CoreProperties.DEFAULT_ISSUE_ASSIGNEE, wolinski);
when(userIndex.getNullableByLogin(wolinski)).thenReturn(null);

process();

assertThat(Iterators.getOnlyElement(outputIssues.traverse()).assignee()).isNull();
assertThat(logTester.logs()).contains(String.format("the %s property was set with an unknown login: %s", CoreProperties.DEFAULT_ISSUE_ASSIGNEE, wolinski));
}

private void process() {
sut.processComponentIssues(Arrays.asList(inputIssue.build()), "FILE_A", 1, PROJECT_KEY, "PROJECT_UUID");
}
}
///*
// * 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 java.io.IOException;
//import java.util.Arrays;
//import org.junit.After;
//import org.junit.Before;
//import org.junit.Rule;
//import org.junit.Test;
//import org.junit.rules.TemporaryFolder;
//import org.sonar.api.CoreProperties;
//import org.sonar.api.config.Settings;
//import org.sonar.api.issue.Issue;
//import org.sonar.core.issue.DefaultIssue;
//import org.sonar.api.rule.RuleKey;
//import org.sonar.api.utils.System2;
//import org.sonar.api.utils.log.LogTester;
//import org.sonar.batch.protocol.output.BatchReport;
//import org.sonar.core.rule.RuleDto;
//import org.sonar.server.computation.batch.BatchReportReaderRule;
//import org.sonar.server.computation.component.ProjectSettingsRepository;
//import org.sonar.server.user.index.UserDoc;
//import org.sonar.server.user.index.UserIndex;
//
//import static org.assertj.core.api.Assertions.assertThat;
//import static org.mockito.Mockito.mock;
//import static org.mockito.Mockito.verifyZeroInteractions;
//import static org.mockito.Mockito.when;
//
//public class IssueComputationTest {
//
// private static final RuleKey RULE_KEY = RuleKey.of("squid", "R1");
// private static final String PROJECT_KEY = "PROJECT_KEY";
//
// @Rule
// public TemporaryFolder temp = new TemporaryFolder();
// @Rule
// public LogTester logTester = new LogTester();
// @Rule
// public BatchReportReaderRule reportReader = new BatchReportReaderRule();
//
// IssueComputation sut;
//
// // inputs
// RuleCache ruleCache = mock(RuleCache.class);
// SourceAuthorsHolder lineCache = mock(SourceAuthorsHolder.class);
// ScmAccountToUser scmAccountToUser = mock(ScmAccountToUser.class);
// RuleDto rule = new RuleDto().setRepositoryKey(RULE_KEY.repository()).setRuleKey(RULE_KEY.rule());
// BatchReport.Issue.Builder inputIssue = BatchReport.Issue.newBuilder()
// .setUuid("ISSUE_A")
// .setRuleRepository(RULE_KEY.repository())
// .setRuleKey(RULE_KEY.rule())
// .setStatus(Issue.STATUS_OPEN);
// Settings projectSettings;
// ProjectSettingsRepository projectSettingsRepository = mock(ProjectSettingsRepository.class);
// UserIndex userIndex = mock(UserIndex.class);
//
// // output
// DeprecatedIssueCache outputIssues;
//
// @Before
// public void setUp() throws IOException {
// when(ruleCache.get(RULE_KEY)).thenReturn(rule);
// outputIssues = new DeprecatedIssueCache(temp.newFile(), System2.INSTANCE);
// projectSettings = new Settings();
// when(projectSettingsRepository.getProjectSettings(PROJECT_KEY)).thenReturn(projectSettings);
// sut = new IssueComputation(ruleCache, lineCache, scmAccountToUser, outputIssues, userIndex, projectSettingsRepository, reportReader);
// }
//
// @After
// public void after() {
// sut.afterReportProcessing();
// }
//
// @Test
// public void store_issues_on_disk() {
// process();
//
// assertThat(Iterators.getOnlyElement(outputIssues.traverse()).key()).isEqualTo("ISSUE_A");
// }
//
// @Test
// public void copy_rule_tags_on_new_issues() {
// inputIssue.setIsNew(true);
// rule.setTags(ImmutableSet.of("bug", "performance"));
// rule.setSystemTags(ImmutableSet.of("blocker"));
//
// process();
//
// assertThat(Iterators.getOnlyElement(outputIssues.traverse()).tags()).containsOnly("blocker", "bug", "performance");
// }
//
// @Test
// public void do_not_copy_rule_tags_on_existing_issues() {
// inputIssue.setIsNew(false);
// rule.setTags(ImmutableSet.of("bug", "performance"));
// rule.setSystemTags(ImmutableSet.of("blocker"));
//
// process();
//
// assertThat(Iterators.getOnlyElement(outputIssues.traverse()).tags()).isEmpty();
// }
//
// @Test
// public void guess_author_of_new_issues() {
// inputIssue.setIsNew(true);
// inputIssue.setLine(3);
// when(lineCache.lineAuthor(3)).thenReturn("charlie");
//
// process();
//
// assertThat(Iterators.getOnlyElement(outputIssues.traverse()).authorLogin()).isEqualTo("charlie");
// }
//
// @Test
// public void do_not_fail_if_missing_author_for_new_issues() {
// inputIssue.setIsNew(true);
// inputIssue.setLine(3);
// when(lineCache.lineAuthor(3)).thenReturn(null);
//
// process();
//
// assertThat(Iterators.getOnlyElement(outputIssues.traverse()).authorLogin()).isNull();
// }
//
// @Test
// public void do_not_guess_author_of_existing_issues() {
// inputIssue.setIsNew(false);
// inputIssue.setLine(3);
// when(lineCache.lineAuthor(3)).thenReturn("charlie");
//
// process();
//
// assertThat(Iterators.getOnlyElement(outputIssues.traverse()).authorLogin()).isNull();
// }
//
// @Test
// public void auto_assign_new_issues() {
// inputIssue.setIsNew(true);
// inputIssue.setAuthorLogin("charlie");
// when(scmAccountToUser.getNullable("charlie")).thenReturn("char.lie");
//
// process();
//
// assertThat(Iterators.getOnlyElement(outputIssues.traverse()).assignee()).isEqualTo("char.lie");
// }
//
// @Test
// public void do_not_auto_assign_existing_issues() {
// inputIssue.setIsNew(false);
// inputIssue.setAuthorLogin("charlie");
// when(scmAccountToUser.getNullable("charlie")).thenReturn("char.lie");
//
// process();
//
// assertThat(Iterators.getOnlyElement(outputIssues.traverse()).assignee()).isNull();
// }
//
// @Test
// public void do_not_override_author_and_assignee_set_by_old_batch_plugins() {
// inputIssue.setIsNew(true);
//
// // these fields were provided during project analysis, for instance
// // by developer cockpit or issue-assign plugins
// inputIssue.setAuthorLogin("charlie");
// inputIssue.setAssignee("cabu");
//
// process();
//
// // keep the values, without trying to update them
// DefaultIssue cachedIssue = Iterators.getOnlyElement(outputIssues.traverse());
// assertThat(cachedIssue.assignee()).isEqualTo("cabu");
// assertThat(cachedIssue.authorLogin()).isEqualTo("charlie");
// verifyZeroInteractions(scmAccountToUser);
// }
//
// @Test
// public void assign_default_assignee_when_available() {
// inputIssue.setIsNew(true);
// String wolinski = "wolinski";
// projectSettings.setProperty(CoreProperties.DEFAULT_ISSUE_ASSIGNEE, wolinski);
// when(userIndex.getNullableByLogin(wolinski)).thenReturn(new UserDoc());
//
// process();
//
// assertThat(Iterators.getOnlyElement(outputIssues.traverse()).assignee()).isEqualTo(wolinski);
// assertThat(logTester.logs()).doesNotContain(String.format("the %s property was set with an unknown login: %s", CoreProperties.DEFAULT_ISSUE_ASSIGNEE, wolinski));
// }
//
// @Test
// public void do_not_assign_default_assignee_when_not_found_in_index() {
// inputIssue.setIsNew(true);
// String wolinski = "wolinski";
// projectSettings.setProperty(CoreProperties.DEFAULT_ISSUE_ASSIGNEE, wolinski);
// when(userIndex.getNullableByLogin(wolinski)).thenReturn(null);
//
// process();
//
// assertThat(Iterators.getOnlyElement(outputIssues.traverse()).assignee()).isNull();
// assertThat(logTester.logs()).contains(String.format("the %s property was set with an unknown login: %s", CoreProperties.DEFAULT_ISSUE_ASSIGNEE, wolinski));
// }
//
// private void process() {
// sut.processComponentIssues(Arrays.asList(inputIssue.build()), "FILE_A", 1, PROJECT_KEY, "PROJECT_UUID");
// }
//}

server/sonar-server/src/test/java/org/sonar/server/computation/issue/ScmAccountCacheLoaderTest.java → server/sonar-server/src/test/java/org/sonar/server/computation/issue/ScmAccountToUserLoaderTest.java View File

@@ -19,6 +19,7 @@
*/
package org.sonar.server.computation.issue;

import java.util.Collections;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
@@ -28,14 +29,12 @@ 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;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

public class ScmAccountCacheLoaderTest {
public class ScmAccountToUserLoaderTest {

@ClassRule
public static EsTester esTester = new EsTester().addDefinitions(new UserIndexDefinition(new Settings()));
@@ -49,7 +48,7 @@ public class ScmAccountCacheLoaderTest {
public void load_login_for_scm_account() throws Exception {
esTester.putDocuments("users", "user", getClass(), "charlie.json");
UserIndex index = new UserIndex(esTester.client());
ScmAccountCacheLoader loader = new ScmAccountCacheLoader(index);
ScmAccountToUserLoader loader = new ScmAccountToUserLoader(index);

assertThat(loader.load("missing")).isNull();
assertThat(loader.load("jesuis@charlie.com")).isEqualTo("charlie");
@@ -60,7 +59,7 @@ public class ScmAccountCacheLoaderTest {
esTester.putDocuments("users", "user", getClass(), "charlie.json", "charlie_conflict.json");
UserIndex index = new UserIndex(esTester.client());
Logger log = mock(Logger.class);
ScmAccountCacheLoader loader = new ScmAccountCacheLoader(index, log);
ScmAccountToUserLoader loader = new ScmAccountToUserLoader(index, log);

assertThat(loader.load("charlie")).isNull();
verify(log).warn("Multiple users share the SCM account 'charlie': charlie, another.charlie");
@@ -69,7 +68,7 @@ public class ScmAccountCacheLoaderTest {
@Test
public void load_by_multiple_scm_accounts_is_not_supported_yet() {
UserIndex index = new UserIndex(esTester.client());
ScmAccountCacheLoader loader = new ScmAccountCacheLoader(index);
ScmAccountToUserLoader loader = new ScmAccountToUserLoader(index);
try {
loader.loadAll(Collections.<String>emptyList());
fail();

+ 137
- 0
server/sonar-server/src/test/java/org/sonar/server/computation/issue/SourceAuthorsHolderTest.java View File

@@ -0,0 +1,137 @@
/*
* 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.
*/
///*
// * 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 java.util.Date;
//import org.junit.Before;
//import org.junit.ClassRule;
//import org.junit.Rule;
//import org.junit.Test;
//import org.junit.experimental.categories.Category;
//import org.sonar.api.config.Settings;
//import org.sonar.batch.protocol.output.BatchReport;
//import org.sonar.server.computation.batch.BatchReportReaderRule;
//import org.sonar.server.es.EsTester;
//import org.sonar.server.source.index.SourceLineDoc;
//import org.sonar.server.source.index.SourceLineIndex;
//import org.sonar.server.source.index.SourceLineIndexDefinition;
//import org.sonar.test.DbTests;
//
//import static org.assertj.core.api.Assertions.assertThat;
//
//@Category(DbTests.class)
//public class SourceAuthorsHolderTest {
//
// @ClassRule
// public static EsTester esTester = new EsTester().addDefinitions(new SourceLineIndexDefinition(new Settings()));
// @Rule
// public BatchReportReaderRule reportReader = new BatchReportReaderRule();
//
// SourceAuthorsHolder sut;
//
// @Before
// public void setUp() throws Exception {
// esTester.truncateIndices();
// sut = new SourceAuthorsHolder(new SourceLineIndex(esTester.client()), reportReader);
// }
//
// @Test
// public void line_author_from_report() {
// reportReader.putChangesets(BatchReport.Changesets.newBuilder()
// .setComponentRef(123_456_789)
// .addChangeset(newChangeset("charb", "123-456-789", 123_456_789L))
// .addChangeset(newChangeset("wolinski", "987-654-321", 987_654_321L))
// .addChangesetIndexByLine(0)
// .addChangesetIndexByLine(0)
// .addChangesetIndexByLine(1)
// .build());
//
// sut.init("ANY_UUID", 123_456_789, reportReader);
//
// assertThat(sut.lineAuthor(1)).isEqualTo("charb");
// assertThat(sut.lineAuthor(2)).isEqualTo("charb");
// assertThat(sut.lineAuthor(3)).isEqualTo("wolinski");
// // compute last author
// assertThat(sut.lineAuthor(4)).isEqualTo("wolinski");
// assertThat(sut.lineAuthor(null)).isEqualTo("wolinski");
// }
//
// @Test
// public void line_author_from_index() throws Exception {
// esTester.putDocuments(SourceLineIndexDefinition.INDEX, SourceLineIndexDefinition.TYPE,
// newSourceLine("cabu", "123-456-789", 123_456_789, 1),
// newSourceLine("cabu", "123-456-789", 123_456_789, 2),
// newSourceLine("cabu", "123-123-789", 123_456_789, 3),
// newSourceLine("wolinski", "987-654-321", 987_654_321, 4),
// newSourceLine("cabu", "123-456-789", 123_456_789, 5)
// );
//
// sut.init("DEFAULT_UUID", 123, reportReader);
//
// assertThat(sut.lineAuthor(1)).isEqualTo("cabu");
// assertThat(sut.lineAuthor(2)).isEqualTo("cabu");
// assertThat(sut.lineAuthor(3)).isEqualTo("cabu");
// assertThat(sut.lineAuthor(4)).isEqualTo("wolinski");
// assertThat(sut.lineAuthor(5)).isEqualTo("cabu");
// assertThat(sut.lineAuthor(6)).isEqualTo("wolinski");
// }
//
// @Test(expected = IllegalStateException.class)
// public void fail_when_component_ref_is_not_filled() {
// sut.init("ANY_UUID", null, reportReader);
// sut.lineAuthor(0);
// }
//
// private BatchReport.Changesets.Changeset.Builder newChangeset(String author, String revision, long date) {
// return BatchReport.Changesets.Changeset.newBuilder()
// .setAuthor(author)
// .setRevision(revision)
// .setDate(date);
// }
//
// private SourceLineDoc newSourceLine(String author, String revision, long date, int lineNumber) {
// return new SourceLineDoc()
// .setScmAuthor(author)
// .setScmRevision(revision)
// .setScmDate(new Date(date))
// .setLine(lineNumber)
// .setProjectUuid("PROJECT_UUID")
// .setFileUuid("DEFAULT_UUID");
// }
//}

+ 0
- 118
server/sonar-server/src/test/java/org/sonar/server/computation/issue/SourceLinesCacheTest.java View File

@@ -1,118 +0,0 @@
/*
* 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 java.util.Date;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.sonar.api.config.Settings;
import org.sonar.batch.protocol.output.BatchReport;
import org.sonar.server.computation.batch.BatchReportReaderRule;
import org.sonar.server.es.EsTester;
import org.sonar.server.source.index.SourceLineDoc;
import org.sonar.server.source.index.SourceLineIndex;
import org.sonar.server.source.index.SourceLineIndexDefinition;
import org.sonar.test.DbTests;

import static org.assertj.core.api.Assertions.assertThat;

@Category(DbTests.class)
public class SourceLinesCacheTest {

@ClassRule
public static EsTester esTester = new EsTester().addDefinitions(new SourceLineIndexDefinition(new Settings()));
@Rule
public BatchReportReaderRule reportReader = new BatchReportReaderRule();

SourceLinesCache sut;

@Before
public void setUp() throws Exception {
esTester.truncateIndices();
sut = new SourceLinesCache(new SourceLineIndex(esTester.client()));
}

@Test
public void line_author_from_report() {
reportReader.putChangesets(BatchReport.Changesets.newBuilder()
.setComponentRef(123_456_789)
.addChangeset(newChangeset("charb", "123-456-789", 123_456_789L))
.addChangeset(newChangeset("wolinski", "987-654-321", 987_654_321L))
.addChangesetIndexByLine(0)
.addChangesetIndexByLine(0)
.addChangesetIndexByLine(1)
.build());

sut.init("ANY_UUID", 123_456_789, reportReader);

assertThat(sut.lineAuthor(1)).isEqualTo("charb");
assertThat(sut.lineAuthor(2)).isEqualTo("charb");
assertThat(sut.lineAuthor(3)).isEqualTo("wolinski");
// compute last author
assertThat(sut.lineAuthor(4)).isEqualTo("wolinski");
assertThat(sut.lineAuthor(null)).isEqualTo("wolinski");
}

@Test
public void line_author_from_index() throws Exception {
esTester.putDocuments(SourceLineIndexDefinition.INDEX, SourceLineIndexDefinition.TYPE,
newSourceLine("cabu", "123-456-789", 123_456_789, 1),
newSourceLine("cabu", "123-456-789", 123_456_789, 2),
newSourceLine("cabu", "123-123-789", 123_456_789, 3),
newSourceLine("wolinski", "987-654-321", 987_654_321, 4),
newSourceLine("cabu", "123-456-789", 123_456_789, 5)
);

sut.init("DEFAULT_UUID", 123, reportReader);

assertThat(sut.lineAuthor(1)).isEqualTo("cabu");
assertThat(sut.lineAuthor(2)).isEqualTo("cabu");
assertThat(sut.lineAuthor(3)).isEqualTo("cabu");
assertThat(sut.lineAuthor(4)).isEqualTo("wolinski");
assertThat(sut.lineAuthor(5)).isEqualTo("cabu");
assertThat(sut.lineAuthor(6)).isEqualTo("wolinski");
}

@Test(expected = IllegalStateException.class)
public void fail_when_component_ref_is_not_filled() {
sut.init("ANY_UUID", null, reportReader);
sut.lineAuthor(0);
}

private BatchReport.Changesets.Changeset.Builder newChangeset(String author, String revision, long date) {
return BatchReport.Changesets.Changeset.newBuilder()
.setAuthor(author)
.setRevision(revision)
.setDate(date);
}

private SourceLineDoc newSourceLine(String author, String revision, long date, int lineNumber) {
return new SourceLineDoc()
.setScmAuthor(author)
.setScmRevision(revision)
.setScmDate(new Date(date))
.setLine(lineNumber)
.setProjectUuid("PROJECT_UUID")
.setFileUuid("DEFAULT_UUID");
}
}

+ 0
- 122
server/sonar-server/src/test/java/org/sonar/server/computation/step/ParseReportStepTest.java View File

@@ -1,122 +0,0 @@
/*
* 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.step;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.sonar.batch.protocol.Constants;
import org.sonar.batch.protocol.output.BatchReport;
import org.sonar.core.persistence.DbTester;
import org.sonar.server.computation.batch.BatchReportReaderRule;
import org.sonar.server.computation.batch.TreeRootHolderRule;
import org.sonar.server.computation.component.Component;
import org.sonar.server.computation.component.DumbComponent;
import org.sonar.server.computation.issue.IssueComputation;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

public class ParseReportStepTest extends BaseStepTest {

private static final String PROJECT_KEY = "PROJECT_KEY";

private static final List<BatchReport.Issue> ISSUES_ON_DELETED_COMPONENT = Arrays.asList(BatchReport.Issue.newBuilder()
.setUuid("DELETED_ISSUE_UUID")
.build());

@Rule
public TemporaryFolder temp = new TemporaryFolder();
@Rule
public BatchReportReaderRule reportReader = new BatchReportReaderRule();
@Rule
public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();

@ClassRule
public static DbTester dbTester = new DbTester();

IssueComputation issueComputation = mock(IssueComputation.class);
ParseReportStep sut = new ParseReportStep(issueComputation, reportReader, treeRootHolder);

@Test
public void extract_report_from_db_and_browse_components() throws Exception {
DumbComponent root = DumbComponent.builder(Component.Type.PROJECT, 1).setUuid("PROJECT_UUID").setKey(PROJECT_KEY).addChildren(
DumbComponent.builder(Component.Type.FILE, 2).setUuid("FILE1_UUID").setKey("PROJECT_KEY:file1").build(),
DumbComponent.builder(Component.Type.FILE, 3).setUuid("FILE2_UUID").setKey("PROJECT_KEY:file2").build())
.build();

generateReport();

treeRootHolder.setRoot(root);

sut.execute();

assertThat(reportReader.readMetadata().getRootComponentRef()).isEqualTo(1);
assertThat(reportReader.readMetadata().getDeletedComponentsCount()).isEqualTo(1);

// verify that all components are processed (currently only for issues)
verify(issueComputation).processComponentIssues(Collections.<BatchReport.Issue>emptyList(), "PROJECT_UUID", 1, PROJECT_KEY, "PROJECT_UUID");
verify(issueComputation).processComponentIssues(Collections.<BatchReport.Issue>emptyList(), "FILE1_UUID", 2, PROJECT_KEY, "PROJECT_UUID");
verify(issueComputation).processComponentIssues(Collections.<BatchReport.Issue>emptyList(), "FILE2_UUID", 3, PROJECT_KEY, "PROJECT_UUID");
verify(issueComputation).processComponentIssues(ISSUES_ON_DELETED_COMPONENT, "DELETED_UUID", null, PROJECT_KEY, "PROJECT_UUID");
verify(issueComputation).afterReportProcessing();
}

private void generateReport() throws IOException {
// project and 2 files
reportReader.setMetadata(BatchReport.Metadata.newBuilder()
.setRootComponentRef(1)
.setDeletedComponentsCount(1)
.build());

reportReader.putComponent(BatchReport.Component.newBuilder()
.setRef(1)
.setType(Constants.ComponentType.PROJECT)
.addChildRef(2)
.addChildRef(3)
.build());
reportReader.putComponent(BatchReport.Component.newBuilder()
.setRef(2)
.setType(Constants.ComponentType.FILE)
.build());
reportReader.putComponent(BatchReport.Component.newBuilder()
.setRef(3)
.setType(Constants.ComponentType.FILE)
.build());

// deleted components
BatchReport.Issues.Builder issuesBuilder = BatchReport.Issues.newBuilder();
issuesBuilder.setComponentRef(1);
issuesBuilder.setComponentUuid("DELETED_UUID");
issuesBuilder.addAllIssue(ISSUES_ON_DELETED_COMPONENT);
reportReader.putDeletedIssues(1, issuesBuilder.build());
}

@Override
protected ComputationStep step() {
return sut;
}
}

+ 3
- 3
server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistIssuesStepTest.java View File

@@ -27,12 +27,12 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.DefaultIssueComment;
import org.sonar.api.issue.internal.FieldDiffs;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.Severity;
import org.sonar.api.utils.System2;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.DefaultIssueComment;
import org.sonar.core.issue.FieldDiffs;
import org.sonar.core.issue.db.UpdateConflictResolver;
import org.sonar.core.persistence.DbSession;
import org.sonar.core.persistence.DbTester;

+ 1
- 1
server/sonar-server/src/test/java/org/sonar/server/computation/step/SendIssueNotificationsStepTest.java View File

@@ -25,7 +25,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.mockito.Mockito;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.api.notifications.Notification;
import org.sonar.api.rule.Severity;
import org.sonar.api.utils.System2;

+ 2
- 2
server/sonar-server/src/test/java/org/sonar/server/issue/ActionServiceTest.java View File

@@ -28,8 +28,8 @@ import org.sonar.api.issue.Issue;
import org.sonar.api.issue.action.Actions;
import org.sonar.api.issue.action.Function;
import org.sonar.api.issue.condition.Condition;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.IssueChangeContext;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.IssueChangeContext;
import org.sonar.core.component.ComponentDto;
import org.sonar.core.issue.IssueUpdater;
import org.sonar.core.issue.db.IssueDto;

+ 2
- 2
server/sonar-server/src/test/java/org/sonar/server/issue/AddTagsActionTest.java View File

@@ -26,8 +26,8 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.Matchers;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.IssueChangeContext;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.IssueChangeContext;
import org.sonar.core.issue.IssueUpdater;

import java.util.Collection;

+ 2
- 2
server/sonar-server/src/test/java/org/sonar/server/issue/AssignActionTest.java View File

@@ -25,8 +25,8 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.IssueChangeContext;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.IssueChangeContext;
import org.sonar.api.user.User;
import org.sonar.api.user.UserFinder;
import org.sonar.core.issue.IssueUpdater;

+ 2
- 2
server/sonar-server/src/test/java/org/sonar/server/issue/CommentActionTest.java View File

@@ -25,8 +25,8 @@ import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.IssueChangeContext;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.IssueChangeContext;
import org.sonar.core.issue.IssueUpdater;
import org.sonar.server.tester.AnonymousMockUserSession;


+ 2
- 2
server/sonar-server/src/test/java/org/sonar/server/issue/InternalRubyIssueServiceTest.java View File

@@ -33,8 +33,8 @@ import org.mockito.ArgumentCaptor;
import org.sonar.api.issue.ActionPlan;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.action.Action;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.FieldDiffs;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.FieldDiffs;
import org.sonar.api.user.User;
import org.sonar.api.web.UserRole;
import org.sonar.core.issue.DefaultActionPlan;

+ 1
- 1
server/sonar-server/src/test/java/org/sonar/server/issue/IssueChangelogFormatterTest.java View File

@@ -28,7 +28,7 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.sonar.api.i18n.I18n;
import org.sonar.api.issue.internal.FieldDiffs;
import org.sonar.core.issue.FieldDiffs;
import org.sonar.api.utils.Duration;
import org.sonar.api.utils.Durations;
import org.sonar.server.tester.UserSessionRule;

+ 2
- 2
server/sonar-server/src/test/java/org/sonar/server/issue/IssueChangelogServiceTest.java View File

@@ -27,8 +27,8 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.FieldDiffs;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.FieldDiffs;
import org.sonar.api.user.User;
import org.sonar.api.user.UserFinder;
import org.sonar.core.issue.db.IssueChangeDao;

+ 1
- 1
server/sonar-server/src/test/java/org/sonar/server/issue/IssueCommentServiceMediumTest.java View File

@@ -27,7 +27,7 @@ import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.internal.DefaultIssueComment;
import org.sonar.core.issue.DefaultIssueComment;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.security.DefaultGroups;

+ 3
- 3
server/sonar-server/src/test/java/org/sonar/server/issue/IssueCommentServiceTest.java View File

@@ -28,9 +28,9 @@ import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.DefaultIssueComment;
import org.sonar.api.issue.internal.IssueChangeContext;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.DefaultIssueComment;
import org.sonar.core.issue.IssueChangeContext;
import org.sonar.core.issue.IssueUpdater;
import org.sonar.core.issue.db.IssueChangeDao;
import org.sonar.core.issue.db.IssueChangeDto;

+ 2
- 2
server/sonar-server/src/test/java/org/sonar/server/issue/PlanActionTest.java View File

@@ -26,8 +26,8 @@ import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.issue.ActionPlan;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.IssueChangeContext;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.IssueChangeContext;
import org.sonar.core.issue.DefaultActionPlan;
import org.sonar.core.issue.IssueUpdater;
import org.sonar.server.issue.actionplan.ActionPlanService;

+ 2
- 2
server/sonar-server/src/test/java/org/sonar/server/issue/RemoveTagsActionTest.java View File

@@ -26,8 +26,8 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.Matchers;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.IssueChangeContext;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.IssueChangeContext;
import org.sonar.core.issue.IssueUpdater;

import java.util.Collection;

+ 3
- 3
server/sonar-server/src/test/java/org/sonar/server/issue/ServerIssueStorageTest.java View File

@@ -23,9 +23,9 @@ package org.sonar.server.issue;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.DefaultIssueComment;
import org.sonar.api.issue.internal.IssueChangeContext;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.DefaultIssueComment;
import org.sonar.core.issue.IssueChangeContext;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.Rule;
import org.sonar.api.rules.RuleFinder;

+ 2
- 2
server/sonar-server/src/test/java/org/sonar/server/issue/SetSeverityActionTest.java View File

@@ -26,8 +26,8 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.IssueChangeContext;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.IssueChangeContext;
import org.sonar.api.web.UserRole;
import org.sonar.core.issue.IssueUpdater;
import org.sonar.server.tester.UserSessionRule;

+ 2
- 2
server/sonar-server/src/test/java/org/sonar/server/issue/TransitionActionTest.java View File

@@ -26,8 +26,8 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.IssueChangeContext;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.IssueChangeContext;
import org.sonar.core.issue.workflow.IssueWorkflow;
import org.sonar.core.issue.workflow.Transition;
import org.sonar.server.tester.UserSessionRule;

+ 2
- 2
server/sonar-server/src/test/java/org/sonar/server/issue/actionplan/ActionPlanServiceTest.java View File

@@ -28,8 +28,8 @@ import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.sonar.api.issue.ActionPlan;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.IssueChangeContext;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.IssueChangeContext;
import org.sonar.api.web.UserRole;
import org.sonar.core.issue.ActionPlanStats;
import org.sonar.core.issue.DefaultActionPlan;

+ 2
- 2
server/sonar-server/src/test/java/org/sonar/server/issue/notification/IssueChangeNotificationTest.java View File

@@ -20,8 +20,8 @@
package org.sonar.server.issue.notification;

import org.junit.Test;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.FieldDiffs;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.FieldDiffs;
import org.sonar.core.component.ComponentDto;

import static org.assertj.core.api.Assertions.assertThat;

+ 1
- 1
server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationTest.java View File

@@ -24,7 +24,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import org.junit.Test;
import org.mockito.Mockito;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.Severity;
import org.sonar.api.utils.DateUtils;

+ 1
- 1
server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesStatisticsTest.java View File

@@ -22,7 +22,7 @@ package org.sonar.server.issue.notification;

import com.google.common.collect.Lists;
import org.junit.Test;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.Severity;
import org.sonar.api.utils.Duration;

+ 1
- 1
server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueActionsWriterTest.java View File

@@ -29,7 +29,7 @@ import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.action.Action;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.utils.text.JsonWriter;
import org.sonar.api.web.UserRole;

+ 3
- 3
server/sonar-server/src/test/java/org/sonar/server/issue/ws/ShowActionTest.java View File

@@ -31,9 +31,9 @@ import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.sonar.api.i18n.I18n;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.DefaultIssueComment;
import org.sonar.api.issue.internal.FieldDiffs;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.DefaultIssueComment;
import org.sonar.core.issue.FieldDiffs;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.server.debt.internal.DefaultDebtCharacteristic;
import org.sonar.api.user.User;

+ 663
- 3025
sonar-batch-protocol/src/main/gen-java/org/sonar/batch/protocol/output/BatchReport.java
File diff suppressed because it is too large
View File


+ 0
- 9
sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/output/BatchReportReader.java View File

@@ -79,15 +79,6 @@ public class BatchReportReader {
return Collections.emptyList();
}

public Issues readDeletedComponentIssues(int deletedComponentRef) {
File file = fileStructure.fileFor(FileStructure.Domain.ISSUES_ON_DELETED, deletedComponentRef);
if (!doesFileExists(file)) {
throw new IllegalStateException("Unable to find report for deleted component #" + deletedComponentRef);
}
// all the issues are loaded in memory
return ProtobufUtil.readFile(file, Issues.PARSER);
}

public List<BatchReport.Duplication> readComponentDuplications(int componentRef) {
File file = fileStructure.fileFor(FileStructure.Domain.DUPLICATIONS, componentRef);
if (doesFileExists(file)) {

+ 0
- 14
sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/output/BatchReportWriter.java View File

@@ -80,20 +80,6 @@ public class BatchReportWriter {
return file;
}

/**
* Issues on components which have been deleted are stored in another location.
* Temporary hack, waiting for computation stack
*/
public File writeDeletedComponentIssues(int componentRef, String componentUuid, Iterable<BatchReport.Issue> issues) {
BatchReport.Issues.Builder issuesBuilder = BatchReport.Issues.newBuilder();
issuesBuilder.setComponentRef(componentRef);
issuesBuilder.setComponentUuid(componentUuid);
issuesBuilder.addAllIssue(issues);
File file = fileStructure.fileFor(FileStructure.Domain.ISSUES_ON_DELETED, componentRef);
ProtobufUtil.writeToFile(issuesBuilder.build(), file);
return file;
}

public File writeComponentDuplications(int componentRef, Iterable<BatchReport.Duplication> duplications) {
BatchReport.Duplications.Builder builder = BatchReport.Duplications.newBuilder();
builder.setComponentRef(componentRef);

+ 4
- 27
sonar-batch-protocol/src/main/protobuf/batch_report.proto View File

@@ -45,9 +45,6 @@ message Metadata {
optional string project_key = 2;
optional string branch = 3;
optional int32 root_component_ref = 4;

// temporary fields used during development of computation stack
optional int32 deleted_components_count = 5;
}

message ComponentLink {
@@ -111,36 +108,16 @@ message Issue {
optional string msg = 4;
optional Severity severity = 5;
repeated string tag = 6;

// temporary fields during development of computation stack
optional double effort_to_fix = 7;
optional bool is_new = 8;
optional string uuid = 9;
optional int64 debt_in_minutes = 10;
optional string resolution = 11;
optional string status = 12;
optional string checksum = 13;
optional bool manual_severity = 14;
optional string reporter = 15;
optional string assignee = 16;
optional string action_plan_key = 17;
optional string attributes = 18;
optional string author_login = 19;
optional int64 creation_date = 20;
optional int64 close_date = 21;
optional int64 update_date = 22;
optional int64 selected_at = 23;
optional string diff_fields = 24;
optional bool is_changed = 25;
optional bool must_send_notification = 26;
optional string attributes = 8;

// TODO should it be moved to compute engine?
optional int64 debt_in_minutes = 9;
}

message Issues {
optional int32 component_ref = 1;
repeated Issue issue = 2;

// Temporary field for issues on deleted components
optional string component_uuid = 3;
}

message Changesets {

+ 1
- 14
sonar-batch-protocol/src/test/java/org/sonar/batch/protocol/output/BatchReportReaderTest.java View File

@@ -56,14 +56,12 @@ public class BatchReportReaderTest {
BatchReport.Metadata.Builder metadata = BatchReport.Metadata.newBuilder()
.setAnalysisDate(15000000L)
.setProjectKey("PROJECT_A")
.setRootComponentRef(1)
.setDeletedComponentsCount(10);
.setRootComponentRef(1);
writer.writeMetadata(metadata.build());

BatchReport.Metadata readMetadata = sut.readMetadata();
assertThat(readMetadata.getAnalysisDate()).isEqualTo(15000000L);
assertThat(readMetadata.getProjectKey()).isEqualTo("PROJECT_A");
assertThat(readMetadata.getDeletedComponentsCount()).isEqualTo(10);
assertThat(readMetadata.getRootComponentRef()).isEqualTo(1);
}

@@ -92,23 +90,12 @@ public class BatchReportReaderTest {
public void read_issues() {
BatchReportWriter writer = new BatchReportWriter(dir);
BatchReport.Issue issue = BatchReport.Issue.newBuilder()
.setUuid("ISSUE_A")
.setLine(50)
.build();
writer.writeComponentIssues(1, Arrays.asList(issue));
writer.writeDeletedComponentIssues(1, "compUuid", Arrays.asList(issue));

assertThat(sut.readComponentIssues(1)).hasSize(1);
assertThat(sut.readComponentIssues(200)).isEmpty();

BatchReport.Issues deletedComponentIssues = sut.readDeletedComponentIssues(1);
assertThat(deletedComponentIssues.getComponentUuid()).isEqualTo("compUuid");
assertThat(deletedComponentIssues.getIssueList()).hasSize(1);
}

@Test(expected = IllegalStateException.class)
public void fail_if_missing_file_on_deleted_component() {
sut.readDeletedComponentIssues(UNKNOWN_COMPONENT_REF);
}

@Test

+ 0
- 25
sonar-batch-protocol/src/test/java/org/sonar/batch/protocol/output/BatchReportWriterTest.java View File

@@ -101,7 +101,6 @@ public class BatchReportWriterTest {

// write data
BatchReport.Issue issue = BatchReport.Issue.newBuilder()
.setUuid("ISSUE_A")
.setLine(50)
.setMsg("the message")
.build();
@@ -113,30 +112,6 @@ public class BatchReportWriterTest {
assertThat(file).exists().isFile();
BatchReport.Issues read = ProtobufUtil.readFile(file, BatchReport.Issues.PARSER);
assertThat(read.getComponentRef()).isEqualTo(1);
assertThat(read.hasComponentUuid()).isFalse();
assertThat(read.getIssueCount()).isEqualTo(1);
}

@Test
public void write_issues_of_deleted_component() {
// no data yet
assertThat(sut.hasComponentData(FileStructure.Domain.ISSUES_ON_DELETED, 1)).isFalse();

// write data
BatchReport.Issue issue = BatchReport.Issue.newBuilder()
.setUuid("ISSUE_A")
.setLine(50)
.setMsg("the message")
.build();

sut.writeDeletedComponentIssues(1, "componentUuid", Arrays.asList(issue));

assertThat(sut.hasComponentData(FileStructure.Domain.ISSUES_ON_DELETED, 1)).isTrue();
File file = sut.getFileStructure().fileFor(FileStructure.Domain.ISSUES_ON_DELETED, 1);
assertThat(file).exists().isFile();
BatchReport.Issues read = ProtobufUtil.readFile(file, BatchReport.Issues.PARSER);
assertThat(read.getComponentRef()).isEqualTo(1);
assertThat(read.getComponentUuid()).isEqualTo("componentUuid");
assertThat(read.getIssueCount()).isEqualTo(1);
}


+ 0
- 8
sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java View File

@@ -40,10 +40,7 @@ import org.sonar.batch.cpd.CpdComponents;
import org.sonar.batch.debt.DebtDecorator;
import org.sonar.batch.debt.IssueChangelogDebtCalculator;
import org.sonar.batch.debt.NewDebtDecorator;
import org.sonar.batch.issue.tracking.InitialOpenIssuesSensor;
import org.sonar.batch.issue.tracking.IssueHandlers;
import org.sonar.batch.issue.tracking.IssueTracking;
import org.sonar.batch.issue.tracking.IssueTrackingDecorator;
import org.sonar.batch.language.LanguageDistributionDecorator;
import org.sonar.batch.scan.report.ConsoleReport;
import org.sonar.batch.scan.report.HtmlReport;
@@ -92,11 +89,6 @@ public class BatchComponents {
DebtDecorator.class,
NewDebtDecorator.class,

// Issue tracking
IssueTrackingDecorator.class,
IssueHandlers.class,
InitialOpenIssuesSensor.class,

// to be moved to compute engine
UnitTestDecorator.class,
LineCoverageDecorator.class,

+ 1
- 1
sonar-batch/src/main/java/org/sonar/batch/debt/DebtDecorator.java View File

@@ -38,7 +38,7 @@ import org.sonar.api.batch.rule.Rules;
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.core.issue.DefaultIssue;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.measures.Measure;
import org.sonar.api.measures.MeasuresFilters;

+ 2
- 2
sonar-batch/src/main/java/org/sonar/batch/debt/IssueChangelogDebtCalculator.java View File

@@ -34,8 +34,8 @@ import javax.annotation.Nullable;
import org.apache.commons.lang.time.DateUtils;
import org.sonar.api.batch.BatchSide;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.FieldDiffs;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.FieldDiffs;
import org.sonar.core.issue.IssueUpdater;

import static com.google.common.collect.Lists.newArrayList;

+ 1
- 1
sonar-batch/src/main/java/org/sonar/batch/issue/DefaultIssuable.java View File

@@ -23,7 +23,7 @@ import com.google.common.collect.Lists;
import java.util.List;
import org.sonar.api.issue.Issuable;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.api.resources.Project;
import org.sonar.batch.index.BatchComponent;
import org.sonar.core.issue.DefaultIssueBuilder;

+ 1
- 1
sonar-batch/src/main/java/org/sonar/batch/issue/DefaultProjectIssues.java View File

@@ -23,7 +23,7 @@ import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.ProjectIssues;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.core.issue.DefaultIssue;

import javax.annotation.Nullable;


+ 1
- 1
sonar-batch/src/main/java/org/sonar/batch/issue/IssueCache.java View File

@@ -20,7 +20,7 @@
package org.sonar.batch.issue;

import org.sonar.api.batch.BatchSide;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.batch.index.Cache;
import org.sonar.batch.index.Caches;


+ 1
- 1
sonar-batch/src/main/java/org/sonar/batch/issue/IssueFilters.java View File

@@ -21,7 +21,7 @@ package org.sonar.batch.issue;

import org.sonar.api.batch.BatchSide;
import org.sonar.api.issue.batch.IssueFilter;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.core.issue.DefaultIssue;

@BatchSide
public class IssueFilters {

+ 1
- 1
sonar-batch/src/main/java/org/sonar/batch/issue/ModuleIssues.java View File

@@ -28,7 +28,7 @@ import org.sonar.api.batch.rule.ActiveRules;
import org.sonar.api.batch.rule.Rule;
import org.sonar.api.batch.rule.Rules;
import org.sonar.api.batch.rule.internal.DefaultActiveRule;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.api.resources.Project;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.Violation;

+ 0
- 93
sonar-batch/src/main/java/org/sonar/batch/issue/tracking/InitialOpenIssuesSensor.java View File

@@ -1,93 +0,0 @@
/*
* 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.batch.issue.tracking;

import java.util.Calendar;
import java.util.Date;
import org.apache.commons.lang.time.DateUtils;
import org.apache.ibatis.session.ResultContext;
import org.apache.ibatis.session.ResultHandler;
import org.sonar.api.batch.RequiresDB;
import org.sonar.api.batch.Sensor;
import org.sonar.api.batch.SensorContext;
import org.sonar.api.resources.Project;
import org.sonar.core.issue.db.IssueChangeDao;
import org.sonar.core.issue.db.IssueChangeDto;
import org.sonar.core.issue.db.IssueDao;
import org.sonar.core.issue.db.IssueDto;
import org.sonar.core.resource.ResourceDao;
import org.sonar.core.resource.ResourceDto;
import org.sonar.core.resource.ResourceQuery;

/**
* Load all the issues referenced during the previous scan.
*/
@RequiresDB
public class InitialOpenIssuesSensor implements Sensor {

private final InitialOpenIssuesStack initialOpenIssuesStack;
private final IssueDao issueDao;
private final IssueChangeDao issueChangeDao;
private final ResourceDao resourceDao;

public InitialOpenIssuesSensor(InitialOpenIssuesStack initialOpenIssuesStack, IssueDao issueDao, IssueChangeDao issueChangeDao, ResourceDao resourceDao) {
this.initialOpenIssuesStack = initialOpenIssuesStack;
this.issueDao = issueDao;
this.issueChangeDao = issueChangeDao;
this.resourceDao = resourceDao;
}

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

@Override
public void analyse(Project project, SensorContext context) {
ResourceDto module = resourceDao.getResource(ResourceQuery.create().setKey(project.getEffectiveKey()));
if (module != null) {
long moduleId = module.getId();
// Adding one second is a hack for resolving conflicts with concurrent user
// changes during issue persistence
final Date now = DateUtils.addSeconds(DateUtils.truncate(new Date(), Calendar.MILLISECOND), 1);
issueDao.selectNonClosedIssuesByModule(moduleId, new ResultHandler() {
@Override
public void handleResult(ResultContext rc) {
IssueDto dto = (IssueDto) rc.getResultObject();
dto.setSelectedAt(now.getTime());
initialOpenIssuesStack.addIssue(dto);
}
});

issueChangeDao.selectChangelogOnNonClosedIssuesByModuleAndType(moduleId, new ResultHandler() {
@Override
public void handleResult(ResultContext rc) {
IssueChangeDto dto = (IssueChangeDto) rc.getResultObject();
initialOpenIssuesStack.addChangelog(dto);
}
});
}
}

@Override
public String toString() {
return getClass().getSimpleName();
}
}

+ 0
- 86
sonar-batch/src/main/java/org/sonar/batch/issue/tracking/InitialOpenIssuesStack.java View File

@@ -1,86 +0,0 @@
/*
* 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.batch.issue.tracking;

import org.sonar.api.batch.BatchSide;
import org.sonar.api.batch.InstantiationStrategy;
import org.sonar.batch.index.Cache;
import org.sonar.batch.index.Caches;
import org.sonar.core.issue.db.IssueChangeDto;
import org.sonar.core.issue.db.IssueDto;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static com.google.common.collect.Lists.newArrayList;

@BatchSide
@InstantiationStrategy(InstantiationStrategy.PER_BATCH)
public class InitialOpenIssuesStack {

private final Cache<IssueDto> issuesCache;
private final Cache<ArrayList<IssueChangeDto>> issuesChangelogCache;

public InitialOpenIssuesStack(Caches caches) {
issuesCache = caches.createCache("last-open-issues");
issuesChangelogCache = caches.createCache("issues-changelog");
}

public InitialOpenIssuesStack addIssue(IssueDto issueDto) {
issuesCache.put(issueDto.getComponentKey(), issueDto.getKee(), issueDto);
return this;
}

public List<ServerIssue> selectAndRemoveIssues(String componentKey) {
Iterable<IssueDto> issues = issuesCache.values(componentKey);
List<ServerIssue> result = newArrayList();
for (IssueDto issue : issues) {
result.add(new ServerIssueFromDb(issue));
}
issuesCache.clear(componentKey);
return result;
}

public Iterable<IssueDto> selectAllIssues() {
return issuesCache.values();
}

public InitialOpenIssuesStack addChangelog(IssueChangeDto issueChangeDto) {
List<IssueChangeDto> changeDtos = issuesChangelogCache.get(issueChangeDto.getIssueKey());
if (changeDtos == null) {
changeDtos = newArrayList();
}
changeDtos.add(issueChangeDto);
issuesChangelogCache.put(issueChangeDto.getIssueKey(), newArrayList(changeDtos));
return this;
}

public List<IssueChangeDto> selectChangelog(String issueKey) {
List<IssueChangeDto> changeDtos = issuesChangelogCache.get(issueKey);
return changeDtos != null ? changeDtos : Collections.<IssueChangeDto>emptyList();
}

public void clear() {
issuesCache.clear();
issuesChangelogCache.clear();
}
}

+ 0
- 141
sonar-batch/src/main/java/org/sonar/batch/issue/tracking/IssueHandlers.java View File

@@ -1,141 +0,0 @@
/*
* 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.batch.issue.tracking;

import org.sonar.api.batch.BatchSide;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.IssueHandler;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.IssueChangeContext;
import org.sonar.api.user.User;
import org.sonar.core.issue.IssueUpdater;
import org.sonar.core.user.DefaultUser;

import javax.annotation.Nullable;

@BatchSide
public class IssueHandlers {
private final IssueHandler[] handlers;
private final DefaultContext context;

public IssueHandlers(IssueUpdater updater, IssueHandler[] handlers) {
this.handlers = handlers;
this.context = new DefaultContext(updater);
}

public IssueHandlers(IssueUpdater updater) {
this(updater, new IssueHandler[0]);
}

public void execute(DefaultIssue issue, IssueChangeContext changeContext) {
context.reset(issue, changeContext);
for (IssueHandler handler : handlers) {
handler.onIssue(context);
}
}

static class DefaultContext implements IssueHandler.Context {
private final IssueUpdater updater;
private DefaultIssue issue;
private IssueChangeContext changeContext;

private DefaultContext(IssueUpdater updater) {
this.updater = updater;
}

private void reset(DefaultIssue i, IssueChangeContext changeContext) {
this.issue = i;
this.changeContext = changeContext;
}

@Override
public Issue issue() {
return issue;
}

@Override
public boolean isNew() {
return issue.isNew();
}

@Override
public boolean isEndOfLife() {
return issue.isEndOfLife();
}

@Override
public IssueHandler.Context setLine(@Nullable Integer line) {
updater.setLine(issue, line);
return this;
}

@Override
public IssueHandler.Context setMessage(@Nullable String s) {
updater.setMessage(issue, s, changeContext);
return this;
}

@Override
public IssueHandler.Context setSeverity(String severity) {
updater.setSeverity(issue, severity, changeContext);
return this;
}

@Override
public IssueHandler.Context setAuthorLogin(@Nullable String login) {
updater.setAuthorLogin(issue, login, changeContext);
return this;
}

@Override
public IssueHandler.Context setEffortToFix(@Nullable Double d) {
updater.setEffortToFix(issue, d, changeContext);
return this;
}

@Override
public IssueHandler.Context setAttribute(String key, @Nullable String value) {
throw new UnsupportedOperationException("TODO");
}

@Override
public IssueHandler.Context assign(@Nullable String assignee) {
User user = null;
if (assignee != null) {
user = new DefaultUser().setLogin(assignee).setName(assignee);
}
updater.assign(issue, user, changeContext);
return this;
}

@Override
public IssueHandler.Context assign(@Nullable User user) {
updater.assign(issue, user, changeContext);
return this;
}

@Override
public IssueHandler.Context addComment(String text) {
updater.addComment(issue, text, changeContext);
return this;
}
}

}

+ 1
- 1
sonar-batch/src/main/java/org/sonar/batch/issue/tracking/IssueTracking.java View File

@@ -29,7 +29,7 @@ import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import org.sonar.api.batch.BatchSide;
import org.sonar.api.batch.InstantiationStrategy;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.core.issue.DefaultIssue;

import javax.annotation.Nullable;


+ 0
- 279
sonar-batch/src/main/java/org/sonar/batch/issue/tracking/IssueTrackingDecorator.java View File

@@ -1,279 +0,0 @@
/*
* 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.batch.issue.tracking;

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.RequiresDB;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.internal.DefaultInputFile;
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.File;
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.filesystem.InputPathCache;
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)
@RequiresDB
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 ServerLineHashesLoader lastLineHashes;
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;
private final InputPathCache inputPathCache;
private final Project project;

public IssueTrackingDecorator(IssueCache issueCache, InitialOpenIssuesStack initialOpenIssues, IssueTracking tracking,
ServerLineHashesLoader lastLineHashes,
IssueHandlers handlers, IssueWorkflow workflow,
IssueUpdater updater,
Project project,
ResourcePerspectives perspectives,
RulesProfile rulesProfile,
RuleFinder ruleFinder, InputPathCache inputPathCache) {
this.issueCache = issueCache;
this.initialOpenIssues = initialOpenIssues;
this.tracking = tracking;
this.lastLineHashes = lastLineHashes;
this.handlers = handlers;
this.workflow = workflow;
this.updater = updater;
this.project = project;
this.inputPathCache = inputPathCache;
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<ServerIssue> dbOpenIssues = initialOpenIssues.selectAndRemoveIssues(resource.getEffectiveKey());

SourceHashHolder sourceHashHolder = null;
if (ResourceUtils.isFile(resource)) {
File sonarFile = (File) resource;
InputFile file = inputPathCache.getFile(project.getEffectiveKey(), sonarFile.getPath());
if (file == null) {
throw new IllegalStateException("File " + resource + " was not found in InputPath cache");
}
sourceHashHolder = new SourceHashHolder((DefaultInputFile) file, lastLineHashes);
}

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 = ((ServerIssueFromDb) result.matching(issue)).getDto();

// 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());
issue.setTags(ref.getTags());

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<ServerIssue> unmatchedIssues, SourceHashHolder sourceHashHolder, Collection<DefaultIssue> issues) {
for (ServerIssue unmatchedIssue : unmatchedIssues) {
IssueDto unmatchedDto = ((ServerIssueFromDb) unmatchedIssue).getDto();
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);
}
}
}

+ 1
- 1
sonar-batch/src/main/java/org/sonar/batch/issue/tracking/IssueTrackingResult.java View File

@@ -23,7 +23,7 @@ import com.google.common.collect.HashMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import org.apache.commons.lang.StringUtils;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.api.rule.RuleKey;

import javax.annotation.Nullable;

+ 5
- 5
sonar-batch/src/main/java/org/sonar/batch/issue/tracking/LocalIssueTracking.java View File

@@ -29,8 +29,8 @@ 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.issue.Issue;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.IssueChangeContext;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.IssueChangeContext;
import org.sonar.api.resources.Project;
import org.sonar.api.resources.ResourceUtils;
import org.sonar.api.rule.RuleKey;
@@ -166,7 +166,7 @@ public class LocalIssueTracking {

// non-persisted fields
issue.setNew(false);
issue.setEndOfLife(false);
issue.setBeingClosed(false);
issue.setOnDisabledRule(false);

// fields to update with old values
@@ -226,9 +226,9 @@ public class LocalIssueTracking {
boolean manualIssue = issue.ruleKey().isManual();
boolean isRemovedRule = activeRule == null;
if (manualIssue) {
issue.setEndOfLife(forceEndOfLife || isRemovedRule);
issue.setBeingClosed(forceEndOfLife || isRemovedRule);
} else {
issue.setEndOfLife(true);
issue.setBeingClosed(true);
}
issue.setOnDisabledRule(isRemovedRule);
}

+ 0
- 0
sonar-batch/src/main/java/org/sonar/batch/mediumtest/TaskResult.java View File


Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save