aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java2
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/ViolationTrackingDecorator.java140
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/ReferenceAnalysisTest.java49
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/ViolationTrackingTest.java17
-rw-r--r--plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/timemachine/ReferenceAnalysisTest/shared.xml16
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/bootstrap/ServerClient.java23
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/scan/LastSnapshots.java (renamed from plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/ReferenceAnalysis.java)49
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/scan/ScanContainer.java1
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/scan/LastSnapshotsTest.java126
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/scan/LastSnapshotsTest/last_snapshot.xml11
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/scan/LastSnapshotsTest/no_last_snapshot.xml3
-rw-r--r--sonar-core/src/main/java/org/sonar/core/persistence/DryRunDatabaseFactory.java40
-rw-r--r--sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java2
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/controllers/batch_bootstrap_controller.rb2
14 files changed, 309 insertions, 172 deletions
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
index c9c65815691..2e42100b525 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
@@ -78,7 +78,6 @@ import org.sonar.plugins.core.timemachine.NewCoverageFileAnalyzer;
import org.sonar.plugins.core.timemachine.NewItCoverageFileAnalyzer;
import org.sonar.plugins.core.timemachine.NewOverallCoverageFileAnalyzer;
import org.sonar.plugins.core.timemachine.NewViolationsDecorator;
-import org.sonar.plugins.core.timemachine.ReferenceAnalysis;
import org.sonar.plugins.core.timemachine.TendencyDecorator;
import org.sonar.plugins.core.timemachine.TimeMachineConfigurationPersister;
import org.sonar.plugins.core.timemachine.VariationDecorator;
@@ -488,7 +487,6 @@ public final class CorePlugin extends SonarPlugin {
FilesDecorator.class,
ReviewNotifications.class,
ReviewWorkflowDecorator.class,
- ReferenceAnalysis.class,
ManualMeasureDecorator.class,
ManualViolationInjector.class,
ViolationSeverityUpdater.class,
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/ViolationTrackingDecorator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/ViolationTrackingDecorator.java
index e2e52396996..9cd1e10a075 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/ViolationTrackingDecorator.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/ViolationTrackingDecorator.java
@@ -39,6 +39,7 @@ import org.sonar.api.resources.Project;
import org.sonar.api.resources.Resource;
import org.sonar.api.rules.Violation;
import org.sonar.api.violations.ViolationQuery;
+import org.sonar.batch.scan.LastSnapshots;
import org.sonar.plugins.core.timemachine.tracking.HashedSequence;
import org.sonar.plugins.core.timemachine.tracking.HashedSequenceComparator;
import org.sonar.plugins.core.timemachine.tracking.RollingHashSequence;
@@ -46,6 +47,8 @@ import org.sonar.plugins.core.timemachine.tracking.RollingHashSequenceComparator
import org.sonar.plugins.core.timemachine.tracking.StringText;
import org.sonar.plugins.core.timemachine.tracking.StringTextComparator;
+import javax.annotation.Nullable;
+
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
@@ -56,7 +59,7 @@ import java.util.Set;
@DependsUpon({DecoratorBarriers.END_OF_VIOLATIONS_GENERATION, DecoratorBarriers.START_VIOLATION_TRACKING})
@DependedUpon(DecoratorBarriers.END_OF_VIOLATION_TRACKING)
public class ViolationTrackingDecorator implements Decorator {
- private ReferenceAnalysis referenceAnalysis;
+ private LastSnapshots lastSnapshots;
private Map<Violation, RuleFailureModel> referenceViolationsMap = Maps.newIdentityHashMap();
private SonarIndex index;
private Project project;
@@ -64,10 +67,10 @@ public class ViolationTrackingDecorator implements Decorator {
/**
* Live collection of unmapped past violations.
*/
- private Set<RuleFailureModel> unmappedPastViolations = Sets.newHashSet();
+ private Set<RuleFailureModel> unmappedLastViolations = Sets.newHashSet();
- public ViolationTrackingDecorator(Project project, ReferenceAnalysis referenceAnalysis, SonarIndex index) {
- this.referenceAnalysis = referenceAnalysis;
+ public ViolationTrackingDecorator(Project project, LastSnapshots lastSnapshots, SonarIndex index) {
+ this.lastSnapshots = lastSnapshots;
this.index = index;
this.project = project;
}
@@ -89,8 +92,8 @@ public class ViolationTrackingDecorator implements Decorator {
// Load new violations
List<Violation> newViolations = prepareNewViolations(context, source);
- // Load reference violations
- List<RuleFailureModel> referenceViolations = referenceAnalysis.getViolations(resource);
+ // Load the violations of the last available analysis
+ List<RuleFailureModel> referenceViolations = lastSnapshots.getViolations(resource);
// Map new violations with old ones
mapViolations(newViolations, referenceViolations, source, resource);
@@ -111,39 +114,45 @@ public class ViolationTrackingDecorator implements Decorator {
}
@VisibleForTesting
- Map<Violation, RuleFailureModel> mapViolations(List<Violation> newViolations, List<RuleFailureModel> pastViolations) {
- return mapViolations(newViolations, pastViolations, null, null);
+ Map<Violation, RuleFailureModel> mapViolations(List<Violation> newViolations, @Nullable List<RuleFailureModel> lastViolations) {
+ return mapViolations(newViolations, lastViolations, null, null);
}
@VisibleForTesting
- Map<Violation, RuleFailureModel> mapViolations(List<Violation> newViolations, List<RuleFailureModel> pastViolations, String source, Resource resource) {
- unmappedPastViolations.addAll(pastViolations);
-
- Multimap<Integer, RuleFailureModel> pastViolationsByRule = LinkedHashMultimap.create();
- for (RuleFailureModel pastViolation : pastViolations) {
- pastViolationsByRule.put(pastViolation.getRuleId(), pastViolation);
- }
-
- // Match the permanent id of the violation. This id is for example set explicitly when injecting manual violations
- for (Violation newViolation : newViolations) {
- mapViolation(newViolation,
- findPastViolationWithSamePermanentId(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())),
- pastViolationsByRule, referenceViolationsMap);
- }
+ Map<Violation, RuleFailureModel> mapViolations(List<Violation> newViolations, @Nullable List<RuleFailureModel> lastViolations,
+ @Nullable String source, @Nullable Resource resource) {
+ boolean hasLastScan = false;
+ Multimap<Integer, RuleFailureModel> lastViolationsByRule = LinkedHashMultimap.create();
+
+ if (lastViolations != null) {
+ hasLastScan = true;
+ unmappedLastViolations.addAll(lastViolations);
+
+ for (RuleFailureModel lastViolation : lastViolations) {
+ lastViolationsByRule.put(lastViolation.getRuleId(), lastViolation);
+ }
- // Try first to match violations on same rule with same line and with same checksum (but not necessarily with same message)
- for (Violation newViolation : newViolations) {
- if (isNotAlreadyMapped(newViolation)) {
+ // Match the permanent id of the violation. This id is for example set explicitly when injecting manual violations
+ for (Violation newViolation : newViolations) {
mapViolation(newViolation,
- findPastViolationWithSameLineAndChecksum(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())),
- pastViolationsByRule, referenceViolationsMap);
+ findLastViolationWithSamePermanentId(newViolation, lastViolationsByRule.get(newViolation.getRule().getId())),
+ lastViolationsByRule, referenceViolationsMap);
+ }
+
+ // Try first to match violations on same rule with same line and with same checksum (but not necessarily with same message)
+ for (Violation newViolation : newViolations) {
+ if (isNotAlreadyMapped(newViolation)) {
+ mapViolation(newViolation,
+ findLastViolationWithSameLineAndChecksum(newViolation, lastViolationsByRule.get(newViolation.getRule().getId())),
+ lastViolationsByRule, referenceViolationsMap);
+ }
}
}
// If each new violation matches an old one we can stop the matching mechanism
if (referenceViolationsMap.size() != newViolations.size()) {
- if (source != null && resource != null) {
- String referenceSource = referenceAnalysis.getSource(resource);
+ if (source != null && resource != null && hasLastScan) {
+ String referenceSource = lastSnapshots.getSource(resource);
if (referenceSource != null) {
HashedSequence<StringText> hashedReference = HashedSequence.wrap(new StringText(referenceSource), StringTextComparator.IGNORE_WHITESPACE);
HashedSequence<StringText> hashedSource = HashedSequence.wrap(new StringText(source), StringTextComparator.IGNORE_WHITESPACE);
@@ -152,7 +161,7 @@ public class ViolationTrackingDecorator implements Decorator {
ViolationTrackingBlocksRecognizer rec = new ViolationTrackingBlocksRecognizer(hashedReference, hashedSource, hashedComparator);
Multimap<Integer, Violation> newViolationsByLines = newViolationsByLines(newViolations, rec);
- Multimap<Integer, RuleFailureModel> pastViolationsByLines = pastViolationsByLines(unmappedPastViolations, rec);
+ Multimap<Integer, RuleFailureModel> lastViolationsByLines = lastViolationsByLines(unmappedLastViolations, rec);
RollingHashSequence<HashedSequence<StringText>> a = RollingHashSequence.wrap(hashedReference, hashedComparator, 5);
RollingHashSequence<HashedSequence<StringText>> b = RollingHashSequence.wrap(hashedSource, hashedComparator, 5);
@@ -160,7 +169,7 @@ public class ViolationTrackingDecorator implements Decorator {
Map<Integer, HashOccurrence> map = Maps.newHashMap();
- for (Integer line : pastViolationsByLines.keySet()) {
+ for (Integer line : lastViolationsByLines.keySet()) {
int hash = cmp.hash(a, line - 1);
HashOccurrence hashOccurrence = map.get(hash);
if (hashOccurrence == null) {
@@ -186,16 +195,16 @@ public class ViolationTrackingDecorator implements Decorator {
for (HashOccurrence hashOccurrence : map.values()) {
if (hashOccurrence.countA == 1 && hashOccurrence.countB == 1) {
// Guaranteed that lineA has been moved to lineB, so we can map all violations on lineA to all violations on lineB
- map(newViolationsByLines.get(hashOccurrence.lineB), pastViolationsByLines.get(hashOccurrence.lineA), pastViolationsByRule);
- pastViolationsByLines.removeAll(hashOccurrence.lineA);
+ map(newViolationsByLines.get(hashOccurrence.lineB), lastViolationsByLines.get(hashOccurrence.lineA), lastViolationsByRule);
+ lastViolationsByLines.removeAll(hashOccurrence.lineA);
newViolationsByLines.removeAll(hashOccurrence.lineB);
}
}
// Check if remaining number of lines exceeds threshold
- if (pastViolationsByLines.keySet().size() * newViolationsByLines.keySet().size() < 250000) {
+ if (lastViolationsByLines.keySet().size() * newViolationsByLines.keySet().size() < 250000) {
List<LinePair> possibleLinePairs = Lists.newArrayList();
- for (Integer oldLine : pastViolationsByLines.keySet()) {
+ for (Integer oldLine : lastViolationsByLines.keySet()) {
for (Integer newLine : newViolationsByLines.keySet()) {
int weight = rec.computeLengthOfMaximalBlock(oldLine - 1, newLine - 1);
possibleLinePairs.add(new LinePair(oldLine, newLine, weight));
@@ -204,7 +213,7 @@ public class ViolationTrackingDecorator implements Decorator {
Collections.sort(possibleLinePairs, LINE_PAIR_COMPARATOR);
for (LinePair linePair : possibleLinePairs) {
// High probability that lineA has been moved to lineB, so we can map all violations on lineA to all violations on lineB
- map(newViolationsByLines.get(linePair.lineB), pastViolationsByLines.get(linePair.lineA), pastViolationsByRule);
+ map(newViolationsByLines.get(linePair.lineB), lastViolationsByLines.get(linePair.lineA), lastViolationsByRule);
}
}
}
@@ -214,8 +223,8 @@ public class ViolationTrackingDecorator implements Decorator {
for (Violation newViolation : newViolations) {
if (isNotAlreadyMapped(newViolation)) {
mapViolation(newViolation,
- findPastViolationWithSameChecksumAndMessage(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())),
- pastViolationsByRule, referenceViolationsMap);
+ findLastViolationWithSameChecksumAndMessage(newViolation, lastViolationsByRule.get(newViolation.getRule().getId())),
+ lastViolationsByRule, referenceViolationsMap);
}
}
@@ -223,8 +232,8 @@ public class ViolationTrackingDecorator implements Decorator {
for (Violation newViolation : newViolations) {
if (isNotAlreadyMapped(newViolation)) {
mapViolation(newViolation,
- findPastViolationWithSameLineAndMessage(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())),
- pastViolationsByRule, referenceViolationsMap);
+ findLastViolationWithSameLineAndMessage(newViolation, lastViolationsByRule.get(newViolation.getRule().getId())),
+ lastViolationsByRule, referenceViolationsMap);
}
}
@@ -233,23 +242,22 @@ public class ViolationTrackingDecorator implements Decorator {
for (Violation newViolation : newViolations) {
if (isNotAlreadyMapped(newViolation)) {
mapViolation(newViolation,
- findPastViolationWithSameChecksum(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())),
- pastViolationsByRule, referenceViolationsMap);
+ findLastViolationWithSameChecksum(newViolation, lastViolationsByRule.get(newViolation.getRule().getId())),
+ lastViolationsByRule, referenceViolationsMap);
}
}
}
- unmappedPastViolations.clear();
-
+ unmappedLastViolations.clear();
return referenceViolationsMap;
}
- private void map(Collection<Violation> newViolations, Collection<RuleFailureModel> pastViolations, Multimap<Integer, RuleFailureModel> pastViolationsByRule) {
+ private void map(Collection<Violation> newViolations, Collection<RuleFailureModel> lastViolations, Multimap<Integer, RuleFailureModel> lastViolationsByRule) {
for (Violation newViolation : newViolations) {
if (isNotAlreadyMapped(newViolation)) {
- for (RuleFailureModel pastViolation : pastViolations) {
+ for (RuleFailureModel pastViolation : lastViolations) {
if (isNotAlreadyMapped(pastViolation) && Objects.equal(newViolation.getRule().getId(), pastViolation.getRuleId())) {
- mapViolation(newViolation, pastViolation, pastViolationsByRule, referenceViolationsMap);
+ mapViolation(newViolation, pastViolation, lastViolationsByRule, referenceViolationsMap);
break;
}
}
@@ -269,14 +277,14 @@ public class ViolationTrackingDecorator implements Decorator {
return newViolationsByLines;
}
- private Multimap<Integer, RuleFailureModel> pastViolationsByLines(Collection<RuleFailureModel> pastViolations, ViolationTrackingBlocksRecognizer rec) {
- Multimap<Integer, RuleFailureModel> pastViolationsByLines = LinkedHashMultimap.create();
- for (RuleFailureModel pastViolation : pastViolations) {
+ private Multimap<Integer, RuleFailureModel> lastViolationsByLines(Collection<RuleFailureModel> lastViolations, ViolationTrackingBlocksRecognizer rec) {
+ Multimap<Integer, RuleFailureModel> lastViolationsByLines = LinkedHashMultimap.create();
+ for (RuleFailureModel pastViolation : lastViolations) {
if (rec.isValidLineInSource(pastViolation.getLine())) {
- pastViolationsByLines.put(pastViolation.getLine(), pastViolation);
+ lastViolationsByLines.put(pastViolation.getLine(), pastViolation);
}
}
- return pastViolationsByLines;
+ return lastViolationsByLines;
}
private static final Comparator<LinePair> LINE_PAIR_COMPARATOR = new Comparator<LinePair>() {
@@ -305,15 +313,15 @@ public class ViolationTrackingDecorator implements Decorator {
}
private boolean isNotAlreadyMapped(RuleFailureModel pastViolation) {
- return unmappedPastViolations.contains(pastViolation);
+ return unmappedLastViolations.contains(pastViolation);
}
private boolean isNotAlreadyMapped(Violation newViolation) {
return !referenceViolationsMap.containsKey(newViolation);
}
- private RuleFailureModel findPastViolationWithSameChecksum(Violation newViolation, Collection<RuleFailureModel> pastViolations) {
- for (RuleFailureModel pastViolation : pastViolations) {
+ private RuleFailureModel findLastViolationWithSameChecksum(Violation newViolation, Collection<RuleFailureModel> lastViolations) {
+ for (RuleFailureModel pastViolation : lastViolations) {
if (isSameChecksum(newViolation, pastViolation)) {
return pastViolation;
}
@@ -321,8 +329,8 @@ public class ViolationTrackingDecorator implements Decorator {
return null;
}
- private RuleFailureModel findPastViolationWithSameLineAndMessage(Violation newViolation, Collection<RuleFailureModel> pastViolations) {
- for (RuleFailureModel pastViolation : pastViolations) {
+ private RuleFailureModel findLastViolationWithSameLineAndMessage(Violation newViolation, Collection<RuleFailureModel> lastViolations) {
+ for (RuleFailureModel pastViolation : lastViolations) {
if (isSameLine(newViolation, pastViolation) && isSameMessage(newViolation, pastViolation)) {
return pastViolation;
}
@@ -330,8 +338,8 @@ public class ViolationTrackingDecorator implements Decorator {
return null;
}
- private RuleFailureModel findPastViolationWithSameChecksumAndMessage(Violation newViolation, Collection<RuleFailureModel> pastViolations) {
- for (RuleFailureModel pastViolation : pastViolations) {
+ private RuleFailureModel findLastViolationWithSameChecksumAndMessage(Violation newViolation, Collection<RuleFailureModel> lastViolations) {
+ for (RuleFailureModel pastViolation : lastViolations) {
if (isSameChecksum(newViolation, pastViolation) && isSameMessage(newViolation, pastViolation)) {
return pastViolation;
}
@@ -339,8 +347,8 @@ public class ViolationTrackingDecorator implements Decorator {
return null;
}
- private RuleFailureModel findPastViolationWithSameLineAndChecksum(Violation newViolation, Collection<RuleFailureModel> pastViolations) {
- for (RuleFailureModel pastViolation : pastViolations) {
+ private RuleFailureModel findLastViolationWithSameLineAndChecksum(Violation newViolation, Collection<RuleFailureModel> lastViolations) {
+ for (RuleFailureModel pastViolation : lastViolations) {
if (isSameLine(newViolation, pastViolation) && isSameChecksum(newViolation, pastViolation)) {
return pastViolation;
}
@@ -348,8 +356,8 @@ public class ViolationTrackingDecorator implements Decorator {
return null;
}
- private RuleFailureModel findPastViolationWithSamePermanentId(Violation newViolation, Collection<RuleFailureModel> pastViolations) {
- for (RuleFailureModel pastViolation : pastViolations) {
+ private RuleFailureModel findLastViolationWithSamePermanentId(Violation newViolation, Collection<RuleFailureModel> lastViolations) {
+ for (RuleFailureModel pastViolation : lastViolations) {
if (isSamePermanentId(newViolation, pastViolation)) {
return pastViolation;
}
@@ -374,16 +382,16 @@ public class ViolationTrackingDecorator implements Decorator {
}
private void mapViolation(Violation newViolation, RuleFailureModel pastViolation,
- Multimap<Integer, RuleFailureModel> pastViolationsByRule, Map<Violation, RuleFailureModel> violationMap) {
+ Multimap<Integer, RuleFailureModel> lastViolationsByRule, Map<Violation, RuleFailureModel> violationMap) {
if (pastViolation != null) {
newViolation.setCreatedAt(pastViolation.getCreatedAt());
newViolation.setPermanentId(pastViolation.getPermanentId());
newViolation.setSwitchedOff(pastViolation.isSwitchedOff());
newViolation.setPersonId(pastViolation.getPersonId());
newViolation.setNew(false);
- pastViolationsByRule.remove(newViolation.getRule().getId(), pastViolation);
+ lastViolationsByRule.remove(newViolation.getRule().getId(), pastViolation);
violationMap.put(newViolation, pastViolation);
- unmappedPastViolations.remove(pastViolation);
+ unmappedLastViolations.remove(pastViolation);
} else {
newViolation.setNew(true);
newViolation.setCreatedAt(project.getAnalysisDate());
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/ReferenceAnalysisTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/ReferenceAnalysisTest.java
deleted file mode 100644
index 14b6922ca4b..00000000000
--- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/ReferenceAnalysisTest.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Sonar, open source software quality management tool.
- * Copyright (C) 2008-2012 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * Sonar 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.
- *
- * Sonar 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 Sonar; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
- */
-package org.sonar.plugins.core.timemachine;
-
-import org.junit.Test;
-import org.sonar.api.resources.JavaFile;
-import org.sonar.api.resources.Resource;
-import org.sonar.jpa.test.AbstractDbUnitTestCase;
-
-import static org.hamcrest.Matchers.is;
-import static org.junit.Assert.assertThat;
-
-public class ReferenceAnalysisTest extends AbstractDbUnitTestCase {
-
- @Test
- public void test() {
- setupData("shared");
-
- ReferenceAnalysis referenceAnalysis = new ReferenceAnalysis(getSession());
-
- Resource resource = new JavaFile("");
-
- resource.setEffectiveKey("project:org.foo.Bar");
- assertThat(referenceAnalysis.getViolations(resource).size(), is(1));
- assertThat(referenceAnalysis.getSource(resource), is("this is the file content"));
-
- resource.setEffectiveKey("project:no-such-resource");
- assertThat(referenceAnalysis.getViolations(resource).size(), is(0));
- assertThat(referenceAnalysis.getSource(resource), is(""));
- }
-
-}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/ViolationTrackingTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/ViolationTrackingTest.java
index a84f402595b..617287da75e 100644
--- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/ViolationTrackingTest.java
+++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/ViolationTrackingTest.java
@@ -28,6 +28,7 @@ import org.sonar.api.resources.Project;
import org.sonar.api.rules.Rule;
import org.sonar.api.rules.Violation;
import org.sonar.api.utils.DateUtils;
+import org.sonar.batch.scan.LastSnapshots;
import java.io.IOException;
import java.util.Arrays;
@@ -47,19 +48,19 @@ public class ViolationTrackingTest {
private ViolationTrackingDecorator decorator;
private Project project;
- private ReferenceAnalysis referenceAnalysis;
+ private LastSnapshots lastSnapshots;
@Before
public void setUp() {
project = mock(Project.class);
when(project.getAnalysisDate()).thenReturn(analysisDate);
- referenceAnalysis = mock(ReferenceAnalysis.class);
- decorator = new ViolationTrackingDecorator(project, referenceAnalysis, null);
+ lastSnapshots = mock(LastSnapshots.class);
+ decorator = new ViolationTrackingDecorator(project, lastSnapshots, null);
}
@Test
public void pastViolationNotAssiciatedWithLineShouldNotCauseNPE() throws Exception {
- when(referenceAnalysis.getSource(project)).thenReturn(load("example2-v1"));
+ when(lastSnapshots.getSource(project)).thenReturn(load("example2-v1"));
String source = load("example2-v2");
RuleFailureModel referenceViolation1 = newReferenceViolation("2 branches need to be covered", null, 50);
@@ -78,7 +79,7 @@ public class ViolationTrackingTest {
@Test
public void newViolationNotAssiciatedWithLineShouldNotCauseNPE() throws Exception {
- when(referenceAnalysis.getSource(project)).thenReturn(load("example2-v1"));
+ when(lastSnapshots.getSource(project)).thenReturn(load("example2-v1"));
String source = load("example2-v2");
RuleFailureModel referenceViolation1 = newReferenceViolation("Indentation", 7, 50);
@@ -100,7 +101,7 @@ public class ViolationTrackingTest {
*/
@Test
public void violationNotAssociatedWithLine() throws Exception {
- when(referenceAnalysis.getSource(project)).thenReturn(load("example2-v1"));
+ when(lastSnapshots.getSource(project)).thenReturn(load("example2-v1"));
String source = load("example2-v2");
RuleFailureModel referenceViolation1 = newReferenceViolation("2 branches need to be covered", null, 50);
@@ -121,7 +122,7 @@ public class ViolationTrackingTest {
*/
@Test
public void example1() throws Exception {
- when(referenceAnalysis.getSource(project)).thenReturn(load("example1-v1"));
+ when(lastSnapshots.getSource(project)).thenReturn(load("example1-v1"));
String source = load("example1-v2");
RuleFailureModel referenceViolation1 = newReferenceViolation("Indentation", 7, 50);
@@ -150,7 +151,7 @@ public class ViolationTrackingTest {
*/
@Test
public void example2() throws Exception {
- when(referenceAnalysis.getSource(project)).thenReturn(load("example2-v1"));
+ when(lastSnapshots.getSource(project)).thenReturn(load("example2-v1"));
String source = load("example2-v2");
RuleFailureModel referenceViolation1 = newReferenceViolation("SystemPrintln", 5, 50);
diff --git a/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/timemachine/ReferenceAnalysisTest/shared.xml b/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/timemachine/ReferenceAnalysisTest/shared.xml
deleted file mode 100644
index cca1757cb78..00000000000
--- a/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/timemachine/ReferenceAnalysisTest/shared.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<dataset>
-
- <projects id="200" scope="FIL" qualifier="CLA" kee="project:org.foo.Bar" root_id="[null]"
- name="Bar" long_name="org.foo.Bar" description="[null]"
- enabled="true" language="java" copy_resource_id="[null]" person_id="[null]" />
-
- <snapshots purge_status="[null]" period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" id="1000" project_id="200" parent_snapshot_id="[null]" root_project_id="100" root_snapshot_id="[null]"
- scope="FIL" qualifier="CLA" created_at="2008-11-01 13:58:00.00" build_date="2008-11-01 13:58:00.00" version="[null]" path=""
- status="P" islast="true" depth="3" />
-
- <rule_failures switched_off="false" permanent_id="1" ID="1" SNAPSHOT_ID="1000" RULE_ID="30" FAILURE_LEVEL="3" MESSAGE="old message" LINE="10" COST="[null]"
- created_at="2008-11-01 13:58:00.00" checksum="[null]" person_id="[null]"/>
-
- <snapshot_sources ID="1" SNAPSHOT_ID="1000" DATA="this is the file content"/>
-
-</dataset>
diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ServerClient.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ServerClient.java
index 181f7703ab3..fda39f6c801 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ServerClient.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ServerClient.java
@@ -33,6 +33,7 @@ import org.sonar.api.utils.SonarException;
import org.sonar.batch.bootstrapper.EnvironmentInformation;
import java.io.File;
+import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
@@ -59,19 +60,23 @@ public class ServerClient implements BatchComponent {
InputSupplier<InputStream> inputSupplier = doRequest(pathStartingWithSlash);
Files.copy(inputSupplier, toFile);
} catch (HttpDownloader.HttpException he) {
- throw handleHttpException(he);
- } catch (Exception e) {
+ throw handleHttpException(pathStartingWithSlash, he);
+ } catch (IOException e) {
throw new SonarException(String.format("Unable to download '%s' to: %s", pathStartingWithSlash, toFile), e);
}
}
public String request(String pathStartingWithSlash) {
+ return request(pathStartingWithSlash, true);
+ }
+
+ public String request(String pathStartingWithSlash, boolean wrapHttpException) {
InputSupplier<InputStream> inputSupplier = doRequest(pathStartingWithSlash);
try {
return IOUtils.toString(inputSupplier.getInput(), "UTF-8");
- } catch (HttpDownloader.HttpException he) {
- throw handleHttpException(he);
- } catch (Exception e) {
+ } catch (HttpDownloader.HttpException e) {
+ throw (wrapHttpException ? handleHttpException(pathStartingWithSlash, e) : e);
+ } catch (IOException e) {
throw new SonarException(String.format("Unable to request: %s", pathStartingWithSlash), e);
}
}
@@ -96,14 +101,14 @@ public class ServerClient implements BatchComponent {
}
}
- private SonarException handleHttpException(HttpDownloader.HttpException he) {
+ private RuntimeException handleHttpException(String uri, HttpDownloader.HttpException he) {
if (he.getResponseCode() == 401) {
- throw new SonarException(String.format(getMessageWhenNotAuthorized(), CoreProperties.LOGIN, CoreProperties.PASSWORD));
+ return new SonarException(String.format(getMessageWhenNotAuthorized(), CoreProperties.LOGIN, CoreProperties.PASSWORD));
}
- throw new SonarException(String.format("Fail to execute request [code=%s, url=%s]", he.getResponseCode(), he.getUri()), he);
+ return new SonarException(String.format("Fail to execute request [code=%s, url=%s]", he.getResponseCode(), he.getUri()), he);
}
- private String getMessageWhenNotAuthorized(){
+ private String getMessageWhenNotAuthorized() {
String login = settings.getProperty(CoreProperties.LOGIN);
String password = settings.getProperty(CoreProperties.PASSWORD);
if (StringUtils.isEmpty(login) && StringUtils.isEmpty(password)) {
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/ReferenceAnalysis.java b/sonar-batch/src/main/java/org/sonar/batch/scan/LastSnapshots.java
index c5a44fd82b0..490ce77ffea 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/ReferenceAnalysis.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/scan/LastSnapshots.java
@@ -17,38 +17,75 @@
* License along with Sonar; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
-package org.sonar.plugins.core.timemachine;
+package org.sonar.batch.scan;
-import org.sonar.api.BatchExtension;
+import org.sonar.api.BatchComponent;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.config.Settings;
import org.sonar.api.database.DatabaseSession;
import org.sonar.api.database.model.ResourceModel;
import org.sonar.api.database.model.RuleFailureModel;
import org.sonar.api.database.model.Snapshot;
import org.sonar.api.database.model.SnapshotSource;
import org.sonar.api.resources.Resource;
+import org.sonar.api.resources.ResourceUtils;
+import org.sonar.api.utils.HttpDownloader;
+import org.sonar.batch.bootstrap.ServerClient;
+import javax.annotation.CheckForNull;
import javax.persistence.Query;
import java.util.Collections;
import java.util.List;
-public class ReferenceAnalysis implements BatchExtension {
+public class LastSnapshots implements BatchComponent {
- private DatabaseSession session;
+ private final Settings settings;
+ private final DatabaseSession session;
+ private final ServerClient server;
- public ReferenceAnalysis(DatabaseSession session) {
+ public LastSnapshots(Settings settings, DatabaseSession session, ServerClient server) {
+ this.settings = settings;
this.session = session;
+ this.server = server;
}
+ /**
+ * Return null if this is the first scan (no last scan).
+ */
+ @CheckForNull
public List<RuleFailureModel> getViolations(Resource resource) {
Snapshot snapshot = getSnapshot(resource);
if (snapshot != null) {
return session.getResults(RuleFailureModel.class, "snapshotId", snapshot.getId());
}
- return Collections.emptyList();
+ return null;
}
public String getSource(Resource resource) {
+ String source = "";
+ if (ResourceUtils.isFile(resource)) {
+ if (settings.getBoolean(CoreProperties.DRY_RUN)) {
+ source = loadSourceFromWs(resource);
+ } else {
+ source = loadSourceFromDb(resource);
+ }
+ }
+ return source;
+ }
+
+ private String loadSourceFromWs(Resource resource) {
+ try {
+ return server.request("/api/sources?resource=" + resource.getEffectiveKey() + "&format=txt", false);
+ } catch (HttpDownloader.HttpException he) {
+ if (he.getResponseCode() == 404) {
+ return "";
+ }
+ throw he;
+ }
+ }
+
+ private String loadSourceFromDb(Resource resource) {
Snapshot snapshot = getSnapshot(resource);
if (snapshot != null) {
SnapshotSource source = session.getSingleResult(SnapshotSource.class, "snapshotId", snapshot.getId());
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ScanContainer.java
index dcb25cbdeaa..e898e87d53f 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/scan/ScanContainer.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ScanContainer.java
@@ -94,6 +94,7 @@ public class ScanContainer extends Container {
}
container.addSingleton(Languages.class);
container.addSingleton(RulesDao.class);
+ container.addSingleton(LastSnapshots.class);
// file system
container.addSingleton(PathResolver.class);
diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/LastSnapshotsTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/LastSnapshotsTest.java
new file mode 100644
index 00000000000..62e756b8c7d
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/scan/LastSnapshotsTest.java
@@ -0,0 +1,126 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.batch.scan;
+
+import org.junit.Test;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.config.Settings;
+import org.sonar.api.database.model.RuleFailureModel;
+import org.sonar.api.resources.File;
+import org.sonar.api.utils.HttpDownloader;
+import org.sonar.batch.bootstrap.ServerClient;
+import org.sonar.jpa.test.AbstractDbUnitTestCase;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.List;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+public class LastSnapshotsTest extends AbstractDbUnitTestCase {
+
+ @Test
+ public void should_return_null_violations_if_no_last_snapshot() {
+ setupData("no_last_snapshot");
+ ServerClient server = mock(ServerClient.class);
+
+ LastSnapshots lastSnapshots = new LastSnapshots(new Settings(), getSession(), server);
+
+ assertThat(lastSnapshots.getViolations(new File("org/foo", "Bar.c"))).isNull();
+ verifyZeroInteractions(server);
+ }
+
+ @Test
+ public void should_get_violations_of_last_snapshot() {
+ setupData("last_snapshot");
+ ServerClient server = mock(ServerClient.class);
+
+ LastSnapshots lastSnapshots = new LastSnapshots(new Settings(), getSession(), server);
+
+ List<RuleFailureModel> violations = lastSnapshots.getViolations(newFile());
+ assertThat(violations).hasSize(1);
+ assertThat(violations.get(0).getChecksum()).isEqualTo("ABCDE");
+ verifyZeroInteractions(server);
+ }
+
+ @Test
+ public void should_get_source_of_last_snapshot() {
+ setupData("last_snapshot");
+ ServerClient server = mock(ServerClient.class);
+
+ LastSnapshots lastSnapshots = new LastSnapshots(new Settings(), getSession(), server);
+
+ assertThat(lastSnapshots.getSource(newFile())).isEqualTo("this is bar");
+ verifyZeroInteractions(server);
+ }
+
+ @Test
+ public void should_return_empty_source_if_no_last_snapshot() {
+ setupData("no_last_snapshot");
+ ServerClient server = mock(ServerClient.class);
+
+ LastSnapshots lastSnapshots = new LastSnapshots(new Settings(), getSession(), server);
+
+ assertThat(lastSnapshots.getSource(newFile())).isEqualTo("");
+ verifyZeroInteractions(server);
+ }
+
+ @Test
+ public void should_download_source_from_ws_if_dry_run() {
+ setupData("last_snapshot");
+ ServerClient server = mock(ServerClient.class);
+ when(server.request(anyString(), eq(false))).thenReturn("downloaded source of Bar.c");
+
+ Settings settings = new Settings();
+ settings.setProperty(CoreProperties.DRY_RUN, true);
+ LastSnapshots lastSnapshots = new LastSnapshots(settings, getSession(), server);
+
+ String source = lastSnapshots.getSource(newFile());
+ assertThat(source).isEqualTo("downloaded source of Bar.c");
+ verify(server).request("/api/sources?resource=myproject:org/foo/Bar.c&format=txt", false);
+ }
+
+ @Test
+ public void should_return_empty_source_if_dry_run_and_no_last_snapshot() throws URISyntaxException {
+ setupData("last_snapshot");
+ ServerClient server = mock(ServerClient.class);
+ when(server.request(anyString(), eq(false))).thenThrow(new HttpDownloader.HttpException(new URI(""), 404));
+
+ Settings settings = new Settings();
+ settings.setProperty(CoreProperties.DRY_RUN, true);
+ LastSnapshots lastSnapshots = new LastSnapshots(settings, getSession(), server);
+
+ String source = lastSnapshots.getSource(newFile());
+ assertThat(source).isEqualTo("");
+ verify(server).request("/api/sources?resource=myproject:org/foo/Bar.c&format=txt", false);
+ }
+
+ private File newFile() {
+ File file = new File("org/foo", "Bar.c");
+ file.setEffectiveKey("myproject:org/foo/Bar.c");
+ return file;
+ }
+}
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/scan/LastSnapshotsTest/last_snapshot.xml b/sonar-batch/src/test/resources/org/sonar/batch/scan/LastSnapshotsTest/last_snapshot.xml
new file mode 100644
index 00000000000..6cc2ea5b8a5
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/scan/LastSnapshotsTest/last_snapshot.xml
@@ -0,0 +1,11 @@
+<dataset>
+ <projects id="100" kee="myproject:org/foo/Bar.c" enabled="[true]" scope="FIL" qualifier="FIL" language="c"/>
+ <snapshots id="1000" project_id="100" status="P" islast="[false]" purge_status="[null]"/>
+ <snapshots id="1100" project_id="100" status="P" islast="[true]" purge_status="[null]"/>
+ <snapshot_sources ID="10000" SNAPSHOT_ID="1100" DATA="this is bar"/>
+ <rule_failures ID="1000000" SNAPSHOT_ID="1100" RULE_ID="1"
+ switched_off="[null]" permanent_id="[null]" FAILURE_LEVEL="2"
+ MESSAGE="msg1" LINE="[null]" COST="[null]"
+ created_at="2008-12-02 13:58:00.00"
+ checksum="ABCDE" person_id="[null]"/>
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/scan/LastSnapshotsTest/no_last_snapshot.xml b/sonar-batch/src/test/resources/org/sonar/batch/scan/LastSnapshotsTest/no_last_snapshot.xml
new file mode 100644
index 00000000000..84d67a04385
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/scan/LastSnapshotsTest/no_last_snapshot.xml
@@ -0,0 +1,3 @@
+<dataset>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/DryRunDatabaseFactory.java b/sonar-core/src/main/java/org/sonar/core/persistence/DryRunDatabaseFactory.java
index c3d9717302e..b11d350e0bc 100644
--- a/sonar-core/src/main/java/org/sonar/core/persistence/DryRunDatabaseFactory.java
+++ b/sonar-core/src/main/java/org/sonar/core/persistence/DryRunDatabaseFactory.java
@@ -24,7 +24,9 @@ import org.apache.commons.dbcp.BasicDataSource;
import org.sonar.api.ServerComponent;
import org.sonar.api.platform.ServerFileSystem;
import org.sonar.api.utils.SonarException;
+import org.sonar.core.review.ReviewDto;
+import javax.annotation.Nullable;
import javax.sql.DataSource;
import java.io.File;
@@ -46,14 +48,14 @@ public class DryRunDatabaseFactory implements ServerComponent {
this.serverFileSystem = serverFileSystem;
}
- public byte[] createDatabaseForDryRun(long projectId) {
+ public byte[] createDatabaseForDryRun(@Nullable Long projectId) {
String name = serverFileSystem.getTempDir().getAbsolutePath() + "db-" + System.nanoTime();
try {
DataSource source = database.getDataSource();
BasicDataSource destination = create(DIALECT, DRIVER, USER, PASSWORD, URL + name);
- copy(source, destination);
+ copy(source, destination, projectId);
close(destination);
return dbFileContent(name);
@@ -62,18 +64,28 @@ public class DryRunDatabaseFactory implements ServerComponent {
}
}
- private void copy(DataSource source, DataSource dest) {
- new DbTemplate()
- .copyTable(source, dest, "active_rules")
- .copyTable(source, dest, "active_rule_parameters")
- .copyTable(source, dest, "characteristics")
- .copyTable(source, dest, "characteristic_edges")
- .copyTable(source, dest, "characteristic_properties")
- .copyTable(source, dest, "metrics")
- .copyTable(source, dest, "quality_models")
- .copyTable(source, dest, "rules")
- .copyTable(source, dest, "rules_parameters")
- .copyTable(source, dest, "rules_profiles");
+ private void copy(DataSource source, DataSource dest, @Nullable Long projectId) {
+ DbTemplate template = new DbTemplate();
+ template
+ .copyTable(source, dest, "active_rules")
+ .copyTable(source, dest, "active_rule_parameters")
+ .copyTable(source, dest, "characteristics")
+ .copyTable(source, dest, "characteristic_edges")
+ .copyTable(source, dest, "characteristic_properties")
+ .copyTable(source, dest, "metrics")
+ .copyTable(source, dest, "quality_models")
+ .copyTable(source, dest, "rules")
+ .copyTable(source, dest, "rules_parameters")
+ .copyTable(source, dest, "rules_profiles");
+ if (projectId != null) {
+ String snapshotCondition = "islast=" + database.getDialect().getTrueSqlValue() + " and (project_id=" + projectId + " or root_project_id=" + projectId + ")";
+ template
+ .copyTable(source, dest, "projects", "(id=" + projectId + " or root_id=" + projectId + ")")
+ .copyTable(source, dest, "reviews", "project_id=" + projectId, "status<>'" + ReviewDto.STATUS_CLOSED + "'")
+ .copyTable(source, dest, "rule_failures", "snapshot_id in (select id from snapshots where " + snapshotCondition + ")")
+ .copyTable(source, dest, "snapshots", snapshotCondition)
+ ;
+ }
}
private BasicDataSource create(String dialect, String driver, String user, String password, String url) {
diff --git a/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java b/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
index 3d19e74337f..c2bb38c09a7 100644
--- a/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
+++ b/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
@@ -517,7 +517,7 @@ public final class JRubyFacade {
}
}
- public byte[] createDatabaseForDryRun(long projectId) {
+ public byte[] createDatabaseForDryRun(@Nullable Long projectId) {
return get(DryRunDatabaseFactory.class).createDatabaseForDryRun(projectId);
}
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/batch_bootstrap_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/batch_bootstrap_controller.rb
index 9f2d570cacc..9c2055bbffe 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/batch_bootstrap_controller.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/batch_bootstrap_controller.rb
@@ -25,7 +25,7 @@ class BatchBootstrapController < Api::ApiController
def db
require_parameters :project
project = load_project()
- db_content = java_facade.createDatabaseForDryRun(project.id)
+ db_content = java_facade.createDatabaseForDryRun(project ? project.id : nil)
send_data String.from_java_bytes(db_content)
end