Skip to content

Commit

Permalink
FeignClient config exception for Add @configuration reconciler
Browse files Browse the repository at this point in the history
  • Loading branch information
BoykoAlex committed Aug 31, 2023
1 parent b609808 commit 4093a0e
Show file tree
Hide file tree
Showing 6 changed files with 435 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
import org.springframework.ide.vscode.boot.index.cache.IndexCache;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchyAwareLookup;
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.FeignClientSymbolProvider;
import org.springframework.ide.vscode.boot.java.data.DataRepositorySymbolProvider;
import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider;
import org.springframework.ide.vscode.boot.java.requestmapping.RequestMappingSymbolProvider;
Expand Down Expand Up @@ -71,6 +71,8 @@ AnnotationHierarchyAwareLookup<SymbolProvider> symbolProviders(IndexCache cache)

providers.put(Annotations.REPOSITORY, dataRepositorySymbolProvider);
providers.put("", webfluxRouterSymbolProvider);

providers.put(Annotations.FEIGN_CLIENT, new FeignClientSymbolProvider());

return providers;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ public class Annotations {
public static final String CONDITIONAL_ON_JNDI = "org.springframework.boot.autoconfigure.condition.ConditionalOnJndi";
public static final String CONDITIONAL_ON_SINGLE_CANDIDATE = "org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate";

public static final String FEIGN_CLIENT = "org.springframework.cloud.openfeign.FeignClient";

public static final String VALUE = "org.springframework.beans.factory.annotation.Value";

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*******************************************************************************
* 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;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
/*******************************************************************************
* 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;

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;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.jsonrpc.messages.Tuple;
import org.eclipse.lsp4j.jsonrpc.messages.Tuple.Two;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.handlers.AbstractSymbolProvider;
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.utils.ASTUtils;
import org.springframework.ide.vscode.boot.java.utils.CachedSymbol;
import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext;
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
import org.springframework.ide.vscode.commons.protocol.spring.InjectionPoint;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.text.TextDocument;

public class FeignClientSymbolProvider extends AbstractSymbolProvider {

private static final Logger log = LoggerFactory.getLogger(FeignClientSymbolProvider.class);

@Override
protected void addSymbolsPass1(Annotation node, ITypeBinding annotationType, Collection<ITypeBinding> metaAnnotations,
SpringIndexerJavaContext context, TextDocument doc) {
try {
if (node != null && node.getParent() != null && node.getParent() instanceof TypeDeclaration) {
Two<EnhancedSymbolInformation, Bean> result = createSymbol(node, annotationType, metaAnnotations, doc);

EnhancedSymbolInformation enhancedSymbol = result.getFirst();
Bean beanDefinition = result.getSecond();
context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), enhancedSymbol));
context.getBeans().add(new CachedBean(context.getDocURI(), beanDefinition));
}
}
catch (Exception e) {
log.error("", e);
}
}

private Two<EnhancedSymbolInformation, Bean> createSymbol(Annotation node, ITypeBinding annotationType,
Collection<ITypeBinding> metaAnnotations, TextDocument doc) throws BadLocationException {
String annotationTypeName = annotationType.getName();
Collection<String> metaAnnotationNames = metaAnnotations.stream()
.map(ITypeBinding::getName)
.collect(Collectors.toList());

TypeDeclaration type = (TypeDeclaration) node.getParent();

String beanName = getBeanName(node, type);
ITypeBinding beanType = type.resolveBinding();

Location location = new Location(doc.getUri(), doc.toRange(node.getStartPosition(), node.getLength()));

WorkspaceSymbol symbol = new WorkspaceSymbol(
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))};

InjectionPoint[] injectionPoints = ASTUtils.findInjectionPoints(type, doc);

Set<String> supertypes = new HashSet<>();
ASTUtils.findSupertypes(beanType, supertypes);

String[] annotations = Stream.concat(Stream.of(annotationType), metaAnnotations.stream()).map(t -> t.getQualifiedName()).toArray(String[]::new);

Bean beanDefinition = new Bean(beanName, beanType == null ? "" : beanType.getQualifiedName(), location, injectionPoints, (String[]) supertypes.toArray(new String[supertypes.size()]), annotations);

return Tuple.two(new EnhancedSymbolInformation(symbol, addon), beanDefinition);
}

protected String beanLabel(String searchPrefix, String annotationTypeName, Collection<String> metaAnnotationNames, String beanName, String beanType) {
StringBuilder symbolLabel = new StringBuilder();
symbolLabel.append("@");
symbolLabel.append(searchPrefix);
symbolLabel.append(' ');
symbolLabel.append('\'');
symbolLabel.append(beanName);
symbolLabel.append('\'');
symbolLabel.append(" (@");
symbolLabel.append(annotationTypeName);
if (!metaAnnotationNames.isEmpty()) {
symbolLabel.append(" <: ");
boolean first = true;
for (String ma : metaAnnotationNames) {
if (!first) {
symbolLabel.append(", ");
}
symbolLabel.append("@");
symbolLabel.append(ma);
first = false;
}
}
symbolLabel.append(") ");
symbolLabel.append(beanType);
return symbolLabel.toString();
}

private String getBeanName(Annotation node, TypeDeclaration typeDecl) {
if (node.isSingleMemberAnnotation()) {
Object o = ((SingleMemberAnnotation)node).getValue().resolveConstantExpressionValue();
if (o instanceof String) {
return (String) o;
}
} else if (node.isNormalAnnotation()) {
NormalAnnotation normalAnnotation = (NormalAnnotation) node;
for (Object o : normalAnnotation.values()) {
if (o instanceof MemberValuePair) {
MemberValuePair pair = (MemberValuePair) o;
switch (pair.getName().getIdentifier()) {
case "name":
case "value":
Object obj = pair.getValue().resolveConstantExpressionValue();
if (obj instanceof String) {
return (String) obj;
}
}
}
}
}
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];
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,17 @@
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.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.java.IJavaProject;
import org.springframework.ide.vscode.commons.java.SpringProjectUtil;
import org.springframework.ide.vscode.commons.java.Version;
Expand All @@ -37,7 +45,7 @@
import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope;
import org.springframework.ide.vscode.commons.rewrite.java.FixDescriptor;

public class AddConfigurationIfBeansPresentReconciler implements JdtAstReconciler {
public class AddConfigurationIfBeansPresentReconciler implements JdtAstReconciler, ApplicationContextAware {

private static final String ID = AddConfigurationAnnotationIfBeansPresent.class.getName();

Expand All @@ -47,6 +55,8 @@ public class AddConfigurationIfBeansPresentReconciler implements JdtAstReconcile

private QuickfixRegistry quickfixRegistry;

private ApplicationContext applicationContext;

public AddConfigurationIfBeansPresentReconciler(QuickfixRegistry quickfixRegistry) {
this.quickfixRegistry = quickfixRegistry;
}
Expand All @@ -58,7 +68,7 @@ public void reconcile(IJavaProject project, URI docUri, CompilationUnit cu, IPro

@Override
public boolean visit(TypeDeclaration classDecl) {
if (isApplicableClass(cu, classDecl)) {
if (isApplicableClass(project, cu, classDecl)) {
SimpleName nameAst = classDecl.getName();
ReconcileProblemImpl problem = new ReconcileProblemImpl(getProblemType(), PROBLEM_LABEL,
nameAst.getStartPosition(), nameAst.getLength());
Expand All @@ -79,7 +89,7 @@ public boolean visit(TypeDeclaration classDecl) {
});
}

private static boolean isApplicableClass(CompilationUnit cu, TypeDeclaration classDecl) {
private boolean isApplicableClass(IJavaProject project, CompilationUnit cu, TypeDeclaration classDecl) {
if (classDecl.isInterface()) {
return false;
}
Expand Down Expand Up @@ -112,16 +122,47 @@ private static boolean isApplicableClass(CompilationUnit cu, TypeDeclaration cla
}
}
}

// No '@Configuration' present. Check if any methods have '@Bean' annotation
for (MethodDeclaration m : classDecl.getMethods()) {
if (isBeanMethod(m)) {
if (isBeanMethod(m) && !isException(project, classDecl)) {
return true;
}
}

return false;
}

private boolean isException(IJavaProject project, TypeDeclaration classDecl) {
if (applicationContext != null) {
SpringSymbolIndex index = applicationContext.getBean(SpringSymbolIndex.class);
if (index != null) {
final String beanClassName = RewriteQuickFixUtils.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;
}
}
}
}
}
}
}
}
}
return false;
}

private static boolean isBeanMethod(MethodDeclaration m) {
for (Iterator<?> itr = m.modifiers().iterator(); itr.hasNext();) {
Expand Down Expand Up @@ -149,4 +190,9 @@ public ProblemType getProblemType() {
return Boot2JavaProblemType.MISSING_CONFIGURATION_ANNOTATION;
}

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}

}
Loading

0 comments on commit 4093a0e

Please sign in to comment.