Skip to content

Commit 26e9d24

Browse files
committed
GH-1545: query code lens support added for aot-generated mongodb repositories
1 parent 3ae66ce commit 26e9d24

File tree

32 files changed

+1171
-87
lines changed

32 files changed

+1171
-87
lines changed

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/Annotations.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ public class Annotations {
4949
public static final String DATA_QUERY_META_ANNOTATION = "org.springframework.data.annotation.QueryAnnotation";
5050
public static final String DATA_JPA_QUERY = "org.springframework.data.jpa.repository.Query";
5151
public static final String DATA_JPA_NATIVE_QUERY = "org.springframework.data.jpa.repository.NativeQuery";
52+
public static final String DATA_MONGODB_QUERY = "org.springframework.data.mongodb.repository.Query";
53+
5254

5355
public static final String AUTOWIRED = "org.springframework.beans.factory.annotation.Autowired";
5456
public static final String QUALIFIER = "org.springframework.beans.factory.annotation.Qualifier";

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadata.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@
1010
*******************************************************************************/
1111
package org.springframework.ide.vscode.boot.java.data;
1212

13-
public record DataRepositoryAotMetadata (String name, String type, DataRepositoryAotMetadataMethod[] methods) {
13+
public record DataRepositoryAotMetadata (String name, String type, String module, DataRepositoryAotMetadataMethod[] methods) {
1414
}

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataCodeLensProvider.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,14 +114,19 @@ protected void provideCodeLens(CancelChecker cancelToken, MethodDeclaration node
114114

115115
private List<CodeLens> createCodeLenses(MethodDeclaration node, TextDocument document, String queryStatement) {
116116
List<CodeLens> codeLenses = new ArrayList<>(2);
117+
117118
try {
118119
IMethodBinding mb = node.resolveBinding();
119120
Position startPos = document.toPosition(node.getStartPosition());
120121
Position endPos = document.toPosition(node.getName().getStartPosition() + node.getName().getLength());
121122
Range range = new Range(startPos, endPos);
122123
AnnotationHierarchies hierarchyAnnot = AnnotationHierarchies.get(node);
124+
123125
if (mb != null && hierarchyAnnot != null) {
124-
boolean isQueryAnnotated = hierarchyAnnot.isAnnotatedWith(mb, Annotations.DATA_JPA_QUERY);
126+
127+
boolean isQueryAnnotated = hierarchyAnnot.isAnnotatedWith(mb, Annotations.DATA_JPA_QUERY)
128+
|| hierarchyAnnot.isAnnotatedWith(mb, Annotations.DATA_MONGODB_QUERY);
129+
125130
if (!isQueryAnnotated) {
126131
codeLenses.add(new CodeLens(range, refactorings.createFixCommand(COVERT_TO_QUERY_LABEL, createFixDescriptor(mb, document.getUri(), queryStatement)), null));
127132
}
@@ -148,7 +153,7 @@ private List<CodeLens> createCodeLenses(MethodDeclaration node, TextDocument doc
148153
}
149154

150155
static FixDescriptor createFixDescriptor(IMethodBinding mb, String docUri, String queryStatement) {
151-
return new FixDescriptor(AddAnnotationOverMethod.class.getName(), List.of(docUri), "Convert into `@Query`")
156+
return new FixDescriptor(AddAnnotationOverMethod.class.getName(), List.of(docUri), "Turn into `@Query`")
152157
.withRecipeScope(RecipeScope.FILE)
153158
.withParameters(Map.of("annotationType", Annotations.DATA_JPA_QUERY, "method",
154159
"%s %s(%s)".formatted(mb.getDeclaringClass().getQualifiedName(), mb.getName(),

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataQuery.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@
1010
*******************************************************************************/
1111
package org.springframework.ide.vscode.boot.java.data;
1212

13-
public record DataRepositoryAotMetadataQuery(String query) {
14-
13+
/**
14+
* Details about the AOT generated query.
15+
*
16+
* query: For JPA-based repositories, this field contains the generated SQL query statememt for the query method
17+
* filter, sort, projection, pipeline: Query details for MongoDB-based repository query methods
18+
*/
19+
public record DataRepositoryAotMetadataQuery(String query, String filter, String sort, String projection, String pipeline, String fields) {
1520
}

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataService.java

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import java.io.File;
1414
import java.io.FileReader;
1515
import java.io.IOException;
16+
import java.util.ArrayList;
17+
import java.util.List;
1618
import java.util.Optional;
1719

1820
import org.eclipse.jdt.core.dom.IMethodBinding;
@@ -22,6 +24,7 @@
2224
import org.springframework.ide.vscode.commons.java.IJavaProject;
2325
import org.springframework.ide.vscode.commons.java.parser.JLRMethodParser;
2426
import org.springframework.ide.vscode.commons.java.parser.JLRMethodParser.JLRMethod;
27+
import org.springframework.util.StringUtils;
2528

2629
import com.google.gson.Gson;
2730

@@ -67,7 +70,56 @@ private DataRepositoryAotMetadata readMetadataFile(File file) {
6770

6871
public String getQueryStatement(DataRepositoryAotMetadata metadata, IMethodBinding method) {
6972
DataRepositoryAotMetadataMethod methodMetadata = findMethod(metadata, method);
70-
return methodMetadata != null ? methodMetadata.query().query() : null;
73+
74+
if (methodMetadata != null) {
75+
if (metadata.module() != null && metadata.module().toUpperCase().equals("JPA")) {
76+
return getJpaQueryStatement(methodMetadata);
77+
}
78+
else if (metadata.module() != null && metadata.module().toUpperCase().equals("MONGODB")) {
79+
return getMongoDbQueryStatement(methodMetadata);
80+
}
81+
}
82+
83+
return null;
84+
}
85+
86+
private String getMongoDbQueryStatement(DataRepositoryAotMetadataMethod methodMetadata) {
87+
List<String> parts = new ArrayList<>();
88+
89+
if (methodMetadata.query().filter() != null) {
90+
if (!StringUtils.hasText(methodMetadata.query().sort())
91+
&& !StringUtils.hasText(methodMetadata.query().fields())
92+
&& !StringUtils.hasText(methodMetadata.query().projection())
93+
&& !StringUtils.hasText(methodMetadata.query().pipeline())) {
94+
95+
parts.add(methodMetadata.query().filter());
96+
}
97+
else {
98+
parts.add("filter = \"" + methodMetadata.query().filter() + "\"");
99+
}
100+
}
101+
102+
if (methodMetadata.query().fields() != null) {
103+
parts.add("fields = \"" + methodMetadata.query().fields() + "\"");
104+
}
105+
106+
if (methodMetadata.query().sort() != null) {
107+
parts.add("sort = \"" + methodMetadata.query().sort() + "\"");
108+
}
109+
110+
if (methodMetadata.query().projection() != null) {
111+
parts.add("projection = \"" + methodMetadata.query().projection() + "\"");
112+
}
113+
114+
if (methodMetadata.query().pipeline() != null) {
115+
parts.add("pipeline = \"" + methodMetadata.query().pipeline() + "\"");
116+
}
117+
118+
return String.join(", ", parts);
119+
}
120+
121+
private String getJpaQueryStatement(DataRepositoryAotMetadataMethod methodMetadata) {
122+
return methodMetadata.query().query();
71123
}
72124

73125
private DataRepositoryAotMetadataMethod findMethod(DataRepositoryAotMetadata metadata, IMethodBinding method) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Broadcom, Inc.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Broadcom, Inc. - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.ide.vscode.boot.java.data.test;
12+
13+
import static org.junit.Assert.assertEquals;
14+
15+
import java.nio.charset.StandardCharsets;
16+
import java.nio.file.Files;
17+
import java.nio.file.Path;
18+
import java.nio.file.Paths;
19+
import java.util.List;
20+
import java.util.concurrent.CompletableFuture;
21+
import java.util.concurrent.TimeUnit;
22+
23+
import org.eclipse.lsp4j.CodeLens;
24+
import org.eclipse.lsp4j.TextDocumentIdentifier;
25+
import org.junit.jupiter.api.BeforeEach;
26+
import org.junit.jupiter.api.Test;
27+
import org.junit.jupiter.api.extension.ExtendWith;
28+
import org.springframework.beans.factory.annotation.Autowired;
29+
import org.springframework.context.annotation.Import;
30+
import org.springframework.ide.vscode.boot.app.SpringSymbolIndex;
31+
import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest;
32+
import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf;
33+
import org.springframework.ide.vscode.commons.java.IJavaProject;
34+
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
35+
import org.springframework.ide.vscode.commons.util.text.LanguageId;
36+
import org.springframework.ide.vscode.languageserver.testharness.Editor;
37+
import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness;
38+
import org.springframework.ide.vscode.project.harness.ProjectsHarness;
39+
import org.springframework.test.context.junit.jupiter.SpringExtension;
40+
41+
@ExtendWith(SpringExtension.class)
42+
@BootLanguageServerTest
43+
@Import(SymbolProviderTestConf.class)
44+
public class DataRepositoryAotMetadataCodeLensProviderMongoDbTest {
45+
46+
@Autowired private BootLanguageServerHarness harness;
47+
@Autowired private JavaProjectFinder projectFinder;
48+
@Autowired private SpringSymbolIndex indexer;
49+
50+
private IJavaProject testProject;
51+
52+
@BeforeEach
53+
public void setup() throws Exception {
54+
testProject = ProjectsHarness.INSTANCE.mavenProject("aot-data-repositories-mongodb");
55+
harness.useProject(testProject);
56+
harness.intialize(null);
57+
58+
// trigger project creation
59+
projectFinder.find(new TextDocumentIdentifier(testProject.getLocationUri().toASCIIString())).get();
60+
61+
CompletableFuture<Void> initProject = indexer.waitOperation();
62+
initProject.get(5, TimeUnit.SECONDS);
63+
}
64+
65+
@Test
66+
void codeLensOverMethodWithJustThePlainFilter() throws Exception {
67+
Path filePath = Paths.get(testProject.getLocationUri())
68+
.resolve("src/main/java/example/springdata/aot/UserRepository.java");
69+
Editor editor = harness.newEditor(LanguageId.JAVA, new String(Files.readAllBytes(filePath), StandardCharsets.UTF_8), filePath.toUri().toASCIIString());
70+
71+
List<CodeLens> cls = editor.getCodeLenses("findUserByUsername", 1);
72+
assertEquals("Turn into @Query", cls.get(0).getCommand().getTitle());
73+
assertEquals("Implementation", cls.get(1).getCommand().getTitle());
74+
assertEquals("{'username':?0}", cls.get(2).getCommand().getTitle());
75+
}
76+
77+
@Test
78+
void codeLensOverMethodWithMultipleElements() throws Exception {
79+
Path filePath = Paths.get(testProject.getLocationUri())
80+
.resolve("src/main/java/example/springdata/aot/UserRepository.java");
81+
Editor editor = harness.newEditor(LanguageId.JAVA, new String(Files.readAllBytes(filePath), StandardCharsets.UTF_8), filePath.toUri().toASCIIString());
82+
83+
List<CodeLens> cls = editor.getCodeLenses("findUserByLastnameLikeOrderByFirstname", 1);
84+
assertEquals("Turn into @Query", cls.get(0).getCommand().getTitle());
85+
assertEquals("Implementation", cls.get(1).getCommand().getTitle());
86+
assertEquals("filter = \"{'lastname':{'$regex':/\\Q?0\\E/}}\"" + ", " + "sort = \"{'firstname':{'$numberInt':'1'}}\"", cls.get(2).getCommand().getTitle());
87+
}
88+
89+
@Test
90+
void noCodeLensOverMethodWithQueryAnnotation() throws Exception {
91+
Path filePath = Paths.get(testProject.getLocationUri())
92+
.resolve("src/main/java/example/springdata/aot/UserRepository.java");
93+
Editor editor = harness.newEditor(LanguageId.JAVA, new String(Files.readAllBytes(filePath), StandardCharsets.UTF_8), filePath.toUri().toASCIIString());
94+
95+
List<CodeLens> cls = editor.getCodeLenses("usersWithUsernamesStartingWith", 1);
96+
assertEquals(1, cls.size());
97+
assertEquals("Implementation", cls.get(0).getCommand().getTitle());
98+
assertEquals(1, cls.get(0).getCommand().getArguments().size());
99+
}
100+
}

headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataCodeLensProviderTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public class DataRepositoryAotMetadataCodeLensProviderTest {
5151

5252
@BeforeEach
5353
public void setup() throws Exception {
54-
testProject = ProjectsHarness.INSTANCE.mavenProject("aot-generation");
54+
testProject = ProjectsHarness.INSTANCE.mavenProject("aot-data-repositories-jpa");
5555
harness.useProject(testProject);
5656
harness.intialize(null);
5757

headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/GenAotQueryMethodImplProviderTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public class GenAotQueryMethodImplProviderTest {
5151

5252
@BeforeEach
5353
public void setup() throws Exception {
54-
testProject = ProjectsHarness.INSTANCE.mavenProject("aot-generation");
54+
testProject = ProjectsHarness.INSTANCE.mavenProject("aot-data-repositories-jpa");
5555
harness.useProject(testProject);
5656
harness.intialize(null);
5757

headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/QueryMethodCodeActionProviderTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public class QueryMethodCodeActionProviderTest {
5858

5959
@BeforeEach
6060
public void setup() throws Exception {
61-
testProject = ProjectsHarness.INSTANCE.mavenProject("aot-generation");
61+
testProject = ProjectsHarness.INSTANCE.mavenProject("aot-data-repositories-jpa");
6262
harness.useProject(testProject);
6363
harness.intialize(null);
6464

0 commit comments

Comments
 (0)