import org.sonar.server.computation.debt.DebtModelHolderImpl;
import org.sonar.server.computation.event.EventRepositoryImpl;
import org.sonar.server.computation.issue.BaseIssuesLoader;
+import org.sonar.server.computation.issue.ComponentIssuesRepositoryImpl;
import org.sonar.server.computation.issue.ComponentsWithUnprocessedIssues;
import org.sonar.server.computation.issue.DebtAggregator;
import org.sonar.server.computation.issue.DebtCalculator;
IssueVisitors.class,
IssueLifecycle.class,
ComponentsWithUnprocessedIssues.class,
+ ComponentIssuesRepositoryImpl.class,
// common rules
CommonRuleEngineImpl.class,
--- /dev/null
+/*
+ * 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.api.ce.measure.MeasureComputer;
+import org.sonar.api.issue.Issue;
+import org.sonar.server.computation.component.Component;
+
+/**
+ * This repository contains issues for only one component at a time. It's populated by {@link IntegrateIssuesVisitor} and
+ * it's mainly used by {@link org.sonar.server.computation.measure.api.MeasureComputerImplementationContext} in order for a {@link MeasureComputer}
+ * to access to the issues of a component.
+ *
+ * This repository must NEVER contains more issues than in issues from one component order to not consume to much memory.
+ */
+public interface ComponentIssuesRepository {
+
+ /**
+ * Return issues from the component
+ *
+ * @throws IllegalStateException if no issues have been set
+ * @throws IllegalArgumentException if the component is not the component that contains current issues.
+ */
+ List<Issue> getIssues(Component component);
+
+}
--- /dev/null
+/*
+ * 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 javax.annotation.CheckForNull;
+import org.sonar.api.issue.Issue;
+import org.sonar.server.computation.component.Component;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Objects.requireNonNull;
+
+public class ComponentIssuesRepositoryImpl implements MutableComponentIssuesRepository {
+
+ @CheckForNull
+ private List<Issue> issues;
+
+ @CheckForNull
+ private Component component;
+
+ @Override
+ public void setIssues(Component component, List<Issue> issues) {
+ this.issues = requireNonNull(issues, "issues cannot be null");
+ this.component = requireNonNull(component, "component cannot be null");
+ }
+
+ @Override
+ public List<Issue> getIssues(Component component) {
+ checkState(this.component != null && this.issues != null, "Issues have not been initialized");
+ checkArgument(component.equals(this.component),
+ String.format("Only issues from component '%s' are available, but wanted component is '%s'.",
+ this.component.getReportAttributes().getRef(), component.getReportAttributes().getRef()));
+ return issues;
+ }
+}
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import org.sonar.api.issue.Issue;
import org.sonar.api.utils.log.Loggers;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.tracking.Tracking;
private final IssueCache issueCache;
private final IssueLifecycle issueLifecycle;
private final IssueVisitors issueVisitors;
+ private final MutableComponentIssuesRepository componentIssuesRepository;
private final ComponentsWithUnprocessedIssues componentsWithUnprocessedIssues;
- private final List<DefaultIssue> componentIssues = new ArrayList<>();
+ private final List<Issue> componentIssues = new ArrayList<>();
public IntegrateIssuesVisitor(TrackerExecution tracker, IssueCache issueCache, IssueLifecycle issueLifecycle, IssueVisitors issueVisitors,
- ComponentsWithUnprocessedIssues componentsWithUnprocessedIssues) {
+ ComponentsWithUnprocessedIssues componentsWithUnprocessedIssues, MutableComponentIssuesRepository componentIssuesRepository) {
super(CrawlerDepthLimit.FILE, POST_ORDER);
this.tracker = tracker;
this.issueCache = issueCache;
this.issueLifecycle = issueLifecycle;
this.issueVisitors = issueVisitors;
this.componentsWithUnprocessedIssues = componentsWithUnprocessedIssues;
+ this.componentIssuesRepository = componentIssuesRepository;
}
@Override
componentIssues.clear();
processIssues(component);
componentsWithUnprocessedIssues.remove(component.getUuid());
+ componentIssuesRepository.setIssues(component, componentIssues);
}
private void processIssues(Component component) {
--- /dev/null
+/*
+ * 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.api.issue.Issue;
+import org.sonar.server.computation.component.Component;
+
+public interface MutableComponentIssuesRepository extends ComponentIssuesRepository {
+
+ /**
+ * Add issues of the component.
+ * *
+ * @throws NullPointerException if {@code component} is {@code null}
+ * @throws NullPointerException if {@code issues} is {@code null}
+ */
+ void setIssues(Component component, List<Issue> issues);
+
+}
--- /dev/null
+/*
+ * 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.Arrays;
+import java.util.Collections;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.issue.Issue;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.server.computation.component.Component;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.server.computation.component.ReportComponent.builder;
+
+public class ComponentIssuesRepositoryImplTest {
+
+ @org.junit.Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ static final Component FILE_1 = builder(Component.Type.FILE, 1).build();
+ static final Component FILE_2 = builder(Component.Type.FILE, 2).build();
+
+ static final Issue DUMB_ISSUE = new DefaultIssue().setKey("ISSUE");
+
+ ComponentIssuesRepositoryImpl sut = new ComponentIssuesRepositoryImpl();
+
+ @Test
+ public void get_issues() throws Exception {
+ sut.setIssues(FILE_1, Arrays.asList(DUMB_ISSUE));
+
+ assertThat(sut.getIssues(FILE_1)).containsOnly(DUMB_ISSUE);
+ }
+
+ @Test
+ public void set_empty_issues() throws Exception {
+ sut.setIssues(FILE_1, Collections.<Issue>emptyList());
+
+ assertThat(sut.getIssues(FILE_1)).isEmpty();
+ }
+
+ @Test
+ public void fail_with_NPE_when_setting_issues_with_null_component() throws Exception {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("component cannot be null");
+
+ sut.setIssues(null, Arrays.asList(DUMB_ISSUE));
+ }
+
+ @Test
+ public void fail_with_NPE_when_setting_issues_with_null_issues() throws Exception {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("issues cannot be null");
+
+ sut.setIssues(FILE_1, null);
+ }
+
+ @Test
+ public void fail_with_IAE_when_getting_issues_on_different_component() throws Exception {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("Only issues from component '1' are available, but wanted component is '2'.");
+
+ sut.setIssues(FILE_1, Arrays.asList(DUMB_ISSUE));
+ sut.getIssues(FILE_2);
+ }
+
+ @Test
+ public void fail_with_ISE_when_getting_issues_but_issues_are_null() throws Exception {
+ thrown.expect(IllegalStateException.class);
+ thrown.expectMessage("Issues have not been initialized");
+
+ sut.getIssues(FILE_1);
+ }
+}
--- /dev/null
+/*
+ * 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 javax.annotation.CheckForNull;
+import org.junit.rules.ExternalResource;
+import org.sonar.api.issue.Issue;
+import org.sonar.server.computation.component.Component;
+import org.sonar.server.computation.component.ReportTreeRootHolder;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Objects.requireNonNull;
+
+public class ComponentIssuesRepositoryRule extends ExternalResource implements MutableComponentIssuesRepository, ComponentIssuesRepository {
+
+ private final ReportTreeRootHolder reportTreeRootHolder;
+
+ @CheckForNull
+ private List<Issue> issues;
+
+ @CheckForNull
+ private Component component;
+
+ public ComponentIssuesRepositoryRule(ReportTreeRootHolder reportTreeRootHolder) {
+ this.reportTreeRootHolder = reportTreeRootHolder;
+ }
+
+ @Override
+ public void setIssues(Component component, List<Issue> issues) {
+ checkNotNull(component, "component cannot be null");
+ setIssues(component.getReportAttributes().getRef(), issues);
+ }
+
+ public void setIssues(int componentRef, List<Issue> issues) {
+ this.issues = requireNonNull(issues, "issues cannot be null");
+ Component component = reportTreeRootHolder.getComponentByRef(componentRef);
+ checkArgument(component != null, String.format("Component '%s' does not exists in the report ", componentRef));
+ this.component = component;
+ }
+
+ @Override
+ public List<Issue> getIssues(Component component) {
+ checkNotNull(component, "component cannot be null");
+ return getIssues(component.getReportAttributes().getRef());
+ }
+
+ public List<Issue> getIssues(int componentRef) {
+ checkState(this.component != null && this.issues != null, "Issues have not been initialized");
+ Component component = reportTreeRootHolder.getComponentByRef(componentRef);
+ checkArgument(component != null, String.format("Component '%s' does not exists in the report ", componentRef));
+ checkArgument(component == this.component,
+ String.format("Only issues from component '%s' are available, but wanted component is '%s'.",
+ this.component.getReportAttributes().getRef(), component.getReportAttributes().getRef()));
+ return issues;
+ }
+
+}
@Rule
public RuleRepositoryRule ruleRepositoryRule = new RuleRepositoryRule();
+ @Rule
+ public ComponentIssuesRepositoryRule componentIssuesRepository = new ComponentIssuesRepositoryRule(treeRootHolder);
+
ArgumentCaptor<DefaultIssue> defaultIssueCaptor = ArgumentCaptor.forClass(DefaultIssue.class);
BaseIssuesLoader baseIssuesLoader = new BaseIssuesLoader(treeRootHolder, dbTester.getDbClient(), ruleRepositoryRule, activeRulesHolderRule);
public void setUp() throws Exception {
treeRootHolder.setRoot(PROJECT);
issueCache = new IssueCache(temp.newFile(), System2.INSTANCE);
- underTest = new IntegrateIssuesVisitor(tracker, issueCache, issueLifecycle, issueVisitors, componentsWithUnprocessedIssues);
+ underTest = new IntegrateIssuesVisitor(tracker, issueCache, issueLifecycle, issueVisitors, componentsWithUnprocessedIssues, componentIssuesRepository);
}
@Test
assertThat(componentsWithUnprocessedIssues.getUuids()).isEmpty();
}
+ @Test
+ public void feed_component_issues_repo() throws Exception {
+ componentsWithUnprocessedIssues.setUuids(Collections.<String>emptySet());
+
+ BatchReport.Issue reportIssue = BatchReport.Issue.newBuilder()
+ .setMsg("the message")
+ .setRuleRepository("xoo")
+ .setRuleKey("S001")
+ .setSeverity(Constants.Severity.BLOCKER)
+ .build();
+ reportReader.putIssues(FILE_REF, asList(reportIssue));
+ reportReader.putFileSourceLines(FILE_REF, "line1");
+
+ underTest.visitAny(FILE);
+
+ assertThat(componentIssuesRepository.getIssues(FILE_REF)).hasSize(1);
+ }
+
+ @Test
+ public void empty_component_issues_repo_when_no_issue() throws Exception {
+ componentsWithUnprocessedIssues.setUuids(Collections.<String>emptySet());
+
+ BatchReport.Issue reportIssue = BatchReport.Issue.newBuilder()
+ .setMsg("the message")
+ .setRuleRepository("xoo")
+ .setRuleKey("S001")
+ .setSeverity(Constants.Severity.BLOCKER)
+ .build();
+ reportReader.putIssues(FILE_REF, asList(reportIssue));
+ reportReader.putFileSourceLines(FILE_REF, "line1");
+
+ underTest.visitAny(FILE);
+ assertThat(componentIssuesRepository.getIssues(FILE_REF)).hasSize(1);
+
+ underTest.visitAny(PROJECT);
+ assertThat(componentIssuesRepository.getIssues(PROJECT)).isEmpty();
+ }
+
private void addBaseIssue(RuleKey ruleKey) {
ComponentDto project = ComponentTesting.newProjectDto(PROJECT_UUID).setKey(PROJECT_KEY);
ComponentDto file = ComponentTesting.newFileDto(project, FILE_UUID).setKey(FILE_KEY);