GH-1041: removed feign-specific symbol addon information and rebased implementation on spring index

This commit is contained in:
Martin Lippert
2025-01-06 08:58:03 +01:00
parent d30dea8d66
commit 133516b317
6 changed files with 70 additions and 147 deletions

View File

@@ -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) {

View File

@@ -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;
}
}

View File

@@ -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];
}
}

View File

@@ -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;
}

View File

@@ -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";

View File

@@ -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<ReconcileProblem> problems = reconcile("A.java", source, false);
List<ReconcileProblem> 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<ReconcileProblem> 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<ReconcileProblem> 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());
}
}