From 133516b317e5c57e2f617f4a4b4e0869d7ea62c6 Mon Sep 17 00:00:00 2001 From: Martin Lippert Date: Mon, 6 Jan 2025 08:58:03 +0100 Subject: [PATCH] GH-1041: removed feign-specific symbol addon information and rebased implementation on spring index --- .../ide/vscode/boot/app/JdtConfig.java | 5 +- ...FeignClientBeanSymbolAddOnInformation.java | 22 ------ .../java/beans/FeignClientSymbolProvider.java | 33 +------- ...ConfigurationIfBeansPresentReconciler.java | 79 +++++++++---------- .../boot/java/utils/SpringIndexerJava.java | 2 +- ...igurationIfBeansPresentReconcilerTest.java | 76 +++++++----------- 6 files changed, 70 insertions(+), 147 deletions(-) delete mode 100644 headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/FeignClientBeanSymbolAddOnInformation.java diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/JdtConfig.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/JdtConfig.java index 82fa28848..94b135d80 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/JdtConfig.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/JdtConfig.java @@ -16,6 +16,7 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; +import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex; import org.springframework.ide.vscode.boot.java.cron.CronExpressionsInlayHintsProvider; import org.springframework.ide.vscode.boot.java.cron.CronReconciler; import org.springframework.ide.vscode.boot.java.cron.CronSemanticTokens; @@ -63,8 +64,8 @@ public class JdtConfig { return new BeanMethodNotPublicReconciler(server.getQuickfixRegistry()); } - @Bean AddConfigurationIfBeansPresentReconciler addConfigurationIfBeansPresentReconciler(SimpleLanguageServer server) { - return new AddConfigurationIfBeansPresentReconciler(server.getQuickfixRegistry()); + @Bean AddConfigurationIfBeansPresentReconciler addConfigurationIfBeansPresentReconciler(SimpleLanguageServer server, SpringMetamodelIndex springIndex) { + return new AddConfigurationIfBeansPresentReconciler(server.getQuickfixRegistry(), springIndex); } @Bean AutowiredFieldIntoConstructorParameterReconciler autowiredFieldIntoConstructorParameterReconciler(SimpleLanguageServer server) { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/FeignClientBeanSymbolAddOnInformation.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/FeignClientBeanSymbolAddOnInformation.java deleted file mode 100644 index 1030dc724..000000000 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/FeignClientBeanSymbolAddOnInformation.java +++ /dev/null @@ -1,22 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2023 VMware, 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: - * VMware, Inc. - initial API and implementation - *******************************************************************************/ -package org.springframework.ide.vscode.boot.java.beans; - -public class FeignClientBeanSymbolAddOnInformation extends BeansSymbolAddOnInformation { - - final public String[] configClasses; - - public FeignClientBeanSymbolAddOnInformation(String beanID, String beanType, String... configClasses) { - super(beanID, beanType); - this.configClasses = configClasses; - } - -} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/FeignClientSymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/FeignClientSymbolProvider.java index 2aef9b77b..0d4ec6077 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/FeignClientSymbolProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/FeignClientSymbolProvider.java @@ -13,20 +13,16 @@ package org.springframework.ide.vscode.boot.java.beans; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; -import java.util.List; -import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import org.eclipse.jdt.core.dom.Annotation; -import org.eclipse.jdt.core.dom.ArrayInitializer; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.MemberValuePair; import org.eclipse.jdt.core.dom.NormalAnnotation; import org.eclipse.jdt.core.dom.SingleMemberAnnotation; import org.eclipse.jdt.core.dom.TypeDeclaration; -import org.eclipse.jdt.core.dom.TypeLiteral; import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.SymbolKind; import org.eclipse.lsp4j.WorkspaceSymbol; @@ -86,7 +82,7 @@ public class FeignClientSymbolProvider extends AbstractSymbolProvider { beanLabel("+", annotationTypeName, metaAnnotationNames, beanName, beanType == null ? "" : beanType.getName()), SymbolKind.Interface, Either.forLeft(location)); - SymbolAddOnInformation[] addon = new SymbolAddOnInformation[] {new FeignClientBeanSymbolAddOnInformation(beanName, beanType == null ? "" : beanType.getQualifiedName(), getConfigClass(node))}; + SymbolAddOnInformation[] addon = new SymbolAddOnInformation[] {new BeansSymbolAddOnInformation(beanName, beanType == null ? "" : beanType.getQualifiedName())}; InjectionPoint[] injectionPoints = ASTUtils.findInjectionPoints(type, doc); @@ -159,31 +155,4 @@ public class FeignClientSymbolProvider extends AbstractSymbolProvider { return BeanUtils.getBeanNameFromType(typeDecl.getName().getIdentifier()); } - private String[] getConfigClass(Annotation node) { - if (node.isNormalAnnotation()) { - NormalAnnotation normalAnnotation = (NormalAnnotation) node; - for (Object o : normalAnnotation.values()) { - if (o instanceof MemberValuePair) { - MemberValuePair pair = (MemberValuePair) o; - if ("configuration".equals(pair.getName().getIdentifier())) { - if (pair.getValue() instanceof TypeLiteral) { - ITypeBinding b = ((TypeLiteral) pair.getValue()).getType().resolveBinding(); - return new String[] { b.getQualifiedName() }; - } else if (pair.getValue() instanceof ArrayInitializer){ - List expressions = (List) ((ArrayInitializer) pair.getValue()).expressions(); - return expressions.stream() - .filter(TypeLiteral.class::isInstance) - .map(TypeLiteral.class::cast) - .map(tl -> tl.getType().resolveBinding()) - .filter(Objects::nonNull) - .map(b -> b.getQualifiedName()) - .toArray(String[]::new); - } - } - } - } - } - return new String[0]; - } - } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/AddConfigurationIfBeansPresentReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/AddConfigurationIfBeansPresentReconciler.java index b3f26d546..394d8cb1d 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/AddConfigurationIfBeansPresentReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/AddConfigurationIfBeansPresentReconciler.java @@ -12,8 +12,10 @@ package org.springframework.ide.vscode.boot.java.reconcilers; import java.lang.reflect.Modifier; import java.net.URI; +import java.util.Arrays; import java.util.Iterator; import java.util.List; +import java.util.stream.Stream; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTVisitor; @@ -23,17 +25,10 @@ import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.openrewrite.java.spring.boot2.AddConfigurationAnnotationIfBeansPresent; -import org.springframework.beans.BeansException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.ide.vscode.boot.app.SpringSymbolIndex; +import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex; import org.springframework.ide.vscode.boot.java.Annotations; import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType; import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies; -import org.springframework.ide.vscode.boot.java.beans.BeansSymbolAddOnInformation; -import org.springframework.ide.vscode.boot.java.beans.FeignClientBeanSymbolAddOnInformation; -import org.springframework.ide.vscode.boot.java.handlers.EnhancedSymbolInformation; -import org.springframework.ide.vscode.boot.java.handlers.SymbolAddOnInformation; import org.springframework.ide.vscode.commons.Version; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.java.SpringProjectUtil; @@ -41,19 +36,21 @@ import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRe import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector; import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType; import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl; +import org.springframework.ide.vscode.commons.protocol.spring.Bean; import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope; import org.springframework.ide.vscode.commons.rewrite.java.FixDescriptor; -public class AddConfigurationIfBeansPresentReconciler implements JdtAstReconciler, ApplicationContextAware { +public class AddConfigurationIfBeansPresentReconciler implements JdtAstReconciler { private static final String PROBLEM_LABEL = "'@Configuration' is missing on a class defining Spring Beans"; private static final String FIX_LABEL = "Add missing '@Configuration' annotations over classes"; - private QuickfixRegistry quickfixRegistry; - private ApplicationContext applicationContext; + private final QuickfixRegistry quickfixRegistry; + private final SpringMetamodelIndex springIndex; - public AddConfigurationIfBeansPresentReconciler(QuickfixRegistry quickfixRegistry) { + public AddConfigurationIfBeansPresentReconciler(QuickfixRegistry quickfixRegistry, SpringMetamodelIndex springIndex) { this.quickfixRegistry = quickfixRegistry; + this.springIndex = springIndex; } @Override @@ -67,11 +64,6 @@ public class AddConfigurationIfBeansPresentReconciler implements JdtAstReconcile return Boot2JavaProblemType.MISSING_CONFIGURATION_ANNOTATION; } - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.applicationContext = applicationContext; - } - @Override public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector, boolean isCompleteAst) { return new ASTVisitor() { @@ -146,33 +138,34 @@ public class AddConfigurationIfBeansPresentReconciler implements JdtAstReconcile } private boolean isException(IJavaProject project, TypeDeclaration classDecl) { - if (applicationContext != null) { - SpringSymbolIndex index = applicationContext.getBean(SpringSymbolIndex.class); - if (index != null) { - final String beanClassName = ReconcileUtils.getDeepErasureType(classDecl.resolveBinding()).getQualifiedName(); - for (EnhancedSymbolInformation s : index.getEnhancedSymbols(project)) { - SymbolAddOnInformation[] additionalInformation = s.getAdditionalInformation(); - if (additionalInformation != null) { - for (SymbolAddOnInformation info : additionalInformation) { - if (info instanceof BeansSymbolAddOnInformation) { - BeansSymbolAddOnInformation info2 = (BeansSymbolAddOnInformation) info; - if (beanClassName.equals(info2.getBeanType())) { - return true; - } - if (info instanceof FeignClientBeanSymbolAddOnInformation) { - FeignClientBeanSymbolAddOnInformation feign = (FeignClientBeanSymbolAddOnInformation) info; - for (String configBean : feign.configClasses) { - if (beanClassName.equals(configBean)) { - return true; - } - } - } - } - } - } - } - } + if (springIndex == null) { + return false; } + + final String beanClassName = ReconcileUtils.getDeepErasureType(classDecl.resolveBinding()).getQualifiedName(); + + Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName()); + + if (beans == null || beans.length == 0) { + return false; + } + + // look for beans with that type + if (Arrays.stream(beans).anyMatch(bean -> bean.getType().equals(beanClassName))) { + return true; + } + + boolean isConfiguredAsfeignConfigClass = Arrays.stream(beans) + .flatMap(bean -> Arrays.stream(bean.getAnnotations())) + .filter(annotation -> annotation.getAnnotationType().equals(Annotations.FEIGN_CLIENT)) + .map(annotation -> annotation.getAttributes().get("configuration")) + .flatMap(attributeValues -> attributeValues != null ? Arrays.stream(attributeValues) : Stream.empty()) + .anyMatch(attributeValue -> attributeValue.getName().equals(beanClassName)); + + if (isConfiguredAsfeignConfigClass) { + return true; + } + return false; } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java index 07d2c5497..b9960de23 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java @@ -93,7 +93,7 @@ public class SpringIndexerJava implements SpringIndexer { // whenever the implementation of the indexer changes in a way that the stored data in the cache is no longer valid, // we need to change the generation - this will result in a re-indexing due to no up-to-date cache data being found - private static final String GENERATION = "GEN-8"; + private static final String GENERATION = "GEN-9"; private static final String INDEX_FILES_TASK_ID = "index-java-source-files-task-"; private static final String SYMBOL_KEY = "symbols"; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/reconcilers/test/AddConfigurationIfBeansPresentReconcilerTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/reconcilers/test/AddConfigurationIfBeansPresentReconcilerTest.java index 70975c9c3..d4f027127 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/reconcilers/test/AddConfigurationIfBeansPresentReconcilerTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/reconcilers/test/AddConfigurationIfBeansPresentReconcilerTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 VMware, Inc. + * Copyright (c) 2023, 2024 VMware, 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 @@ -11,32 +11,23 @@ package org.springframework.ide.vscode.boot.java.reconcilers.test; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; import java.util.List; +import java.util.Map; -import org.eclipse.lsp4j.Location; -import org.eclipse.lsp4j.Range; -import org.eclipse.lsp4j.SymbolKind; -import org.eclipse.lsp4j.WorkspaceSymbol; -import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.context.ApplicationContext; -import org.springframework.ide.vscode.boot.app.SpringSymbolIndex; +import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex; +import org.springframework.ide.vscode.boot.java.Annotations; import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType; -import org.springframework.ide.vscode.boot.java.beans.ConfigBeanSymbolAddOnInformation; -import org.springframework.ide.vscode.boot.java.beans.FeignClientBeanSymbolAddOnInformation; -import org.springframework.ide.vscode.boot.java.handlers.EnhancedSymbolInformation; -import org.springframework.ide.vscode.boot.java.handlers.SymbolAddOnInformation; import org.springframework.ide.vscode.boot.java.reconcilers.AddConfigurationIfBeansPresentReconciler; import org.springframework.ide.vscode.boot.java.reconcilers.JdtAstReconciler; -import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry; import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem; +import org.springframework.ide.vscode.commons.protocol.spring.AnnotationAttributeValue; +import org.springframework.ide.vscode.commons.protocol.spring.AnnotationMetadata; +import org.springframework.ide.vscode.commons.protocol.spring.Bean; public class AddConfigurationIfBeansPresentReconcilerTest extends BaseReconcilerTest { @@ -52,7 +43,7 @@ public class AddConfigurationIfBeansPresentReconcilerTest extends BaseReconciler @Override protected JdtAstReconciler getReconciler() { - return new AddConfigurationIfBeansPresentReconciler(new QuickfixRegistry()); + return new AddConfigurationIfBeansPresentReconciler(new QuickfixRegistry(), null); } @BeforeEach @@ -81,7 +72,11 @@ public class AddConfigurationIfBeansPresentReconcilerTest extends BaseReconciler } """; - List problems = reconcile("A.java", source, false); + List problems = reconcile(() -> { + SpringMetamodelIndex springIndex = new SpringMetamodelIndex(); + AddConfigurationIfBeansPresentReconciler r = new AddConfigurationIfBeansPresentReconciler(new QuickfixRegistry(), springIndex); + return r; + }, "A.java", source, false); assertEquals(1, problems.size()); @@ -93,7 +88,6 @@ public class AddConfigurationIfBeansPresentReconcilerTest extends BaseReconciler assertEquals("A", markedStr); assertEquals(2, problem.getQuickfixes().size()); - } @Test @@ -113,26 +107,20 @@ public class AddConfigurationIfBeansPresentReconcilerTest extends BaseReconciler } """; List problems = reconcile(() -> { - AddConfigurationIfBeansPresentReconciler r = new AddConfigurationIfBeansPresentReconciler(new QuickfixRegistry()); - - WorkspaceSymbol workspaceSymbol = new WorkspaceSymbol("testConfig", SymbolKind.Class, Either.forLeft(new Location("file:///someUri", new Range()))); - ConfigBeanSymbolAddOnInformation beanSymbolAddOn = new ConfigBeanSymbolAddOnInformation("a", "example.demo.A"); - EnhancedSymbolInformation beanSymbol = new EnhancedSymbolInformation(workspaceSymbol, new SymbolAddOnInformation[] { beanSymbolAddOn }); - - SpringSymbolIndex mockSymbolIndex = mock(SpringSymbolIndex.class); - - when(mockSymbolIndex.getEnhancedSymbols(any(IJavaProject.class))).thenReturn(List.of(beanSymbol)); - - ApplicationContext context = mock(ApplicationContext.class); - when(context.getBean(SpringSymbolIndex.class)).thenReturn(mockSymbolIndex); - - r.setApplicationContext(context); + SpringMetamodelIndex springIndex = new SpringMetamodelIndex(); + + AnnotationMetadata annotationMetadata = new AnnotationMetadata(Annotations.CONFIGURATION, false, null, Map.of()); + AnnotationMetadata[] annotations = new AnnotationMetadata[] {annotationMetadata}; + Bean configBean = new Bean("a", "example.demo.A", null, null, null, annotations); + Bean[] beans = new Bean[] {configBean}; + springIndex.updateBeans(getProjectName(), beans); + + AddConfigurationIfBeansPresentReconciler r = new AddConfigurationIfBeansPresentReconciler(new QuickfixRegistry(), springIndex); return r; }, "A.java", source, false); assertEquals(0, problems.size()); - } @Test @@ -152,25 +140,19 @@ public class AddConfigurationIfBeansPresentReconcilerTest extends BaseReconciler } """; List problems = reconcile(() -> { - AddConfigurationIfBeansPresentReconciler r = new AddConfigurationIfBeansPresentReconciler(new QuickfixRegistry()); + SpringMetamodelIndex springIndex = new SpringMetamodelIndex(); - WorkspaceSymbol workspaceSymbol = new WorkspaceSymbol("testConfig", SymbolKind.Class, Either.forLeft(new Location("file:///someUri", new Range()))); - FeignClientBeanSymbolAddOnInformation beanSymbolAddOn = new FeignClientBeanSymbolAddOnInformation("b", "example.demo.B", "example.demo.A"); - EnhancedSymbolInformation beanSymbol = new EnhancedSymbolInformation(workspaceSymbol, new SymbolAddOnInformation[] { beanSymbolAddOn }); + AnnotationMetadata annotationMetadata = new AnnotationMetadata(Annotations.FEIGN_CLIENT, false, null, Map.of("configuration", new AnnotationAttributeValue[] {new AnnotationAttributeValue("example.demo.A", null)})); + AnnotationMetadata[] annotations = new AnnotationMetadata[] {annotationMetadata}; + Bean configBean = new Bean("feignClient", "example.demo.FeignClientExample", null, null, null, annotations); + Bean[] beans = new Bean[] {configBean}; + springIndex.updateBeans(getProjectName(), beans); - SpringSymbolIndex mockSymbolIndex = mock(SpringSymbolIndex.class); - - when(mockSymbolIndex.getEnhancedSymbols(any(IJavaProject.class))).thenReturn(List.of(beanSymbol)); - - ApplicationContext context = mock(ApplicationContext.class); - when(context.getBean(SpringSymbolIndex.class)).thenReturn(mockSymbolIndex); - - r.setApplicationContext(context); + AddConfigurationIfBeansPresentReconciler r = new AddConfigurationIfBeansPresentReconciler(new QuickfixRegistry(), springIndex); return r; }, "A.java", source, false); assertEquals(0, problems.size()); - } }