From a6c2486d7cede226b5aef0290f6acf1a8601bf43 Mon Sep 17 00:00:00 2001 From: Artem Date: Tue, 22 Sep 2026 17:10:21 +0200 Subject: [PATCH] GH-2002: answer with no metadata when the project build type is unknown DataRepositoryAotMetadataService finds a repository's AOT metadata file by switching on the project's build type. That type is null whenever the build cannot be classified: the invisible project the language server creates for a plain folder has no build file, and neither does a project contributed by an importer that is neither Maven nor Gradle, so ClasspathUtil.createProjectBuild reports a build whose type is null - and switching on null throws. The caller is the Spring Data repository indexer, reached for every query method that carries no @Query, and SpringIndexerJavaAstScanner catches per type declaration. The whole repository interface then contributes nothing at all - no repository bean, none of its query methods - while the rest of the index is built normally, so the index looks healthy and the repositories are simply absent from it. Both switches in the class now read the type through a null-safe helper and answer Optional.empty(), which is what they already do for a type they do not recognise. Adds a test for a build with no type and for a project with no build. Signed-off-by: Artem --- .../DataRepositoryAotMetadataService.java | 29 +++++++++- ...sitoryAotMetadataServiceBuildTypeTest.java | 53 +++++++++++++++++++ 2 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataServiceBuildTypeTest.java diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataService.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataService.java index abf59c8281..dcdfe53f0e 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataService.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataService.java @@ -32,6 +32,7 @@ import org.springframework.ide.vscode.boot.java.BuildCommandProvider; import org.springframework.ide.vscode.commons.java.IClasspathUtil; import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.java.IProjectBuild; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.commons.protocol.java.ProjectBuild; import org.springframework.ide.vscode.commons.util.FileObserver; @@ -136,10 +137,29 @@ public DataRepositoryAotMetadataService(FileObserver fileObserver, JavaProjectFi } } + /** + * The project's build type, or null when it is not known. + *

+ * Not every project has a build this server can classify: the invisible project the language server + * creates for a plain folder has none, and neither does a project contributed by an importer that is + * neither Maven nor Gradle - {@code ClasspathUtil.createProjectBuild} then reports a build whose type + * is null. Switching on that throws, and the callers here are on the indexing path, where an + * exception costs a whole type its symbols. + */ + private static String buildType(IJavaProject project) { + IProjectBuild build = project.getProjectBuild(); + return build == null ? null : build.getType(); + } + public Optional getRepositoryMetadata(IJavaProject project, String repositoryType) { String metadataFilePath = repositoryType.replace('.', '/') + ".json"; - switch (project.getProjectBuild().getType()) { + String buildType = buildType(project); + if (buildType == null) { + return Optional.empty(); + } + + switch (buildType) { case ProjectBuild.MAVEN_PROJECT_TYPE: return IClasspathUtil.getOutputFolders(project.getClasspath()) .map(outputFolder -> outputFolder.getParentFile().toPath().resolve("spring-aot/main/resources/").resolve(metadataFilePath)) @@ -167,7 +187,12 @@ private Optional readMetadataFile(Path filePath) { } Optional regenerateMetadataCommand(IJavaProject jp) { - switch (jp.getProjectBuild().getType()) { + String buildType = buildType(jp); + if (buildType == null) { + return Optional.empty(); + } + + switch (buildType) { case ProjectBuild.MAVEN_PROJECT_TYPE: List goal = new ArrayList<>(); if (!IClasspathUtil.getOutputFolders(jp.getClasspath()).map(f -> f.toPath()).filter(Files::isDirectory).flatMap(d -> { diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataServiceBuildTypeTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataServiceBuildTypeTest.java new file mode 100644 index 0000000000..60b3d293a9 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataServiceBuildTypeTest.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2026 Broadcom + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Broadcom - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.data.test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Optional; + +import org.junit.jupiter.api.Test; +import org.springframework.ide.vscode.boot.java.data.DataRepositoryAotMetadataService; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.java.IProjectBuild; + +/** + * Asking for AOT metadata on a project whose build is neither Maven nor Gradle. + *

+ * The answer has to be "no metadata" rather than an exception: the caller is the Spring Data + * repository indexer, which reaches this service for every query method that carries no + * {@code @Query}, and an exception there is caught per type - so the whole repository interface + * loses its symbols. + * + * @author Artem + */ +public class DataRepositoryAotMetadataServiceBuildTypeTest { + + private final DataRepositoryAotMetadataService service = new DataRepositoryAotMetadataService(null, null, null); + + @Test + void buildWithoutATypeYieldsNoMetadata() { + IJavaProject project = mock(IJavaProject.class); + when(project.getProjectBuild()).thenReturn(IProjectBuild.create(null, null)); + + assertEquals(Optional.empty(), service.getRepositoryMetadata(project, "com.example.UserRepository")); + } + + @Test + void noBuildAtAllYieldsNoMetadata() { + IJavaProject project = mock(IJavaProject.class); + when(project.getProjectBuild()).thenReturn(null); + + assertEquals(Optional.empty(), service.getRepositoryMetadata(project, "com.example.UserRepository")); + } +}