From 26e9d24f4e621b13a53dfd674c4d454bfbc12db0 Mon Sep 17 00:00:00 2001 From: Martin Lippert Date: Fri, 16 May 2025 12:30:24 +0200 Subject: [PATCH] GH-1545: query code lens support added for aot-generated mongodb repositories --- .../ide/vscode/boot/java/Annotations.java | 2 + .../java/data/DataRepositoryAotMetadata.java | 2 +- ...RepositoryAotMetadataCodeLensProvider.java | 9 +- .../data/DataRepositoryAotMetadataQuery.java | 9 +- .../DataRepositoryAotMetadataService.java | 54 +++- ...otMetadataCodeLensProviderMongoDbTest.java | 100 +++++++ ...sitoryAotMetadataCodeLensProviderTest.java | 2 +- .../GenAotQueryMethodImplProviderTest.java | 2 +- .../QueryMethodCodeActionProviderTest.java | 2 +- .../.mvn/wrapper/maven-wrapper.properties | 0 .../mvnw | 0 .../mvnw.cmd | 0 .../pom.xml | 129 ++++----- .../example/springdata/aot/AotJpaApp.java | 0 .../main/java/example/springdata/aot/CLR.java | 0 .../java/example/springdata/aot/Post.java | 0 .../java/example/springdata/aot/User.java | 0 .../springdata/aot/UserProjection.java | 0 .../springdata/aot/UserRepository.java | 0 .../src/main/resources/application.properties | 0 .../.mvn/wrapper/maven-wrapper.properties | 19 ++ .../aot-data-repositories-mongodb/mvnw | 259 ++++++++++++++++++ .../aot-data-repositories-mongodb/mvnw.cmd | 149 ++++++++++ .../aot-data-repositories-mongodb/pom.xml | 99 +++++++ .../main/java/example/springdata/aot/App.java | 30 ++ .../main/java/example/springdata/aot/CLR.java | 114 ++++++++ .../java/example/springdata/aot/Post.java | 56 ++++ .../java/example/springdata/aot/User.java | 105 +++++++ .../springdata/aot/UserProjection.java | 28 ++ .../springdata/aot/UserRepository.java | 59 ++++ .../src/main/resources/application.properties | 1 + .../src/main/resources/logback.xml | 28 ++ 32 files changed, 1171 insertions(+), 87 deletions(-) create mode 100644 headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataCodeLensProviderMongoDbTest.java rename headless-services/spring-boot-language-server/src/test/resources/test-projects/{aot-generation => aot-data-repositories-jpa}/.mvn/wrapper/maven-wrapper.properties (100%) rename headless-services/spring-boot-language-server/src/test/resources/test-projects/{aot-generation => aot-data-repositories-jpa}/mvnw (100%) rename headless-services/spring-boot-language-server/src/test/resources/test-projects/{aot-generation => aot-data-repositories-jpa}/mvnw.cmd (100%) rename headless-services/spring-boot-language-server/src/test/resources/test-projects/{aot-generation => aot-data-repositories-jpa}/pom.xml (63%) rename headless-services/spring-boot-language-server/src/test/resources/test-projects/{aot-generation => aot-data-repositories-jpa}/src/main/java/example/springdata/aot/AotJpaApp.java (100%) rename headless-services/spring-boot-language-server/src/test/resources/test-projects/{aot-generation => aot-data-repositories-jpa}/src/main/java/example/springdata/aot/CLR.java (100%) rename headless-services/spring-boot-language-server/src/test/resources/test-projects/{aot-generation => aot-data-repositories-jpa}/src/main/java/example/springdata/aot/Post.java (100%) rename headless-services/spring-boot-language-server/src/test/resources/test-projects/{aot-generation => aot-data-repositories-jpa}/src/main/java/example/springdata/aot/User.java (100%) rename headless-services/spring-boot-language-server/src/test/resources/test-projects/{aot-generation => aot-data-repositories-jpa}/src/main/java/example/springdata/aot/UserProjection.java (100%) rename headless-services/spring-boot-language-server/src/test/resources/test-projects/{aot-generation => aot-data-repositories-jpa}/src/main/java/example/springdata/aot/UserRepository.java (100%) rename headless-services/spring-boot-language-server/src/test/resources/test-projects/{aot-generation => aot-data-repositories-jpa}/src/main/resources/application.properties (100%) create mode 100644 headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/.mvn/wrapper/maven-wrapper.properties create mode 100755 headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/mvnw create mode 100644 headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/mvnw.cmd create mode 100644 headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/pom.xml create mode 100644 headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/src/main/java/example/springdata/aot/App.java create mode 100644 headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/src/main/java/example/springdata/aot/CLR.java create mode 100644 headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/src/main/java/example/springdata/aot/Post.java create mode 100644 headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/src/main/java/example/springdata/aot/User.java create mode 100644 headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/src/main/java/example/springdata/aot/UserProjection.java create mode 100644 headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/src/main/java/example/springdata/aot/UserRepository.java create mode 100644 headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/src/main/resources/application.properties create mode 100644 headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/src/main/resources/logback.xml diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/Annotations.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/Annotations.java index ca07edcb0..381eceb32 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/Annotations.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/Annotations.java @@ -49,6 +49,8 @@ public class Annotations { public static final String DATA_QUERY_META_ANNOTATION = "org.springframework.data.annotation.QueryAnnotation"; public static final String DATA_JPA_QUERY = "org.springframework.data.jpa.repository.Query"; public static final String DATA_JPA_NATIVE_QUERY = "org.springframework.data.jpa.repository.NativeQuery"; + public static final String DATA_MONGODB_QUERY = "org.springframework.data.mongodb.repository.Query"; + public static final String AUTOWIRED = "org.springframework.beans.factory.annotation.Autowired"; public static final String QUALIFIER = "org.springframework.beans.factory.annotation.Qualifier"; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadata.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadata.java index fd10191a2..25dd860dd 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadata.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadata.java @@ -10,5 +10,5 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.data; -public record DataRepositoryAotMetadata (String name, String type, DataRepositoryAotMetadataMethod[] methods) { +public record DataRepositoryAotMetadata (String name, String type, String module, DataRepositoryAotMetadataMethod[] methods) { } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataCodeLensProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataCodeLensProvider.java index ebdd91200..465bcc6a9 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataCodeLensProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataCodeLensProvider.java @@ -114,14 +114,19 @@ public class DataRepositoryAotMetadataCodeLensProvider implements CodeLensProvid private List createCodeLenses(MethodDeclaration node, TextDocument document, String queryStatement) { List codeLenses = new ArrayList<>(2); + try { IMethodBinding mb = node.resolveBinding(); Position startPos = document.toPosition(node.getStartPosition()); Position endPos = document.toPosition(node.getName().getStartPosition() + node.getName().getLength()); Range range = new Range(startPos, endPos); AnnotationHierarchies hierarchyAnnot = AnnotationHierarchies.get(node); + if (mb != null && hierarchyAnnot != null) { - boolean isQueryAnnotated = hierarchyAnnot.isAnnotatedWith(mb, Annotations.DATA_JPA_QUERY); + + boolean isQueryAnnotated = hierarchyAnnot.isAnnotatedWith(mb, Annotations.DATA_JPA_QUERY) + || hierarchyAnnot.isAnnotatedWith(mb, Annotations.DATA_MONGODB_QUERY); + if (!isQueryAnnotated) { codeLenses.add(new CodeLens(range, refactorings.createFixCommand(COVERT_TO_QUERY_LABEL, createFixDescriptor(mb, document.getUri(), queryStatement)), null)); } @@ -148,7 +153,7 @@ public class DataRepositoryAotMetadataCodeLensProvider implements CodeLensProvid } static FixDescriptor createFixDescriptor(IMethodBinding mb, String docUri, String queryStatement) { - return new FixDescriptor(AddAnnotationOverMethod.class.getName(), List.of(docUri), "Convert into `@Query`") + return new FixDescriptor(AddAnnotationOverMethod.class.getName(), List.of(docUri), "Turn into `@Query`") .withRecipeScope(RecipeScope.FILE) .withParameters(Map.of("annotationType", Annotations.DATA_JPA_QUERY, "method", "%s %s(%s)".formatted(mb.getDeclaringClass().getQualifiedName(), mb.getName(), diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataQuery.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataQuery.java index a0c876a68..e96f60a1c 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataQuery.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataQuery.java @@ -10,6 +10,11 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.data; -public record DataRepositoryAotMetadataQuery(String query) { - +/** + * Details about the AOT generated query. + * + * query: For JPA-based repositories, this field contains the generated SQL query statememt for the query method + * filter, sort, projection, pipeline: Query details for MongoDB-based repository query methods + */ +public record DataRepositoryAotMetadataQuery(String query, String filter, String sort, String projection, String pipeline, String fields) { } 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 fb889427a..677faa53a 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 @@ -13,6 +13,8 @@ package org.springframework.ide.vscode.boot.java.data; import java.io.File; import java.io.FileReader; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; import org.eclipse.jdt.core.dom.IMethodBinding; @@ -22,6 +24,7 @@ import org.springframework.ide.vscode.commons.java.IClasspathUtil; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.java.parser.JLRMethodParser; import org.springframework.ide.vscode.commons.java.parser.JLRMethodParser.JLRMethod; +import org.springframework.util.StringUtils; import com.google.gson.Gson; @@ -67,7 +70,56 @@ public class DataRepositoryAotMetadataService { public String getQueryStatement(DataRepositoryAotMetadata metadata, IMethodBinding method) { DataRepositoryAotMetadataMethod methodMetadata = findMethod(metadata, method); - return methodMetadata != null ? methodMetadata.query().query() : null; + + if (methodMetadata != null) { + if (metadata.module() != null && metadata.module().toUpperCase().equals("JPA")) { + return getJpaQueryStatement(methodMetadata); + } + else if (metadata.module() != null && metadata.module().toUpperCase().equals("MONGODB")) { + return getMongoDbQueryStatement(methodMetadata); + } + } + + return null; + } + + private String getMongoDbQueryStatement(DataRepositoryAotMetadataMethod methodMetadata) { + List parts = new ArrayList<>(); + + if (methodMetadata.query().filter() != null) { + if (!StringUtils.hasText(methodMetadata.query().sort()) + && !StringUtils.hasText(methodMetadata.query().fields()) + && !StringUtils.hasText(methodMetadata.query().projection()) + && !StringUtils.hasText(methodMetadata.query().pipeline())) { + + parts.add(methodMetadata.query().filter()); + } + else { + parts.add("filter = \"" + methodMetadata.query().filter() + "\""); + } + } + + if (methodMetadata.query().fields() != null) { + parts.add("fields = \"" + methodMetadata.query().fields() + "\""); + } + + if (methodMetadata.query().sort() != null) { + parts.add("sort = \"" + methodMetadata.query().sort() + "\""); + } + + if (methodMetadata.query().projection() != null) { + parts.add("projection = \"" + methodMetadata.query().projection() + "\""); + } + + if (methodMetadata.query().pipeline() != null) { + parts.add("pipeline = \"" + methodMetadata.query().pipeline() + "\""); + } + + return String.join(", ", parts); + } + + private String getJpaQueryStatement(DataRepositoryAotMetadataMethod methodMetadata) { + return methodMetadata.query().query(); } private DataRepositoryAotMetadataMethod findMethod(DataRepositoryAotMetadata metadata, IMethodBinding method) { diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataCodeLensProviderMongoDbTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataCodeLensProviderMongoDbTest.java new file mode 100644 index 000000000..ab9884d88 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataCodeLensProviderMongoDbTest.java @@ -0,0 +1,100 @@ +/******************************************************************************* + * Copyright (c) 2025 Broadcom, Inc. + * 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, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.data.test; + +import static org.junit.Assert.assertEquals; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.lsp4j.CodeLens; +import org.eclipse.lsp4j.TextDocumentIdentifier; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Import; +import org.springframework.ide.vscode.boot.app.SpringSymbolIndex; +import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest; +import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; +import org.springframework.ide.vscode.commons.util.text.LanguageId; +import org.springframework.ide.vscode.languageserver.testharness.Editor; +import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness; +import org.springframework.ide.vscode.project.harness.ProjectsHarness; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@ExtendWith(SpringExtension.class) +@BootLanguageServerTest +@Import(SymbolProviderTestConf.class) +public class DataRepositoryAotMetadataCodeLensProviderMongoDbTest { + + @Autowired private BootLanguageServerHarness harness; + @Autowired private JavaProjectFinder projectFinder; + @Autowired private SpringSymbolIndex indexer; + + private IJavaProject testProject; + + @BeforeEach + public void setup() throws Exception { + testProject = ProjectsHarness.INSTANCE.mavenProject("aot-data-repositories-mongodb"); + harness.useProject(testProject); + harness.intialize(null); + + // trigger project creation + projectFinder.find(new TextDocumentIdentifier(testProject.getLocationUri().toASCIIString())).get(); + + CompletableFuture initProject = indexer.waitOperation(); + initProject.get(5, TimeUnit.SECONDS); + } + + @Test + void codeLensOverMethodWithJustThePlainFilter() throws Exception { + Path filePath = Paths.get(testProject.getLocationUri()) + .resolve("src/main/java/example/springdata/aot/UserRepository.java"); + Editor editor = harness.newEditor(LanguageId.JAVA, new String(Files.readAllBytes(filePath), StandardCharsets.UTF_8), filePath.toUri().toASCIIString()); + + List cls = editor.getCodeLenses("findUserByUsername", 1); + assertEquals("Turn into @Query", cls.get(0).getCommand().getTitle()); + assertEquals("Implementation", cls.get(1).getCommand().getTitle()); + assertEquals("{'username':?0}", cls.get(2).getCommand().getTitle()); + } + + @Test + void codeLensOverMethodWithMultipleElements() throws Exception { + Path filePath = Paths.get(testProject.getLocationUri()) + .resolve("src/main/java/example/springdata/aot/UserRepository.java"); + Editor editor = harness.newEditor(LanguageId.JAVA, new String(Files.readAllBytes(filePath), StandardCharsets.UTF_8), filePath.toUri().toASCIIString()); + + List cls = editor.getCodeLenses("findUserByLastnameLikeOrderByFirstname", 1); + assertEquals("Turn into @Query", cls.get(0).getCommand().getTitle()); + assertEquals("Implementation", cls.get(1).getCommand().getTitle()); + assertEquals("filter = \"{'lastname':{'$regex':/\\Q?0\\E/}}\"" + ", " + "sort = \"{'firstname':{'$numberInt':'1'}}\"", cls.get(2).getCommand().getTitle()); + } + + @Test + void noCodeLensOverMethodWithQueryAnnotation() throws Exception { + Path filePath = Paths.get(testProject.getLocationUri()) + .resolve("src/main/java/example/springdata/aot/UserRepository.java"); + Editor editor = harness.newEditor(LanguageId.JAVA, new String(Files.readAllBytes(filePath), StandardCharsets.UTF_8), filePath.toUri().toASCIIString()); + + List cls = editor.getCodeLenses("usersWithUsernamesStartingWith", 1); + assertEquals(1, cls.size()); + assertEquals("Implementation", cls.get(0).getCommand().getTitle()); + assertEquals(1, cls.get(0).getCommand().getArguments().size()); + } +} diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataCodeLensProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataCodeLensProviderTest.java index c440d234f..7ca2e7bf8 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataCodeLensProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataCodeLensProviderTest.java @@ -51,7 +51,7 @@ public class DataRepositoryAotMetadataCodeLensProviderTest { @BeforeEach public void setup() throws Exception { - testProject = ProjectsHarness.INSTANCE.mavenProject("aot-generation"); + testProject = ProjectsHarness.INSTANCE.mavenProject("aot-data-repositories-jpa"); harness.useProject(testProject); harness.intialize(null); diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/GenAotQueryMethodImplProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/GenAotQueryMethodImplProviderTest.java index 2012e084d..cf7a84ec5 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/GenAotQueryMethodImplProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/GenAotQueryMethodImplProviderTest.java @@ -51,7 +51,7 @@ public class GenAotQueryMethodImplProviderTest { @BeforeEach public void setup() throws Exception { - testProject = ProjectsHarness.INSTANCE.mavenProject("aot-generation"); + testProject = ProjectsHarness.INSTANCE.mavenProject("aot-data-repositories-jpa"); harness.useProject(testProject); harness.intialize(null); diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/QueryMethodCodeActionProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/QueryMethodCodeActionProviderTest.java index e01a4d3c5..71bcf80c7 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/QueryMethodCodeActionProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/QueryMethodCodeActionProviderTest.java @@ -58,7 +58,7 @@ public class QueryMethodCodeActionProviderTest { @BeforeEach public void setup() throws Exception { - testProject = ProjectsHarness.INSTANCE.mavenProject("aot-generation"); + testProject = ProjectsHarness.INSTANCE.mavenProject("aot-data-repositories-jpa"); harness.useProject(testProject); harness.intialize(null); diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-generation/.mvn/wrapper/maven-wrapper.properties b/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-jpa/.mvn/wrapper/maven-wrapper.properties similarity index 100% rename from headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-generation/.mvn/wrapper/maven-wrapper.properties rename to headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-jpa/.mvn/wrapper/maven-wrapper.properties diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-generation/mvnw b/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-jpa/mvnw similarity index 100% rename from headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-generation/mvnw rename to headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-jpa/mvnw diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-generation/mvnw.cmd b/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-jpa/mvnw.cmd similarity index 100% rename from headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-generation/mvnw.cmd rename to headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-jpa/mvnw.cmd diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-generation/pom.xml b/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-jpa/pom.xml similarity index 63% rename from headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-generation/pom.xml rename to headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-jpa/pom.xml index 63dc9d1bc..3ec316640 100644 --- a/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-generation/pom.xml +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-jpa/pom.xml @@ -1,20 +1,21 @@ + xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 org.springframework.boot spring-boot-starter-parent - 3.4.1 + 4.0.0-SNAPSHOT + org.example - aot-generation + aot-data-repositories-jpa - UTF-8 - 7.0.0.Beta5 + 21 + 2025.1.0-SNAPSHOT @@ -24,32 +25,9 @@ - org.springframework.data - spring-data-commons - 4.0.0-SNAPSHOT - - - - org.springframework.data - spring-data-jpa - 4.0.0-SNAPSHOT - - - - jakarta.persistence - jakarta.persistence-api - 3.2.0 - - - - org.hsqldb - hsqldb - - - - org.jspecify - jspecify - 1.0.0 + org.springframework.boot + spring-boot-starter-test + test @@ -64,55 +42,12 @@ 5.1.0 jakarta + - - - - central - Maven Central - https://repo1.maven.org/maven2/ - - false - - - - spring-milestone - https://repo.spring.io/milestone - - false - - - - spring-snapshot - https://repo.spring.io/snapshot - - false - - - - - - - central - Maven Central - https://repo1.maven.org/maven2/ - - false - - - - spring-milestone - https://repo.spring.io/milestone/ - - false - - - - - + + Recommended: Some IDE's might require this configuration to include generated sources for IDE usage target/generated-test-sources target/generated-sources +--> org.springframework.boot spring-boot-maven-plugin @@ -158,4 +94,41 @@ + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + false + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + false + + + + diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-generation/src/main/java/example/springdata/aot/AotJpaApp.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-jpa/src/main/java/example/springdata/aot/AotJpaApp.java similarity index 100% rename from headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-generation/src/main/java/example/springdata/aot/AotJpaApp.java rename to headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-jpa/src/main/java/example/springdata/aot/AotJpaApp.java diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-generation/src/main/java/example/springdata/aot/CLR.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-jpa/src/main/java/example/springdata/aot/CLR.java similarity index 100% rename from headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-generation/src/main/java/example/springdata/aot/CLR.java rename to headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-jpa/src/main/java/example/springdata/aot/CLR.java diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-generation/src/main/java/example/springdata/aot/Post.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-jpa/src/main/java/example/springdata/aot/Post.java similarity index 100% rename from headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-generation/src/main/java/example/springdata/aot/Post.java rename to headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-jpa/src/main/java/example/springdata/aot/Post.java diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-generation/src/main/java/example/springdata/aot/User.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-jpa/src/main/java/example/springdata/aot/User.java similarity index 100% rename from headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-generation/src/main/java/example/springdata/aot/User.java rename to headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-jpa/src/main/java/example/springdata/aot/User.java diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-generation/src/main/java/example/springdata/aot/UserProjection.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-jpa/src/main/java/example/springdata/aot/UserProjection.java similarity index 100% rename from headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-generation/src/main/java/example/springdata/aot/UserProjection.java rename to headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-jpa/src/main/java/example/springdata/aot/UserProjection.java diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-generation/src/main/java/example/springdata/aot/UserRepository.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-jpa/src/main/java/example/springdata/aot/UserRepository.java similarity index 100% rename from headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-generation/src/main/java/example/springdata/aot/UserRepository.java rename to headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-jpa/src/main/java/example/springdata/aot/UserRepository.java diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-generation/src/main/resources/application.properties b/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-jpa/src/main/resources/application.properties similarity index 100% rename from headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-generation/src/main/resources/application.properties rename to headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-jpa/src/main/resources/application.properties diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/.mvn/wrapper/maven-wrapper.properties b/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 000000000..2076438e0 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.3/apache-maven-3.9.3-bin.zip diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/mvnw b/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/mvnw new file mode 100755 index 000000000..d7c358e5a --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/mvnw.cmd b/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/mvnw.cmd new file mode 100644 index 000000000..6f779cff2 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/pom.xml b/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/pom.xml new file mode 100644 index 000000000..3be815d37 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/pom.xml @@ -0,0 +1,99 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 4.0.0-SNAPSHOT + + + + org.example + aot-data-repositories-mongodb + + + 21 + + + + + org.springframework.boot + spring-boot-starter-data-mongodb + + + + org.springframework.data + spring-data-mongodb + 5.0.0-SNAPSHOT + + + + org.springframework.data + spring-data-commons + 4.0.0-SNAPSHOT + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + process-aot + + process-aot + + + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + false + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + false + + + + + diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/src/main/java/example/springdata/aot/App.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/src/main/java/example/springdata/aot/App.java new file mode 100644 index 000000000..1f3c478af --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/src/main/java/example/springdata/aot/App.java @@ -0,0 +1,30 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.aot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author Christoph Strobl + */ +@SpringBootApplication +public class App { + + public static void main(String[] args) { + SpringApplication.run(App.class, args); + } +} diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/src/main/java/example/springdata/aot/CLR.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/src/main/java/example/springdata/aot/CLR.java new file mode 100644 index 000000000..1ccacfaa2 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/src/main/java/example/springdata/aot/CLR.java @@ -0,0 +1,114 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.aot; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Slice; +import org.springframework.stereotype.Component; + +/** + * @author Christoph Strobl + * @since 2025/01 + */ +@Component +public class CLR implements CommandLineRunner { + + @Autowired UserRepository repository; + + @Override + public void run(String... args) throws Exception { + + User luke = new User("id-1", "luke"); + luke.setFirstname("Luke"); + luke.setLastname("Skywalker"); + luke.setPosts(List.of(new Post("I have a bad feeling about this."))); + + User leia = new User("id-2", "leia"); + leia.setFirstname("Leia"); + leia.setLastname("Organa"); + + User han = new User("id-3", "han"); + han.setFirstname("Han"); + han.setLastname("Solo"); + han.setPosts(List.of(new Post("It's the ship that made the Kessel Run in less than 12 Parsecs."))); + + User chewbacca = new User("id-4", "chewbacca"); + User yoda = new User("id-5", "yoda"); + yoda.setPosts(List.of(new Post("Do. Or do not. There is no try."), new Post("Decide you must, how to serve them best. If you leave now, help them you could; but you would destroy all for which they have fought, and suffered."))); + + User vader = new User("id-6", "vader"); + vader.setFirstname("Anakin"); + vader.setLastname("Skywalker"); + vader.setPosts(List.of(new Post("I am your father"))); + + User kylo = new User("id-7", "kylo"); + kylo.setFirstname("Ben"); + kylo.setLastname("Solo"); + + repository.saveAll(List.of(luke, leia, han, chewbacca, yoda, vader, kylo)); + + System.out.println("------- annotated multi -------"); + System.out.println(repository.usersWithUsernamesStartingWith("l")); + + System.out.println("------- derived single -------"); + System.out.println(repository.findUserByUsername("yoda")); + + System.out.println("------- derived nested.path -------"); + System.out.println(repository.findUserByPostsMessageLike("father")); + + System.out.println("------- derived optional -------"); + System.out.println(repository.findOptionalUserByUsername("yoda")); + + System.out.println("------- derived count -------"); + Long count = repository.countUsersByLastnameLike("Sky"); + System.out.println("user count " + count); + + System.out.println("------- derived exists -------"); + Boolean exists = repository.existsByUsername("vader"); + System.out.println("user exists " + exists); + + System.out.println("------- derived multi -------"); + System.out.println(repository.findUserByLastnameLike("Sky")); + + System.out.println("------- derived sorted -------"); + System.out.println(repository.findUserByLastnameLikeOrderByFirstname("Sky")); + + System.out.println("------- derived page -------"); + Page page0 = repository.findUserByLastnameStartingWith("S", PageRequest.of(0, 2)); + System.out.println("page0: " + page0); + System.out.println("page0.content: " + page0.getContent()); + + Page page1 = repository.findUserByLastnameStartingWith("S", PageRequest.of(1, 2)); + System.out.println("page1: " + page1); + System.out.println("page1.content: " + page1.getContent()); + + System.out.println("------- derived slice -------"); + Slice slice0 = repository.findUserByUsernameAfter("luke", PageRequest.of(0, 2)); + System.out.println("slice0: " + slice0); + System.out.println("slice0.content: " + slice0.getContent()); + + System.out.println("------- derived top -------"); + System.out.println(repository.findTop2UsersByLastnameLike("S")); + + System.out.println("------- derived with fields -------"); + System.out.println(repository.findJustUsernameBy()); + } +} diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/src/main/java/example/springdata/aot/Post.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/src/main/java/example/springdata/aot/Post.java new file mode 100644 index 000000000..10f5068ba --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/src/main/java/example/springdata/aot/Post.java @@ -0,0 +1,56 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.aot; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Random; + +/** + * @author Christoph Strobl + * @since 2025/01 + */ +public class Post { + + private String message; + private Instant date; + + public Post(String message) { + this.message = message; + this.date = Instant.now().minus(new Random().nextLong(100), ChronoUnit.MINUTES); + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Instant getDate() { + return date; + } + + public void setDate(Instant date) { + this.date = date; + } + + @Override + public String toString() { + return message; + } +} diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/src/main/java/example/springdata/aot/User.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/src/main/java/example/springdata/aot/User.java new file mode 100644 index 000000000..56317490e --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/src/main/java/example/springdata/aot/User.java @@ -0,0 +1,105 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.aot; + +import java.time.Instant; +import java.util.List; + +import org.springframework.data.mongodb.core.mapping.Field; + +/** + * @author Christoph Strobl + * @since 2025/01 + */ +public class User { + + private final String id; + private final String username; + + @Field("first_name") String firstname; + @Field("last_name") String lastname; + + private List posts; + + Instant registrationDate; + Instant lastSeen; + + public User(String id, String username) { + this.id = id; + this.username = username; + } + + public String getId() { + return id; + } + + public String getUsername() { + return username; + } + + public String getFirstname() { + return firstname; + } + + public void setFirstname(String firstname) { + this.firstname = firstname; + } + + public String getLastname() { + return lastname; + } + + public void setLastname(String lastname) { + this.lastname = lastname; + } + + public Instant getRegistrationDate() { + return registrationDate; + } + + public void setRegistrationDate(Instant registrationDate) { + this.registrationDate = registrationDate; + } + + public Instant getLastSeen() { + return lastSeen; + } + + public void setLastSeen(Instant lastSeen) { + this.lastSeen = lastSeen; + } + + public List getPosts() { + return posts; + } + + public void setPosts(List posts) { + this.posts = posts; + } + + @Override + public String toString() { + return "User{" + + "id='" + id + '\'' + + ", username='" + username + '\'' + + ", firstname='" + firstname + '\'' + + ", lastname='" + lastname + '\'' + + ", registrationDate=" + registrationDate + + ", lastSeen=" + lastSeen + + ", posts=" + posts + + '}'; + } +} diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/src/main/java/example/springdata/aot/UserProjection.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/src/main/java/example/springdata/aot/UserProjection.java new file mode 100644 index 000000000..d243ba3a8 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/src/main/java/example/springdata/aot/UserProjection.java @@ -0,0 +1,28 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.aot; + +import java.time.Instant; + +/** + * @author Christoph Strobl + * @since 2025/01 + */ +public interface UserProjection { + + String getUsername(); + Instant getRegistrationDate(); +} diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/src/main/java/example/springdata/aot/UserRepository.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/src/main/java/example/springdata/aot/UserRepository.java new file mode 100644 index 000000000..6a935551e --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/src/main/java/example/springdata/aot/UserRepository.java @@ -0,0 +1,59 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.aot; + +import java.util.List; +import java.util.Optional; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.mongodb.repository.Query; +import org.springframework.data.repository.CrudRepository; + +/** + * @author Christoph Strobl + * @since 2025/01 + */ +public interface UserRepository extends CrudRepository { + + User findUserByUsername(String username); + + Optional findOptionalUserByUsername(String username); + + Long countUsersByLastnameLike(String lastname); + + Boolean existsByUsername(String username); + + List findUserByLastnameLike(String lastname); + + List findUserByPostsMessageLike(String part); + + List findUserByLastnameLikeOrderByFirstname(String lastname); + + List findTop2UsersByLastnameLike(String lastname); + + Slice findUserByUsernameAfter(String username, Pageable pageable); + + Page findUserByLastnameStartingWith(String lastname, Pageable page); + + @Query("{ 'username' : { $regex: '?0.*', $options: 'i' } }") + List usersWithUsernamesStartingWith(String username); + + @Query(fields = "{ 'username' : 1 }", sort = "{ 'username' : -1 }") + List findJustUsernameBy(); + +} diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/src/main/resources/application.properties b/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/src/main/resources/application.properties new file mode 100644 index 000000000..fb68fc02e --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.aot.repositories.enabled=true diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/src/main/resources/logback.xml b/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/src/main/resources/logback.xml new file mode 100644 index 000000000..1be6becb6 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-mongodb/src/main/resources/logback.xml @@ -0,0 +1,28 @@ + + + + + + %d %5p %40.40c:%4L - %m%n + + + + + + + + + + + + + + + + + + + +