Skip to content

Commit 68cc248

Browse files
committed
initial work on using aot information for spring data repositories
1 parent ff5bb3d commit 68cc248

File tree

9 files changed

+296
-10
lines changed

9 files changed

+296
-10
lines changed

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2018, 2024 Pivotal, Inc.
2+
* Copyright (c) 2018, 2025 Pivotal, Inc.
33
* All rights reserved. This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v1.0
55
* which accompanies this distribution, and is available at
@@ -53,6 +53,7 @@
5353
import org.springframework.ide.vscode.boot.java.conditionals.ConditionalOnBeanDefinitionProvider;
5454
import org.springframework.ide.vscode.boot.java.conditionals.ConditionalOnResourceDefinitionProvider;
5555
import org.springframework.ide.vscode.boot.java.copilot.util.ResponseModifier;
56+
import org.springframework.ide.vscode.boot.java.data.DataRepositoryAotMetadataService;
5657
import org.springframework.ide.vscode.boot.java.data.jpa.queries.DataQueryParameterDefinitionProvider;
5758
import org.springframework.ide.vscode.boot.java.data.jpa.queries.JdtDataQuerySemanticTokensProvider;
5859
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCodeActionProvider;
@@ -428,6 +429,11 @@ ModulithService modulithService(SimpleLanguageServer server, JavaProjectFinder p
428429
return new ModulithService(server, projectFinder, projectObserver, springIndex, reconciler, config);
429430
}
430431

432+
@Bean
433+
DataRepositoryAotMetadataService dataAotMetadataService() {
434+
return new DataRepositoryAotMetadataService();
435+
}
436+
431437
@Bean ResponseModifier responseModifier() {
432438
return new ResponseModifier();
433439
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.springframework.ide.vscode.boot.java.beans.ComponentSymbolProvider;
2020
import org.springframework.ide.vscode.boot.java.beans.ConfigurationPropertiesSymbolProvider;
2121
import org.springframework.ide.vscode.boot.java.beans.FeignClientSymbolProvider;
22+
import org.springframework.ide.vscode.boot.java.data.DataRepositoryAotMetadataService;
2223
import org.springframework.ide.vscode.boot.java.data.DataRepositorySymbolProvider;
2324
import org.springframework.ide.vscode.boot.java.events.EventListenerSymbolProvider;
2425
import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider;
@@ -29,14 +30,14 @@
2930
public class SpringSymbolIndexerConfig {
3031

3132
@Bean
32-
AnnotationHierarchyAwareLookup<SymbolProvider> symbolProviders(IndexCache cache) {
33+
AnnotationHierarchyAwareLookup<SymbolProvider> symbolProviders(IndexCache cache, DataRepositoryAotMetadataService repositoryMetadataService) {
3334
AnnotationHierarchyAwareLookup<SymbolProvider> providers = new AnnotationHierarchyAwareLookup<>();
3435

3536
RequestMappingSymbolProvider requestMappingSymbolProvider = new RequestMappingSymbolProvider();
3637
BeansSymbolProvider beansSymbolProvider = new BeansSymbolProvider();
3738
ComponentSymbolProvider componentSymbolProvider = new ComponentSymbolProvider();
3839
ConfigurationPropertiesSymbolProvider configPropsSymbolProvider = new ConfigurationPropertiesSymbolProvider();
39-
DataRepositorySymbolProvider dataRepositorySymbolProvider = new DataRepositorySymbolProvider();
40+
DataRepositorySymbolProvider dataRepositorySymbolProvider = new DataRepositorySymbolProvider(repositoryMetadataService);
4041
EventListenerSymbolProvider eventListenerSymbolProvider = new EventListenerSymbolProvider();
4142

4243
RestrictedDefaultSymbolProvider restrictedDefaultSymbolProvider = new RestrictedDefaultSymbolProvider();

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
import org.springframework.ide.vscode.boot.java.conditionals.ConditionalsLiveHoverProvider;
3434
import org.springframework.ide.vscode.boot.java.copilot.CopilotAgentCommandHandler;
3535
import org.springframework.ide.vscode.boot.java.copilot.util.ResponseModifier;
36+
import org.springframework.ide.vscode.boot.java.data.DataRepositoryAotMetadataCodeLensProvider;
37+
import org.springframework.ide.vscode.boot.java.data.DataRepositoryAotMetadataService;
3638
import org.springframework.ide.vscode.boot.java.events.EventReferenceProvider;
3739
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCodeActionProvider;
3840
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCodeLensEngine;
@@ -123,6 +125,7 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
123125
private JdtSemanticTokensHandler semanticTokensHandler;
124126
private JdtInlayHintsHandler inlayHintsHandler;
125127
private SpelSemanticTokens spelSemanticTokens;
128+
private DataRepositoryAotMetadataService dataRepositoryAotMetadataService;
126129

127130
public BootJavaLanguageServerComponents(ApplicationContext appContext) {
128131
this.server = appContext.getBean(SimpleLanguageServer.class);
@@ -177,8 +180,8 @@ public BootJavaLanguageServerComponents(ApplicationContext appContext) {
177180
new LiveAppURLSymbolProvider(liveDataProvider)));
178181

179182
spelSemanticTokens = appContext.getBean(SpelSemanticTokens.class);
180-
181-
codeLensHandler = createCodeLensEngine(springIndex, projectFinder, server, spelSemanticTokens);
183+
dataRepositoryAotMetadataService = appContext.getBean(DataRepositoryAotMetadataService.class);
184+
codeLensHandler = createCodeLensEngine(springIndex, projectFinder, server, spelSemanticTokens, dataRepositoryAotMetadataService);
182185

183186
highlightsEngine = createDocumentHighlightEngine(appContext);
184187
documents.onDocumentHighlight(highlightsEngine);
@@ -312,10 +315,13 @@ protected ReferencesHandler createReferenceHandler(SimpleLanguageServer server,
312315
return new BootJavaReferencesHandler(this, cuCache, projectFinder, specificProviders, unspecificProviders);
313316
}
314317

315-
protected BootJavaCodeLensEngine createCodeLensEngine(SpringMetamodelIndex springIndex, JavaProjectFinder projectFinder, SimpleLanguageServer server, SpelSemanticTokens spelSemanticTokens) {
318+
protected BootJavaCodeLensEngine createCodeLensEngine(SpringMetamodelIndex springIndex, JavaProjectFinder projectFinder, SimpleLanguageServer server,
319+
SpelSemanticTokens spelSemanticTokens, DataRepositoryAotMetadataService repositoryAotMetadataService) {
320+
316321
Collection<CodeLensProvider> codeLensProvider = new ArrayList<>();
317322
codeLensProvider.add(new WebfluxHandlerCodeLensProvider(springIndex));
318323
codeLensProvider.add(new CopilotCodeLensProvider(projectFinder, server, spelSemanticTokens));
324+
codeLensProvider.add(new DataRepositoryAotMetadataCodeLensProvider(projectFinder, repositoryAotMetadataService));
319325

320326
return new BootJavaCodeLensEngine(this, codeLensProvider);
321327
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Broadcom
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 - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.ide.vscode.boot.java.data;
12+
13+
public record DataRepositoryAotMetadata (String name, String type, DataRepositoryAotMetadataMethod[] methods) {
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Broadcom
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 - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.ide.vscode.boot.java.data;
12+
13+
import java.util.List;
14+
15+
import org.eclipse.jdt.core.dom.ASTVisitor;
16+
import org.eclipse.jdt.core.dom.CompilationUnit;
17+
import org.eclipse.jdt.core.dom.IMethodBinding;
18+
import org.eclipse.jdt.core.dom.MethodDeclaration;
19+
import org.eclipse.lsp4j.CodeLens;
20+
import org.eclipse.lsp4j.Command;
21+
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
22+
import org.slf4j.Logger;
23+
import org.slf4j.LoggerFactory;
24+
import org.springframework.ide.vscode.boot.java.handlers.CodeLensProvider;
25+
import org.springframework.ide.vscode.commons.java.IJavaProject;
26+
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
27+
import org.springframework.ide.vscode.commons.util.BadLocationException;
28+
import org.springframework.ide.vscode.commons.util.text.TextDocument;
29+
30+
/**
31+
* @author Martin Lippert
32+
*/
33+
public class DataRepositoryAotMetadataCodeLensProvider implements CodeLensProvider {
34+
35+
private static final Logger log = LoggerFactory.getLogger(DataRepositoryAotMetadataCodeLensProvider.class);
36+
37+
private final DataRepositoryAotMetadataService repositoryMetadataService;
38+
private final JavaProjectFinder projectFinder;
39+
40+
public DataRepositoryAotMetadataCodeLensProvider(JavaProjectFinder projectFinder, DataRepositoryAotMetadataService repositoryMetadataService) {
41+
this.projectFinder = projectFinder;
42+
this.repositoryMetadataService = repositoryMetadataService;
43+
}
44+
45+
@Override
46+
public void provideCodeLenses(CancelChecker cancelToken, TextDocument document, CompilationUnit cu, List<CodeLens> resultAccumulator) {
47+
cu.accept(new ASTVisitor() {
48+
@Override
49+
public boolean visit(MethodDeclaration node) {
50+
provideCodeLens(cancelToken, node, document, resultAccumulator);
51+
return super.visit(node);
52+
}
53+
});
54+
}
55+
56+
protected void provideCodeLens(CancelChecker cancelToken, MethodDeclaration node, TextDocument document, List<CodeLens> resultAccumulator) {
57+
cancelToken.checkCanceled();
58+
59+
IMethodBinding methodBinding = node.resolveBinding();
60+
61+
if (methodBinding == null || methodBinding.getDeclaringClass() == null
62+
|| methodBinding.getMethodDeclaration() == null
63+
|| methodBinding.getDeclaringClass().getBinaryName() == null
64+
|| methodBinding.getMethodDeclaration().toString() == null) {
65+
return;
66+
}
67+
68+
cancelToken.checkCanceled();
69+
70+
final String repositoryClass = methodBinding.getDeclaringClass().getBinaryName().trim();
71+
final IMethodBinding method = methodBinding.getMethodDeclaration();
72+
73+
IJavaProject project = projectFinder.find(document.getId()).get();
74+
if (project == null) {
75+
return;
76+
}
77+
78+
DataRepositoryAotMetadata metadata = repositoryMetadataService.getRepositoryMetadata(project, repositoryClass);
79+
80+
if (metadata == null) {
81+
return;
82+
}
83+
84+
cancelToken.checkCanceled();
85+
86+
String queryStatement = repositoryMetadataService.getQueryStatement(metadata, method);
87+
if (queryStatement == null) {
88+
return;
89+
}
90+
91+
CodeLens codeLens = createCodeLens(node, document, queryStatement);
92+
resultAccumulator.add(codeLens);
93+
}
94+
95+
private CodeLens createCodeLens(MethodDeclaration node, TextDocument document, String queryStatement) {
96+
try {
97+
Command cmd = new Command();
98+
cmd.setTitle(queryStatement);
99+
100+
CodeLens codeLens = new CodeLens();
101+
codeLens.setRange(document.toRange(node.getName().getStartPosition(), node.getName().getLength()));
102+
codeLens.setCommand(cmd);
103+
104+
return codeLens;
105+
106+
} catch (BadLocationException e) {
107+
log.error("bad location while calculating code lens for data repository query method", e);
108+
return null;
109+
}
110+
}
111+
112+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Broadcom
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 - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.ide.vscode.boot.java.data;
12+
13+
public record DataRepositoryAotMetadataMethod(String name, String signature, DataRepositoryAotMetadataQuery query) {
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Broadcom
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 - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.ide.vscode.boot.java.data;
12+
13+
public record DataRepositoryAotMetadataQuery(String query) {
14+
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Broadcom
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 - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.ide.vscode.boot.java.data;
12+
13+
import java.io.File;
14+
import java.io.FileReader;
15+
import java.io.IOException;
16+
import java.util.Optional;
17+
18+
import org.eclipse.jdt.core.dom.IMethodBinding;
19+
import org.slf4j.Logger;
20+
import org.slf4j.LoggerFactory;
21+
import org.springframework.ide.vscode.commons.java.IClasspathUtil;
22+
import org.springframework.ide.vscode.commons.java.IJavaProject;
23+
import org.springframework.ide.vscode.commons.java.parser.JLRMethodParser;
24+
import org.springframework.ide.vscode.commons.java.parser.JLRMethodParser.JLRMethod;
25+
26+
import com.google.gson.Gson;
27+
28+
/**
29+
* @author Martin Lippert
30+
*/
31+
public class DataRepositoryAotMetadataService {
32+
33+
private static final Logger log = LoggerFactory.getLogger(DataRepositoryAotMetadataService.class);
34+
35+
public DataRepositoryAotMetadata getRepositoryMetadata(IJavaProject project, String repositoryType) {
36+
try {
37+
String metadataFilePath = repositoryType.replace('.', File.separatorChar);
38+
39+
IClasspathUtil.getOutputFolders(project.getClasspath()).forEach(System.out::println);
40+
41+
Optional<File> metadataFile = IClasspathUtil.getOutputFolders(project.getClasspath())
42+
.map(outputFolder -> new File(outputFolder.getParentFile(), "spring-aot/main/resources/" + metadataFilePath + ".json"))
43+
.filter(file -> file.exists())
44+
.findFirst();
45+
46+
if (metadataFile.isPresent()) {
47+
return readMetadataFile(metadataFile.get());
48+
}
49+
50+
} catch (Exception e) {
51+
log.error("error finding spring data repository definition metadata file", e);
52+
}
53+
54+
return null;
55+
}
56+
57+
private DataRepositoryAotMetadata readMetadataFile(File file) {
58+
59+
try (FileReader reader = new FileReader(file)) {
60+
Gson gson = new Gson();
61+
DataRepositoryAotMetadata result = gson.fromJson(reader, DataRepositoryAotMetadata.class);
62+
63+
return result;
64+
}
65+
catch (IOException e) {
66+
return null;
67+
}
68+
}
69+
70+
public String getQueryStatement(DataRepositoryAotMetadata metadata, IMethodBinding method) {
71+
DataRepositoryAotMetadataMethod methodMetadata = findMethod(metadata, method);
72+
return methodMetadata != null ? methodMetadata.query().query() : null;
73+
}
74+
75+
private DataRepositoryAotMetadataMethod findMethod(DataRepositoryAotMetadata metadata, IMethodBinding method) {
76+
String name = method.getName();
77+
78+
for (DataRepositoryAotMetadataMethod methodMetadata : metadata.methods()) {
79+
80+
// TODO: This check needs more exact method signature matching - which is a little more complicated
81+
// due to runtime Method.toGenericString() output needs to be compared to IMethodBinding source level method information
82+
83+
if (methodMetadata.name() != null && methodMetadata.name().equals(name)) {
84+
String signature = methodMetadata.signature();
85+
JLRMethod parsedMethodSignature = JLRMethodParser.parse(signature);
86+
87+
String methodName = parsedMethodSignature.getMethodName();
88+
String[] parameters = parsedMethodSignature.getParameters();
89+
String returnType = parsedMethodSignature.getReturnType();
90+
parsedMethodSignature.getFQClassName();
91+
92+
return methodMetadata;
93+
}
94+
}
95+
96+
return null;
97+
}
98+
99+
100+
}

0 commit comments

Comments
 (0)