initial work on using aot information for spring data repositories
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2018, 2024 Pivotal, Inc.
|
||||
* Copyright (c) 2018, 2025 Pivotal, 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
|
||||
@@ -53,6 +53,7 @@ import org.springframework.ide.vscode.boot.java.beans.ResourceDefinitionProvider
|
||||
import org.springframework.ide.vscode.boot.java.conditionals.ConditionalOnBeanDefinitionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.conditionals.ConditionalOnResourceDefinitionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.copilot.util.ResponseModifier;
|
||||
import org.springframework.ide.vscode.boot.java.data.DataRepositoryAotMetadataService;
|
||||
import org.springframework.ide.vscode.boot.java.data.jpa.queries.DataQueryParameterDefinitionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.data.jpa.queries.JdtDataQuerySemanticTokensProvider;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCodeActionProvider;
|
||||
@@ -428,6 +429,11 @@ public class BootLanguageServerBootApp {
|
||||
return new ModulithService(server, projectFinder, projectObserver, springIndex, reconciler, config);
|
||||
}
|
||||
|
||||
@Bean
|
||||
DataRepositoryAotMetadataService dataAotMetadataService() {
|
||||
return new DataRepositoryAotMetadataService();
|
||||
}
|
||||
|
||||
@Bean ResponseModifier responseModifier() {
|
||||
return new ResponseModifier();
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.springframework.ide.vscode.boot.java.beans.BeansSymbolProvider;
|
||||
import org.springframework.ide.vscode.boot.java.beans.ComponentSymbolProvider;
|
||||
import org.springframework.ide.vscode.boot.java.beans.ConfigurationPropertiesSymbolProvider;
|
||||
import org.springframework.ide.vscode.boot.java.beans.FeignClientSymbolProvider;
|
||||
import org.springframework.ide.vscode.boot.java.data.DataRepositoryAotMetadataService;
|
||||
import org.springframework.ide.vscode.boot.java.data.DataRepositorySymbolProvider;
|
||||
import org.springframework.ide.vscode.boot.java.events.EventListenerSymbolProvider;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider;
|
||||
@@ -29,14 +30,14 @@ import org.springframework.ide.vscode.boot.java.utils.RestrictedDefaultSymbolPro
|
||||
public class SpringSymbolIndexerConfig {
|
||||
|
||||
@Bean
|
||||
AnnotationHierarchyAwareLookup<SymbolProvider> symbolProviders(IndexCache cache) {
|
||||
AnnotationHierarchyAwareLookup<SymbolProvider> symbolProviders(IndexCache cache, DataRepositoryAotMetadataService repositoryMetadataService) {
|
||||
AnnotationHierarchyAwareLookup<SymbolProvider> providers = new AnnotationHierarchyAwareLookup<>();
|
||||
|
||||
RequestMappingSymbolProvider requestMappingSymbolProvider = new RequestMappingSymbolProvider();
|
||||
BeansSymbolProvider beansSymbolProvider = new BeansSymbolProvider();
|
||||
ComponentSymbolProvider componentSymbolProvider = new ComponentSymbolProvider();
|
||||
ConfigurationPropertiesSymbolProvider configPropsSymbolProvider = new ConfigurationPropertiesSymbolProvider();
|
||||
DataRepositorySymbolProvider dataRepositorySymbolProvider = new DataRepositorySymbolProvider();
|
||||
DataRepositorySymbolProvider dataRepositorySymbolProvider = new DataRepositorySymbolProvider(repositoryMetadataService);
|
||||
EventListenerSymbolProvider eventListenerSymbolProvider = new EventListenerSymbolProvider();
|
||||
|
||||
RestrictedDefaultSymbolProvider restrictedDefaultSymbolProvider = new RestrictedDefaultSymbolProvider();
|
||||
|
||||
@@ -33,6 +33,8 @@ import org.springframework.ide.vscode.boot.java.beans.QualifierReferencesProvide
|
||||
import org.springframework.ide.vscode.boot.java.conditionals.ConditionalsLiveHoverProvider;
|
||||
import org.springframework.ide.vscode.boot.java.copilot.CopilotAgentCommandHandler;
|
||||
import org.springframework.ide.vscode.boot.java.copilot.util.ResponseModifier;
|
||||
import org.springframework.ide.vscode.boot.java.data.DataRepositoryAotMetadataCodeLensProvider;
|
||||
import org.springframework.ide.vscode.boot.java.data.DataRepositoryAotMetadataService;
|
||||
import org.springframework.ide.vscode.boot.java.events.EventReferenceProvider;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCodeActionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCodeLensEngine;
|
||||
@@ -123,6 +125,7 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
|
||||
private JdtSemanticTokensHandler semanticTokensHandler;
|
||||
private JdtInlayHintsHandler inlayHintsHandler;
|
||||
private SpelSemanticTokens spelSemanticTokens;
|
||||
private DataRepositoryAotMetadataService dataRepositoryAotMetadataService;
|
||||
|
||||
public BootJavaLanguageServerComponents(ApplicationContext appContext) {
|
||||
this.server = appContext.getBean(SimpleLanguageServer.class);
|
||||
@@ -177,8 +180,8 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
|
||||
new LiveAppURLSymbolProvider(liveDataProvider)));
|
||||
|
||||
spelSemanticTokens = appContext.getBean(SpelSemanticTokens.class);
|
||||
|
||||
codeLensHandler = createCodeLensEngine(springIndex, projectFinder, server, spelSemanticTokens);
|
||||
dataRepositoryAotMetadataService = appContext.getBean(DataRepositoryAotMetadataService.class);
|
||||
codeLensHandler = createCodeLensEngine(springIndex, projectFinder, server, spelSemanticTokens, dataRepositoryAotMetadataService);
|
||||
|
||||
highlightsEngine = createDocumentHighlightEngine(appContext);
|
||||
documents.onDocumentHighlight(highlightsEngine);
|
||||
@@ -312,10 +315,13 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
|
||||
return new BootJavaReferencesHandler(this, cuCache, projectFinder, specificProviders, unspecificProviders);
|
||||
}
|
||||
|
||||
protected BootJavaCodeLensEngine createCodeLensEngine(SpringMetamodelIndex springIndex, JavaProjectFinder projectFinder, SimpleLanguageServer server, SpelSemanticTokens spelSemanticTokens) {
|
||||
protected BootJavaCodeLensEngine createCodeLensEngine(SpringMetamodelIndex springIndex, JavaProjectFinder projectFinder, SimpleLanguageServer server,
|
||||
SpelSemanticTokens spelSemanticTokens, DataRepositoryAotMetadataService repositoryAotMetadataService) {
|
||||
|
||||
Collection<CodeLensProvider> codeLensProvider = new ArrayList<>();
|
||||
codeLensProvider.add(new WebfluxHandlerCodeLensProvider(springIndex));
|
||||
codeLensProvider.add(new CopilotCodeLensProvider(projectFinder, server, spelSemanticTokens));
|
||||
codeLensProvider.add(new DataRepositoryAotMetadataCodeLensProvider(projectFinder, repositoryAotMetadataService));
|
||||
|
||||
return new BootJavaCodeLensEngine(this, codeLensProvider);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2025 Broadcom
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.data;
|
||||
|
||||
public record DataRepositoryAotMetadata (String name, String type, DataRepositoryAotMetadataMethod[] methods) {
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2025 Broadcom
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTVisitor;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.jdt.core.dom.IMethodBinding;
|
||||
import org.eclipse.jdt.core.dom.MethodDeclaration;
|
||||
import org.eclipse.lsp4j.CodeLens;
|
||||
import org.eclipse.lsp4j.Command;
|
||||
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.CodeLensProvider;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class DataRepositoryAotMetadataCodeLensProvider implements CodeLensProvider {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(DataRepositoryAotMetadataCodeLensProvider.class);
|
||||
|
||||
private final DataRepositoryAotMetadataService repositoryMetadataService;
|
||||
private final JavaProjectFinder projectFinder;
|
||||
|
||||
public DataRepositoryAotMetadataCodeLensProvider(JavaProjectFinder projectFinder, DataRepositoryAotMetadataService repositoryMetadataService) {
|
||||
this.projectFinder = projectFinder;
|
||||
this.repositoryMetadataService = repositoryMetadataService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void provideCodeLenses(CancelChecker cancelToken, TextDocument document, CompilationUnit cu, List<CodeLens> resultAccumulator) {
|
||||
cu.accept(new ASTVisitor() {
|
||||
@Override
|
||||
public boolean visit(MethodDeclaration node) {
|
||||
provideCodeLens(cancelToken, node, document, resultAccumulator);
|
||||
return super.visit(node);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void provideCodeLens(CancelChecker cancelToken, MethodDeclaration node, TextDocument document, List<CodeLens> resultAccumulator) {
|
||||
cancelToken.checkCanceled();
|
||||
|
||||
IMethodBinding methodBinding = node.resolveBinding();
|
||||
|
||||
if (methodBinding == null || methodBinding.getDeclaringClass() == null
|
||||
|| methodBinding.getMethodDeclaration() == null
|
||||
|| methodBinding.getDeclaringClass().getBinaryName() == null
|
||||
|| methodBinding.getMethodDeclaration().toString() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
cancelToken.checkCanceled();
|
||||
|
||||
final String repositoryClass = methodBinding.getDeclaringClass().getBinaryName().trim();
|
||||
final IMethodBinding method = methodBinding.getMethodDeclaration();
|
||||
|
||||
IJavaProject project = projectFinder.find(document.getId()).get();
|
||||
if (project == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
DataRepositoryAotMetadata metadata = repositoryMetadataService.getRepositoryMetadata(project, repositoryClass);
|
||||
|
||||
if (metadata == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
cancelToken.checkCanceled();
|
||||
|
||||
String queryStatement = repositoryMetadataService.getQueryStatement(metadata, method);
|
||||
if (queryStatement == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
CodeLens codeLens = createCodeLens(node, document, queryStatement);
|
||||
resultAccumulator.add(codeLens);
|
||||
}
|
||||
|
||||
private CodeLens createCodeLens(MethodDeclaration node, TextDocument document, String queryStatement) {
|
||||
try {
|
||||
Command cmd = new Command();
|
||||
cmd.setTitle(queryStatement);
|
||||
|
||||
CodeLens codeLens = new CodeLens();
|
||||
codeLens.setRange(document.toRange(node.getName().getStartPosition(), node.getName().getLength()));
|
||||
codeLens.setCommand(cmd);
|
||||
|
||||
return codeLens;
|
||||
|
||||
} catch (BadLocationException e) {
|
||||
log.error("bad location while calculating code lens for data repository query method", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2025 Broadcom
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.data;
|
||||
|
||||
public record DataRepositoryAotMetadataMethod(String name, String signature, DataRepositoryAotMetadataQuery query) {
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2025 Broadcom
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.data;
|
||||
|
||||
public record DataRepositoryAotMetadataQuery(String query) {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2025 Broadcom
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.data;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.core.dom.IMethodBinding;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
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 com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class DataRepositoryAotMetadataService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(DataRepositoryAotMetadataService.class);
|
||||
|
||||
public DataRepositoryAotMetadata getRepositoryMetadata(IJavaProject project, String repositoryType) {
|
||||
try {
|
||||
String metadataFilePath = repositoryType.replace('.', File.separatorChar);
|
||||
|
||||
IClasspathUtil.getOutputFolders(project.getClasspath()).forEach(System.out::println);
|
||||
|
||||
Optional<File> metadataFile = IClasspathUtil.getOutputFolders(project.getClasspath())
|
||||
.map(outputFolder -> new File(outputFolder.getParentFile(), "spring-aot/main/resources/" + metadataFilePath + ".json"))
|
||||
.filter(file -> file.exists())
|
||||
.findFirst();
|
||||
|
||||
if (metadataFile.isPresent()) {
|
||||
return readMetadataFile(metadataFile.get());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("error finding spring data repository definition metadata file", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private DataRepositoryAotMetadata readMetadataFile(File file) {
|
||||
|
||||
try (FileReader reader = new FileReader(file)) {
|
||||
Gson gson = new Gson();
|
||||
DataRepositoryAotMetadata result = gson.fromJson(reader, DataRepositoryAotMetadata.class);
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String getQueryStatement(DataRepositoryAotMetadata metadata, IMethodBinding method) {
|
||||
DataRepositoryAotMetadataMethod methodMetadata = findMethod(metadata, method);
|
||||
return methodMetadata != null ? methodMetadata.query().query() : null;
|
||||
}
|
||||
|
||||
private DataRepositoryAotMetadataMethod findMethod(DataRepositoryAotMetadata metadata, IMethodBinding method) {
|
||||
String name = method.getName();
|
||||
|
||||
for (DataRepositoryAotMetadataMethod methodMetadata : metadata.methods()) {
|
||||
|
||||
// TODO: This check needs more exact method signature matching - which is a little more complicated
|
||||
// due to runtime Method.toGenericString() output needs to be compared to IMethodBinding source level method information
|
||||
|
||||
if (methodMetadata.name() != null && methodMetadata.name().equals(name)) {
|
||||
String signature = methodMetadata.signature();
|
||||
JLRMethod parsedMethodSignature = JLRMethodParser.parse(signature);
|
||||
|
||||
String methodName = parsedMethodSignature.getMethodName();
|
||||
String[] parameters = parsedMethodSignature.getParameters();
|
||||
String returnType = parsedMethodSignature.getReturnType();
|
||||
parsedMethodSignature.getFQClassName();
|
||||
|
||||
return methodMetadata;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.IMethodBinding;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.MethodDeclaration;
|
||||
import org.eclipse.jdt.core.dom.Modifier;
|
||||
@@ -57,7 +58,13 @@ import reactor.util.function.Tuples;
|
||||
public class DataRepositorySymbolProvider implements SymbolProvider {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(DataRepositorySymbolProvider.class);
|
||||
|
||||
private final DataRepositoryAotMetadataService repositoryMetadataService;
|
||||
|
||||
public DataRepositorySymbolProvider(DataRepositoryAotMetadataService repositoryMetadataService) {
|
||||
this.repositoryMetadataService = repositoryMetadataService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSymbols(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) {
|
||||
// this checks spring data repository beans that are defined as extensions of the repository interface
|
||||
@@ -115,7 +122,7 @@ public class DataRepositorySymbolProvider implements SymbolProvider {
|
||||
Range range = doc.toRange(nodeRegion);
|
||||
|
||||
if (methodName != null) {
|
||||
String queryString = identifyQueryString(method, annotationHierarchies);
|
||||
String queryString = identifyQueryString(method, annotationHierarchies, context);
|
||||
String methodSignature = identifyMethodSignature(method);
|
||||
beanDefinition.addChild(new QueryMethodIndexElement(methodSignature, queryString, range));
|
||||
}
|
||||
@@ -174,8 +181,9 @@ public class DataRepositorySymbolProvider implements SymbolProvider {
|
||||
return result;
|
||||
}
|
||||
|
||||
private String identifyQueryString(MethodDeclaration method, AnnotationHierarchies annotationHierarchies) {
|
||||
private String identifyQueryString(MethodDeclaration method, AnnotationHierarchies annotationHierarchies, SpringIndexerJavaContext context) {
|
||||
|
||||
// lookup query annotation on the method first
|
||||
EmbeddedQueryExpression queryExpression = null;
|
||||
|
||||
Collection<Annotation> annotations = ASTUtils.getAnnotations(method);
|
||||
@@ -191,12 +199,22 @@ public class DataRepositorySymbolProvider implements SymbolProvider {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (queryExpression != null) {
|
||||
return queryExpression.query().getText();
|
||||
}
|
||||
|
||||
// second option: lookup repository metadata service to see if there is a matching enty
|
||||
IMethodBinding methodBinding = method.resolveBinding();
|
||||
final String repositoryClass = methodBinding.getDeclaringClass().getBinaryName().trim();
|
||||
|
||||
return null;
|
||||
DataRepositoryAotMetadata repositoryMetadata = this.repositoryMetadataService.getRepositoryMetadata(context.getProject(), repositoryClass);
|
||||
if (repositoryMetadata == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String queryStatement = repositoryMetadataService.getQueryStatement(repositoryMetadata, methodBinding);
|
||||
return queryStatement;
|
||||
}
|
||||
|
||||
protected String beanLabel(boolean isFunctionBean, String beanName, String beanType, String markerString) {
|
||||
|
||||
Reference in New Issue
Block a user