[boot-3] BeanPostProcessor ignored in AOT setup problem and quick fix

This commit is contained in:
aboyko
2022-09-29 20:31:48 -04:00
parent 3b08ec05ae
commit 311c276217
6 changed files with 270 additions and 3 deletions

View File

@@ -0,0 +1,150 @@
/*******************************************************************************
* Copyright (c) 2022 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.commons.rewrite.java;
import java.util.ArrayList;
import java.util.List;
import org.openrewrite.ExecutionContext;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.JavaParser;
import org.openrewrite.java.JavaTemplate;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaType;
import org.openrewrite.java.tree.TypeUtils;
public class BeanPostProcessingIgnoreInAot extends Recipe {
private static final String RUNTIME_BEAN_POST_PROCESSOR = "org.springframework.beans.factory.config.BeanPostProcessor";
private static final String COMPILE_BEAN_POST_PROCESSOR = "org.springframework.beans.factory.aot.BeanRegistrationAotProcessor";
public static final String METHOD_NAME = "isBeanExcludedFromAotProcessing";
@Override
public String getDisplayName() {
return "Prevent BeanPostProcessor from being ignored in AOT";
}
@Override
public String getDescription() {
return "Bean implementing both 'BeanPostProcessor' and 'BeanRegistrationAotProcessor' and does not return 'false' from 'isBeanExcludedFromAotProcessing()' method then Spring's behavior is to IGNORE the `BeanPostProcessor`."
+ " Recipe adds 'isBeanExcludedFromAotProcessing()' implemetation returning 'false' if necessary";
}
public static boolean isApplicableClass(J.ClassDeclaration classDecl) {
JavaType.FullyQualified type = classDecl.getType();
if (classDecl.getImplements() != null) {
return classDecl.getImplements().stream()
.anyMatch(f -> TypeUtils.isAssignableTo(RUNTIME_BEAN_POST_PROCESSOR, type)
&& TypeUtils.isAssignableTo(COMPILE_BEAN_POST_PROCESSOR, type));
}
return false;
}
public static boolean isApplicableMethod(J.MethodDeclaration m) {
return METHOD_NAME.equals(m.getSimpleName()) && m.getParameters().size() == 1 && m.getParameters().get(0) instanceof J.Empty;
}
public static boolean isReturnTrue(J.MethodDeclaration m) {
List<J.Return> returnStatements = findReturnStatementsInMethod(m);
if (returnStatements.size() == 1) {
J.Return returnStatement = returnStatements.get(0);
if (returnStatement.getExpression() instanceof J.Literal) {
if (((J.Literal) returnStatement.getExpression()).getValue() == Boolean.TRUE) {
// Method returns "true" boolean value constant
return true;
}
} else if (returnStatement.getExpression() instanceof J.FieldAccess) {
J.FieldAccess fa = (J.FieldAccess) returnStatement.getExpression();
if (fa.getName() != null && fa.getName().getSimpleName().equals("TRUE")) {
if (fa.getTarget() != null) {
JavaType.FullyQualified type = TypeUtils.asFullyQualified(fa.getTarget().getType());
// Method returns Boolean.TRUE
if ("java.lang.Boolean".equals(type.getFullyQualifiedName())) {
return true;
}
}
}
}
}
return false;
}
@Override
protected TreeVisitor<?, ExecutionContext> getApplicableTest() {
return new JavaIsoVisitor<ExecutionContext>() {
@Override
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext executionContext) {
if (isApplicableClass(classDecl)) {
return classDecl.withMarkers(classDecl.getMarkers().searchResult());
}
return super.visitClassDeclaration(classDecl, executionContext);
}
};
}
@Override
protected TreeVisitor<?, ExecutionContext> getVisitor() {
return new JavaIsoVisitor<ExecutionContext>() {
private JavaTemplate createTemplate() {
return JavaTemplate.builder(this::getCursor, "@Override\n"
+ "public boolean isBeanExcludedFromAotProcessing() {\n"
+ " return false;\n"
+ "}\n"
)
.javaParser(() -> JavaParser.fromJavaVersion().build())
.build();
}
@Override
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext executionContext) {
J.ClassDeclaration c = super.visitClassDeclaration(classDecl, executionContext);
if (isApplicableClass(classDecl)) {
J.MethodDeclaration method = c.getBody().getStatements().stream().filter(J.MethodDeclaration.class::isInstance).map(J.MethodDeclaration.class::cast).filter(BeanPostProcessingIgnoreInAot::isApplicableMethod).findFirst().orElse(null);
if (method == null) {
J.Block body = c.getBody().withTemplate(createTemplate(), classDecl.getBody().getCoordinates().addMethodDeclaration((m1, m2) -> 1));
c = c.withBody(body);
}
}
return c;
}
@Override
public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext executionContext) {
J.MethodDeclaration m = super.visitMethodDeclaration(method, executionContext);
if (isApplicableMethod(m)) {
J.ClassDeclaration c = getCursor().firstEnclosing(J.ClassDeclaration.class);
if (c != null && isApplicableClass(c) && isReturnTrue(m)) {
m = m.withTemplate(createTemplate(), m.getCoordinates().replace());
}
}
return m;
}
};
}
private static List<J.Return> findReturnStatementsInMethod(J.MethodDeclaration m) {
List<J.Return> returnStatements = new ArrayList<>();
new JavaIsoVisitor<List<J.Return>>() {
@Override
public J.Return visitReturn(J.Return _return, List<J.Return> returns) {
if (getCursor().firstEnclosing(J.MethodDeclaration.class) == m) {
returns.add(_return);
}
return _return;
}
}.visit(m, returnStatements);
return returnStatements;
}
}

View File

@@ -23,7 +23,9 @@ import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemTy
*/
public enum Boot3JavaProblemType implements ProblemType {
JAVA_CONCRETE_BEAN_TYPE(WARNING, "Bean definition should have precise type for Spring 6 AOT", "Not precise bean defintion type");
JAVA_CONCRETE_BEAN_TYPE(WARNING, "Bean definition should have precise type for Spring 6 AOT", "Not precise bean defintion type"),
JAVA_BEAN_NOT_REGISTERED_IN_AOT(WARNING, "'BeanPostProcessor' behaviour is ignored in Spring 6 AOT", "'BeanPostProcessor' behaviour is ignored in AOT");
private final ProblemSeverity defaultSeverity;
private String description;

View File

@@ -16,6 +16,7 @@ import org.springframework.ide.vscode.boot.java.rewrite.codeaction.AutowiredFiel
import org.springframework.ide.vscode.boot.java.rewrite.codeaction.BeanMethodsNotPublicCodeAction;
import org.springframework.ide.vscode.boot.java.rewrite.codeaction.NoRequestMappingAnnotationCodeAction;
import org.springframework.ide.vscode.boot.java.rewrite.codeaction.UnnecessarySpringExtensionCodeAction;
import org.springframework.ide.vscode.boot.java.rewrite.reconcile.BeanPostProcessingIgnoreInAotProblem;
import org.springframework.ide.vscode.boot.java.rewrite.reconcile.BeanMethodNotPublicProblem;
import org.springframework.ide.vscode.boot.java.rewrite.reconcile.NoAutowiredOnConstructorProblem;
import org.springframework.ide.vscode.boot.java.rewrite.reconcile.PreciseBeanTypeProblem;
@@ -42,7 +43,8 @@ public class BootCodeActionRepository extends CodeActionRepository {
new BeanMethodNotPublicProblem(),
new NoAutowiredOnConstructorProblem(),
new UnnecessarySpringExtensionProblem(),
new PreciseBeanTypeProblem()
new PreciseBeanTypeProblem(),
new BeanPostProcessingIgnoreInAotProblem()
);
}

View File

@@ -0,0 +1,95 @@
/*******************************************************************************
* Copyright (c) 2022 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.rewrite.reconcile;
import static org.springframework.ide.vscode.commons.java.SpringProjectUtil.springBootVersionGreaterOrEqual;
import java.util.List;
import java.util.stream.Collectors;
import org.openrewrite.ExecutionContext;
import org.openrewrite.Tree;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.JavaVisitor;
import org.openrewrite.java.tree.J.ClassDeclaration;
import org.openrewrite.java.tree.J.MethodDeclaration;
import org.openrewrite.marker.Range;
import org.springframework.ide.vscode.boot.java.Boot3JavaProblemType;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType;
import org.springframework.ide.vscode.commons.rewrite.config.RecipeCodeActionDescriptor;
import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope;
import org.springframework.ide.vscode.commons.rewrite.config.RecipeSpringJavaProblemDescriptor;
import org.springframework.ide.vscode.commons.rewrite.java.BeanPostProcessingIgnoreInAot;
import org.springframework.ide.vscode.commons.rewrite.java.FixAssistMarker;
public class BeanPostProcessingIgnoreInAotProblem implements RecipeSpringJavaProblemDescriptor {
private static final String LABEL = "Add method 'isBeanExcludedFromAotProcessing' that returns 'false'";
@Override
public String getRecipeId() {
return "org.springframework.ide.vscode.commons.rewrite.java.BeanPostProcessingIgnoreInAot";
}
@Override
public String getLabel(RecipeScope s) {
return RecipeCodeActionDescriptor.buildLabel(LABEL, s);
}
@Override
public RecipeScope[] getScopes() {
return RecipeScope.values();
}
@Override
public JavaVisitor<ExecutionContext> getMarkerVisitor() {
return new JavaIsoVisitor<ExecutionContext>() {
@Override
public ClassDeclaration visitClassDeclaration(ClassDeclaration classDecl, ExecutionContext p) {
ClassDeclaration c = super.visitClassDeclaration(classDecl, p);
if (BeanPostProcessingIgnoreInAot.isApplicableClass(classDecl)) {
List<MethodDeclaration> methods = classDecl.getBody().getStatements().stream()
.filter(MethodDeclaration.class::isInstance).map(MethodDeclaration.class::cast)
.filter(BeanPostProcessingIgnoreInAot::isApplicableMethod)
.collect(Collectors.toList());
FixAssistMarker marker = new FixAssistMarker(Tree.randomId())
.withRecipeId(getRecipeId())
.withScope(classDecl.getMarkers().findFirst(Range.class).orElse(null));
if (methods.isEmpty()) {
// Didn't find a method. Default implementation return true therefore mark it.
c = c.withName(c.getName().withMarkers(c.getName().getMarkers().add(marker)));
} else {
MethodDeclaration m = methods.stream().filter(BeanPostProcessingIgnoreInAot::isReturnTrue).findFirst().orElse(null);
// Found method that return true explicitly
if (m != null) {
c = c.withName(c.getName().withMarkers(c.getName().getMarkers().add(marker)));
}
}
}
return c;
}
};
}
@Override
public boolean isApplicable(IJavaProject project) {
return springBootVersionGreaterOrEqual(3, 0, 0).test(project);
}
@Override
public ProblemType getProblemType() {
return Boot3JavaProblemType.JAVA_BEAN_NOT_REGISTERED_IN_AOT;
}
}

View File

@@ -54,6 +54,12 @@
"label": "Not precise bean defintion type",
"description": "Bean definition should have precise type for Spring 6 AOT",
"defaultSeverity": "WARNING"
},
{
"code": "JAVA_BEAN_NOT_REGISTERED_IN_AOT",
"label": "'BeanPostProcessor' behaviour is ignored in AOT",
"description": "'BeanPostProcessor' behaviour is ignored in Spring 6 AOT",
"defaultSeverity": "WARNING"
}
]
},

View File

@@ -335,6 +335,18 @@
"HINT",
"ERROR"
]
},
"spring-boot.ls.problem.boot3.JAVA_BEAN_NOT_REGISTERED_IN_AOT": {
"type": "string",
"default": "WARNING",
"description": "'BeanPostProcessor' behaviour is ignored in Spring 6 AOT",
"enum": [
"IGNORE",
"INFO",
"WARNING",
"HINT",
"ERROR"
]
}
}
},
@@ -704,4 +716,4 @@
"extensionDependencies": [
"redhat.java"
]
}
}