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")); + } +}