Skip to content

Commit b8cf697

Browse files
committed
Fix DataTableRowWatcher to work under CsvDataTableStore
DataTableRowWatcher diffed snapshot row counts via DataTableStore.getRows(), which returns Stream.empty() under CsvDataTableStore. The watcher therefore saw zero rows in production (`mod`, SaaS) regardless of what the inner recipe actually emitted, so all six recipes that depend on it (LibraryUpgrade, ScalaVersionUpgrade, KotlinVersionUpgrade, GroovyVersionUpgrade, ParentPomUpgrade, DependencyVulnerabilityCheck) silently produced no UpgradesAndMigrations rows. Replace the read-back approach with a transparent decorator that wraps the existing store for the duration of the inner-recipe call, intercepts insertRow on the matching DataTable bucket, and forwards every call to the delegate. Works under any DataTableStore implementation.
1 parent cbc2695 commit b8cf697

3 files changed

Lines changed: 154 additions & 12 deletions

File tree

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ dependencies {
3737
testImplementation("org.openrewrite:rewrite-java-21:${rewriteVersion}")
3838
testImplementation(gradleApi())
3939
testImplementation("org.openrewrite.gradle.tooling:model:${rewriteVersion}")
40+
testImplementation("de.siegmar:fastcsv:3.+")
4041
testRuntimeOnly("junit:junit:4.+")
4142
}
4243

src/main/java/io/moderne/devcenter/internal/DataTableRowWatcher.java

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,39 +15,77 @@
1515
*/
1616
package io.moderne.devcenter.internal;
1717

18+
import org.jspecify.annotations.Nullable;
1819
import org.openrewrite.DataTable;
1920
import org.openrewrite.DataTableExecutionContextView;
2021
import org.openrewrite.DataTableStore;
2122
import org.openrewrite.ExecutionContext;
2223

2324
import java.util.ArrayList;
25+
import java.util.Collection;
2426
import java.util.List;
25-
26-
import static java.util.stream.Collectors.toList;
27+
import java.util.Objects;
28+
import java.util.stream.Stream;
2729

2830
public class DataTableRowWatcher<Row> {
2931
private final DataTable<Row> dataTable;
3032
private final ExecutionContext ctx;
31-
private int snapshotSize;
33+
private final List<Row> captured = new ArrayList<>();
34+
private @Nullable DataTableStore originalStore;
3235

3336
public DataTableRowWatcher(DataTable<Row> dataTable, ExecutionContext ctx) {
3437
this.dataTable = dataTable;
3538
this.ctx = ctx;
3639
}
3740

3841
public void start() {
39-
DataTableStore store = DataTableExecutionContextView.view(ctx).getDataTableStore();
40-
snapshotSize = (int) store.getRows(dataTable.getName(), dataTable.getGroup()).count();
42+
DataTableExecutionContextView view = DataTableExecutionContextView.view(ctx);
43+
originalStore = view.getDataTableStore();
44+
view.setDataTableStore(new RecordingStore(originalStore));
4145
}
4246

43-
@SuppressWarnings("unchecked")
4447
public List<Row> stop() {
45-
DataTableStore store = DataTableExecutionContextView.view(ctx).getDataTableStore();
46-
List<Row> allRows = (List<Row>) store.getRows(dataTable.getName(), dataTable.getGroup())
47-
.collect(toList());
48-
if (allRows.size() > snapshotSize) {
49-
return new ArrayList<>(allRows.subList(snapshotSize, allRows.size()));
48+
DataTableExecutionContextView.view(ctx).setDataTableStore(
49+
Objects.requireNonNull(originalStore, "stop() called before start()"));
50+
return new ArrayList<>(captured);
51+
}
52+
53+
private boolean matchesTarget(DataTable<?> table) {
54+
if (!table.getName().equals(dataTable.getName())) {
55+
return false;
56+
}
57+
return Objects.equals(bucketKey(table), bucketKey(dataTable));
58+
}
59+
60+
private static @Nullable String bucketKey(DataTable<?> table) {
61+
return table.getGroup() != null ? table.getGroup() : table.getInstanceName();
62+
}
63+
64+
private final class RecordingStore implements DataTableStore {
65+
private final DataTableStore delegate;
66+
67+
RecordingStore(DataTableStore delegate) {
68+
this.delegate = delegate;
69+
}
70+
71+
@Override
72+
public <R> void insertRow(DataTable<R> table, ExecutionContext ctx, R row) {
73+
delegate.insertRow(table, ctx, row);
74+
if (matchesTarget(table)) {
75+
@SuppressWarnings("unchecked")
76+
Row casted = (Row) row;
77+
captured.add(casted);
78+
}
79+
}
80+
81+
@Override
82+
public Stream<?> getRows(String dataTableName, @Nullable String group) {
83+
return delegate.getRows(dataTableName, group);
84+
}
85+
86+
@Override
87+
public Collection<DataTable<?>> getDataTables() {
88+
return delegate.getDataTables();
5089
}
51-
return new ArrayList<>();
5290
}
5391
}

src/test/java/io/moderne/devcenter/DevCenterTest.java

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,32 @@
1515
*/
1616
package io.moderne.devcenter;
1717

18+
import de.siegmar.fastcsv.reader.CommentStrategy;
19+
import de.siegmar.fastcsv.reader.CsvReader;
20+
import de.siegmar.fastcsv.reader.NamedCsvRecord;
1821
import io.moderne.devcenter.table.UpgradesAndMigrations;
1922
import org.intellij.lang.annotations.Language;
2023
import org.junit.jupiter.api.BeforeEach;
2124
import org.junit.jupiter.api.Test;
25+
import org.junit.jupiter.api.io.TempDir;
2226
import org.junit.jupiter.params.ParameterizedTest;
2327
import org.junit.jupiter.params.provider.Arguments;
2428
import org.junit.jupiter.params.provider.MethodSource;
29+
import org.openrewrite.CsvDataTableStore;
30+
import org.openrewrite.DataTableExecutionContextView;
2531
import org.openrewrite.DocumentExample;
32+
import org.openrewrite.InMemoryExecutionContext;
2633
import org.openrewrite.Recipe;
2734
import org.openrewrite.config.Environment;
2835
import org.openrewrite.config.YamlResourceLoader;
2936
import org.openrewrite.test.RewriteTest;
3037

3138
import java.io.ByteArrayInputStream;
39+
import java.io.IOException;
3240
import java.net.URI;
3341
import java.nio.charset.StandardCharsets;
42+
import java.nio.file.Files;
43+
import java.nio.file.Path;
3444
import java.util.List;
3545
import java.util.Properties;
3646
import java.util.stream.Stream;
@@ -248,6 +258,99 @@ public List<Recipe> getRecipeList() {
248258
);
249259
}
250260

261+
@Test
262+
void libraryUpgradeUnderCsvDataTableStore(@TempDir Path tempDir) throws Exception {
263+
Path csvDir = tempDir.resolve("datatables");
264+
Files.createDirectories(csvDir);
265+
CsvDataTableStore store = new CsvDataTableStore(csvDir);
266+
InMemoryExecutionContext ctx = new InMemoryExecutionContext();
267+
DataTableExecutionContextView.view(ctx).setDataTableStore(store);
268+
269+
@Language("yaml") var recipe = """
270+
type: specs.openrewrite.org/v1beta/recipe
271+
name: io.moderne.devcenter.SpringBoot4Card
272+
displayName: Move to Spring Boot 4.0
273+
description: Spring Boot 4.0 upgrade card.
274+
recipeList:
275+
- io.moderne.devcenter.LibraryUpgrade:
276+
cardName: Move to Spring Boot 4.0
277+
groupIdPattern: org.springframework.boot
278+
artifactIdPattern: '*'
279+
version: 4.0.0
280+
upgradeRecipe: io.moderne.java.spring.boot4.UpgradeSpringBoot_4_0
281+
""";
282+
rewriteRun(
283+
spec ->
284+
spec.recipeFromYaml(recipe, "io.moderne.devcenter.SpringBoot4Card")
285+
.executionContext(ctx)
286+
.beforeRecipe(withToolingApi()),
287+
//language=Groovy
288+
buildGradle(
289+
"""
290+
plugins {
291+
id "java"
292+
}
293+
repositories {
294+
mavenCentral()
295+
}
296+
dependencies {
297+
implementation "org.springframework.boot:spring-boot-starter:3.1.2"
298+
}
299+
""",
300+
"""
301+
plugins {
302+
id "java"
303+
}
304+
repositories {
305+
mavenCentral()
306+
}
307+
dependencies {
308+
/*~~(org.springframework.boot:spring-boot-autoconfigure:3.1.2,org.springframework.boot:spring-boot-starter-logging:3.1.2,org.springframework.boot:spring-boot-starter:3.1.2,org.springframework.boot:spring-boot:3.1.2)~~>*/implementation "org.springframework.boot:spring-boot-starter:3.1.2"
309+
}
310+
"""
311+
)
312+
);
313+
store.close();
314+
315+
Path depsCsv = csvDir.resolve("org.openrewrite.maven.table.DependenciesInUse.csv");
316+
assertThat(depsCsv).exists();
317+
assertThat(readCsvRows(depsCsv))
318+
.as("Dependency is resolved and written to DependenciesInUse.csv")
319+
.anySatisfy(row -> {
320+
assertThat(row.getField("groupId")).isEqualTo("org.springframework.boot");
321+
assertThat(row.getField("artifactId")).isEqualTo("spring-boot-starter");
322+
assertThat(row.getField("version")).isEqualTo("3.1.2");
323+
});
324+
325+
Path upgradesCsv = findCsv(csvDir, "io.moderne.devcenter.table.UpgradesAndMigrations");
326+
assertThat(readCsvRows(upgradesCsv))
327+
.singleElement()
328+
.satisfies(row -> {
329+
assertThat(row.getField("card")).isEqualTo("Move to Spring Boot 4.0");
330+
assertThat(row.getField("value")).isEqualTo("Major");
331+
assertThat(row.getField("currentMinimumVersion")).isEqualTo("3.1.2");
332+
});
333+
}
334+
335+
private static List<NamedCsvRecord> readCsvRows(Path csv) throws IOException {
336+
try (CsvReader<NamedCsvRecord> reader = CsvReader.builder()
337+
.commentCharacter('#')
338+
.commentStrategy(CommentStrategy.SKIP)
339+
.ofNamedCsvRecord(csv)) {
340+
return reader.stream().toList();
341+
}
342+
}
343+
344+
private static Path findCsv(Path dir, String namePrefix) throws IOException {
345+
try (Stream<Path> files = Files.list(dir)) {
346+
return files
347+
.filter(p -> p.getFileName().toString().startsWith(namePrefix))
348+
.findFirst()
349+
.orElseThrow(() -> new AssertionError(
350+
"No CSV starting with '" + namePrefix + "' in " + dir));
351+
}
352+
}
353+
251354
@DocumentExample
252355
@Test
253356
void devcenterWithMultipleLibraryUpgradeRecipesHasCorrectData() {

0 commit comments

Comments
 (0)