Next batch of JDT reconcilers

This commit is contained in:
aboyko
2023-08-09 17:46:13 -04:00
parent a140519874
commit 1b17d55486
17 changed files with 1759 additions and 9 deletions

View File

@@ -18,12 +18,14 @@ import java.util.stream.Collectors;
import org.openrewrite.ExecutionContext;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.internal.ListUtils;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.JavaParser;
import org.openrewrite.java.JavaTemplate;
import org.openrewrite.java.MethodMatcher;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaType;
import org.openrewrite.java.tree.JavaType.FullyQualified;
public class DefineMethod extends Recipe {
@@ -87,11 +89,22 @@ public class DefineMethod extends Recipe {
.classpath(classpath.stream().map(s -> Paths.get(s)).collect(Collectors.toList())))
.imports(imports.toArray(new String[imports.size()])).build();
J.Block body = t.apply(getCursor(), classDecl.getBody().getCoordinates().addMethodDeclaration((m, n) -> 1));
// TODO: why did this return ClassDEclaration rather than Block??? Figure this out!!!
J.ClassDeclaration templateClass = t.apply(getCursor(), classDecl.getBody().getCoordinates().addMethodDeclaration((m, n) -> 1));
FullyQualified classType = c.getType();
if (classType != null) {
J.Block body = templateClass.getBody().withStatements(ListUtils.map(templateClass.getBody().getStatements(), s -> {
if (s instanceof J.MethodDeclaration) {
J.MethodDeclaration m = (J.MethodDeclaration) s;
return m.withMethodType(m.getMethodType().withDeclaringType(classType));
}
return s;
}));
c = c.withBody(body);
}
for (String fq : imports) {
maybeAddImport(fq);
}
c = c.withBody(body);
}
}
return c;

View File

@@ -14,14 +14,19 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ide.vscode.boot.java.reconcilers.AddConfigurationIfBeansPresentReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.AnnotationNodeReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.AuthorizeHttpRequestsReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.AutowiredFieldIntoConstructorParameterReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.BeanMethodNotPublicReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.BeanPostProcessingIgnoreInAotReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.Boot3NotSupportedTypeReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.HttpSecurityLambdaDslReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.ModulithTypeReferenceViolationReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.NoAutowiredOnConstructorReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.NoRepoAnnotationReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.NoRequestMappingAnnotationReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.NotRegisteredBeansReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.PreciseBeanTypeReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.ServerHttpSecurityLambdaDslReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.UnnecessarySpringExtensionReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.WebSecurityConfigurerAdapterReconciler;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
@@ -76,5 +81,25 @@ public class JdtConfig {
@Bean UnnecessarySpringExtensionReconciler unnecessarySpringExtensionReconciler(SimpleLanguageServer server) {
return new UnnecessarySpringExtensionReconciler(server.getQuickfixRegistry());
}
@Bean HttpSecurityLambdaDslReconciler httpSecurityLamdaDslReconciler(SimpleLanguageServer server) {
return new HttpSecurityLambdaDslReconciler(server.getQuickfixRegistry());
}
@Bean ServerHttpSecurityLambdaDslReconciler serverHttpSecurityLambdaDslReconciler(SimpleLanguageServer server) {
return new ServerHttpSecurityLambdaDslReconciler(server.getQuickfixRegistry());
}
@Bean AuthorizeHttpRequestsReconciler authorizeHttpRequestsReconciler(SimpleLanguageServer server) {
return new AuthorizeHttpRequestsReconciler(server.getQuickfixRegistry());
}
@Bean BeanPostProcessingIgnoreInAotReconciler beanPostProcessingIgnoreInAotReconciler(SimpleLanguageServer server) {
return new BeanPostProcessingIgnoreInAotReconciler(server.getQuickfixRegistry());
}
@Bean NotRegisteredBeansReconciler notRegisteredBeansReconciler(SimpleLanguageServer server) {
return new NotRegisteredBeansReconciler(server.getQuickfixRegistry());
}
}

View File

@@ -0,0 +1,93 @@
/*******************************************************************************
* 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.reconcilers;
import java.net.URI;
import java.util.Collection;
import java.util.List;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MethodInvocation;
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.IProblemCollector;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl;
import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope;
import org.springframework.ide.vscode.commons.rewrite.java.FixDescriptor;
public abstract class AbstractSecurityLamdaDslReconciler implements JdtAstReconciler {
private QuickfixRegistry registry;
AbstractSecurityLamdaDslReconciler(QuickfixRegistry registry) {
this.registry = registry;
}
@Override
public void reconcile(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector,
boolean isCompleteAst) throws RequiredCompleteAstException {
if (isCompleteAst) {
cu.accept(new ASTVisitor() {
@Override
public boolean visit(MethodInvocation node) {
if (getApplicableMethodNames().contains(node.getName().getIdentifier()) && node.arguments().isEmpty()) {
ITypeBinding type = node.getExpression().resolveTypeBinding();
if (type != null && getTargetTypeFqName().equals(type.getQualifiedName())) {
MethodInvocation topMethodInvocation = findTopLevelMethodInvocation(node);
ReconcileProblemImpl problem = new ReconcileProblemImpl(getProblemType(), getProblemLabel(), topMethodInvocation.getStartPosition(), topMethodInvocation.getLength());
String uri = docUri.toASCIIString();
RewriteQuickFixUtils.setRewriteFixes(registry, problem, List.of(
new FixDescriptor(getRecipeId(), List.of(uri),
RewriteQuickFixUtils.buildLabel(getFixLabel(), RecipeScope.NODE))
.withRangeScope(RewriteQuickFixUtils.createOpenRewriteRange(cu, topMethodInvocation))
.withRecipeScope(RecipeScope.NODE),
new FixDescriptor(getRecipeId(), List.of(uri),
RewriteQuickFixUtils.buildLabel(getFixLabel(), RecipeScope.FILE))
.withRecipeScope(RecipeScope.FILE),
new FixDescriptor(getRecipeId(), List.of(uri),
RewriteQuickFixUtils.buildLabel(getFixLabel(), RecipeScope.PROJECT))
.withRecipeScope(RecipeScope.PROJECT)
));
problemCollector.accept(problem);
return false;
}
}
return true;
}
});
} else {
if (RewriteQuickFixUtils.isAnyTypeUsed(cu, List.of(getTargetTypeFqName()))) {
throw new RequiredCompleteAstException();
}
}
}
private static MethodInvocation findTopLevelMethodInvocation(MethodInvocation m) {
for (; m.getParent() instanceof MethodInvocation; m = (MethodInvocation) m.getParent()) {}
return m;
}
protected abstract String getFixLabel();
protected abstract String getRecipeId();
protected abstract String getProblemLabel();
abstract protected String getTargetTypeFqName();
abstract protected Collection<String> getApplicableMethodNames();
}

View File

@@ -0,0 +1,148 @@
/*******************************************************************************
* 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.reconcilers;
import java.net.URI;
import java.util.List;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.SimpleType;
import org.openrewrite.java.spring.boot2.AuthorizeHttpRequests;
import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.java.SpringProjectUtil;
import org.springframework.ide.vscode.commons.java.Version;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry;
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.rewrite.config.RecipeScope;
import org.springframework.ide.vscode.commons.rewrite.java.FixDescriptor;
public class AuthorizeHttpRequestsReconciler implements JdtAstReconciler {
private static final String ID = AuthorizeHttpRequests.class.getName();
private static final String FQN_HTTP_SECURITY = "org.springframework.security.config.annotation.web.builders.HttpSecurity";
private static final String AUTHORIZE_REQUESTS = "authorizeRequests";
private static final String AUTHORIZE_REQUESTS_PROBLEM_LABEL = "HttpSecurity API 'authorizeRequests(...)' is outdated";
private static final String AUTHORIZE_REQUESTS_FIX_LABEL = "Replace with 'authorizeHttpRequests(...)' and related types";
private static final String CLASS_FIX_LABEL_TEMPLATE = "Replace with %s and use 'HttpSecurity.authorizeHttpRequests(...) and related types";
private static final String FQN_AUTH_REQ_CONFIG = "org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer";
private static final String FQN_EXPR_AUTH_CONFIG = "org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer";
private static final String FQN_EXPR_INTERCEPT_REG = "org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry";
private QuickfixRegistry registry;
public AuthorizeHttpRequestsReconciler(QuickfixRegistry registry) {
this.registry = registry;
}
@Override
public void reconcile(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector,
boolean isCompleteAst) throws RequiredCompleteAstException {
if (isCompleteAst) {
cu.accept(new ASTVisitor() {
@Override
public boolean visit(MethodInvocation node) {
if (AUTHORIZE_REQUESTS.equals(node.getName().getIdentifier()) && node.arguments().isEmpty()) {
ITypeBinding type = node.getExpression().resolveTypeBinding();
if (type != null && FQN_HTTP_SECURITY.equals(type.getQualifiedName())) {
ReconcileProblemImpl problem = new ReconcileProblemImpl(getProblemType(),
AUTHORIZE_REQUESTS_PROBLEM_LABEL, node.getName().getStartPosition(),
node.getName().getLength());
String uri = docUri.toASCIIString();
RewriteQuickFixUtils
.setRewriteFixes(registry, problem, List.of(
new FixDescriptor(ID, List.of(uri),
RewriteQuickFixUtils.buildLabel(AUTHORIZE_REQUESTS_FIX_LABEL,
RecipeScope.FILE))
.withRecipeScope(RecipeScope.FILE),
new FixDescriptor(ID, List.of(uri),
RewriteQuickFixUtils.buildLabel(AUTHORIZE_REQUESTS_FIX_LABEL,
RecipeScope.PROJECT))
.withRecipeScope(RecipeScope.PROJECT)));
problemCollector.accept(problem);
return false;
}
}
return true;
}
@Override
public boolean visit(SimpleType node) {
String replacementClass = null;
if (RewriteQuickFixUtils.isApplicableTypeWithoutResolving(cu,
List.of(FQN_AUTH_REQ_CONFIG, FQN_EXPR_AUTH_CONFIG), node.getName())) {
replacementClass = "AuthorizeHttpRequestsConfigurer";
} else if (RewriteQuickFixUtils.isApplicableTypeWithoutResolving(cu, List.of(FQN_EXPR_INTERCEPT_REG),
node.getName())) {
replacementClass = "AuthorizationManagerRequestMatcherRegistry";
}
if (replacementClass != null) {
ReconcileProblemImpl problem = new ReconcileProblemImpl(getProblemType(),
"Use of type '" + node.getName().getFullyQualifiedName() + "' is outdated",
node.getName().getStartPosition(), node.getName().getLength());
String uri = docUri.toASCIIString();
RewriteQuickFixUtils
.setRewriteFixes(registry, problem, List.of(
new FixDescriptor(ID, List.of(uri),
RewriteQuickFixUtils.buildLabel(String.format(CLASS_FIX_LABEL_TEMPLATE,
replacementClass), RecipeScope.FILE))
.withRecipeScope(RecipeScope.FILE),
new FixDescriptor(ID, List.of(uri),
RewriteQuickFixUtils.buildLabel(
String.format(CLASS_FIX_LABEL_TEMPLATE, replacementClass),
RecipeScope.PROJECT))
.withRecipeScope(RecipeScope.PROJECT)));
problemCollector.accept(problem);
return false;
}
return super.visit(node);
}
});
} else {
boolean needsFullAst = RewriteQuickFixUtils.isAnyTypeUsed(cu, List.of(
FQN_HTTP_SECURITY,
FQN_AUTH_REQ_CONFIG,
FQN_EXPR_AUTH_CONFIG,
FQN_EXPR_INTERCEPT_REG
));
if (needsFullAst) {
throw new RequiredCompleteAstException();
}
}
}
@Override
public boolean isApplicable(IJavaProject project) {
Version version = SpringProjectUtil.getDependencyVersion(project, "spring-security-config");
return version != null && version.compareTo(new Version(5, 6, 0, null)) >= 0;
}
@Override
public ProblemType getProblemType() {
return Boot2JavaProblemType.HTTP_SECURITY_AUTHORIZE_HTTP_REQUESTS;
}
}

View File

@@ -85,10 +85,12 @@ public class BeanMethodNotPublicReconciler implements JdtAstReconciler {
private static final boolean isOverriding(IMethodBinding binding) {
try {
Field f = binding.getClass().getDeclaredField("binding");
f.setAccessible(true);
org.eclipse.jdt.internal.compiler.lookup.MethodBinding value = (org.eclipse.jdt.internal.compiler.lookup.MethodBinding) f.get(binding);
return value.isOverriding();
if (binding != null) {
Field f = binding.getClass().getDeclaredField("binding");
f.setAccessible(true);
org.eclipse.jdt.internal.compiler.lookup.MethodBinding value = (org.eclipse.jdt.internal.compiler.lookup.MethodBinding) f.get(binding);
return value.isOverriding();
}
} catch (Exception e) {
log.error("", e);
}

View File

@@ -0,0 +1,120 @@
/*******************************************************************************
* 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.reconcilers;
import static org.springframework.ide.vscode.commons.java.SpringProjectUtil.springBootVersionGreaterOrEqual;
import java.net.URI;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.ReturnStatement;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.springframework.ide.vscode.boot.java.SpringAotJavaProblemType;
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.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.rewrite.config.RecipeScope;
import org.springframework.ide.vscode.commons.rewrite.java.BeanPostProcessingIgnoreInAot;
import org.springframework.ide.vscode.commons.rewrite.java.FixDescriptor;
public class BeanPostProcessingIgnoreInAotReconciler implements JdtAstReconciler {
private static final String RECIPE_ID = BeanPostProcessingIgnoreInAot.class.getName();
private static final String LABEL = "Add method 'isBeanExcludedFromAotProcessing' that returns 'false'";
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";
private QuickfixRegistry registry;
public BeanPostProcessingIgnoreInAotReconciler(QuickfixRegistry registry) {
this.registry = registry;
}
@Override
public void reconcile(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector,
boolean isCompleteAst) throws RequiredCompleteAstException {
AtomicBoolean requiresFullAst = new AtomicBoolean(false);
cu.accept(new ASTVisitor() {
@Override
public boolean visit(TypeDeclaration typeDecl) {
ITypeBinding type = typeDecl.resolveBinding();
if (type != null && isApplicable(type)) {
MethodDeclaration foundMethod = null;
boolean markProblem = false;
for (MethodDeclaration m : typeDecl.getMethods()) {
if (METHOD_NAME.equals(m.getName().getIdentifier()) && m.parameters().isEmpty()) {
foundMethod = m;
break;
}
}
if (foundMethod != null) {
if (isCompleteAst) {
AtomicBoolean returnsTrue = new AtomicBoolean(false);
foundMethod.accept(new ASTVisitor() {
@Override
public boolean visit(ReturnStatement node) {
if (Boolean.TRUE.equals(node.getExpression().resolveConstantExpressionValue())) {
returnsTrue.set(true);
}
return !returnsTrue.get();
}
});
markProblem = returnsTrue.get();
} else {
requiresFullAst.set(true);
}
} else {
markProblem = true;
}
if (markProblem) {
ReconcileProblemImpl problem = new ReconcileProblemImpl(getProblemType(), LABEL, typeDecl.getName().getStartPosition(), typeDecl.getName().getLength());
RewriteQuickFixUtils.setRewriteFixes(registry, problem, List.of(
new FixDescriptor(RECIPE_ID, List.of(docUri.toASCIIString()), RewriteQuickFixUtils.buildLabel(LABEL, RecipeScope.NODE))
.withRangeScope(RewriteQuickFixUtils.createOpenRewriteRange(cu, typeDecl))
.withRecipeScope(RecipeScope.NODE)
));
problemCollector.accept(problem);
}
}
return !requiresFullAst.get();
}
private boolean isApplicable(ITypeBinding type) {
return RewriteQuickFixUtils.implementsType(RUNTIME_BEAN_POST_PROCESSOR, type) && RewriteQuickFixUtils.implementsType(COMPILE_BEAN_POST_PROCESSOR, type);
}
});
if (requiresFullAst.get()) {
throw new RequiredCompleteAstException();
}
}
@Override
public boolean isApplicable(IJavaProject project) {
return springBootVersionGreaterOrEqual(3, 0, 0).test(project);
}
@Override
public ProblemType getProblemType() {
return SpringAotJavaProblemType.JAVA_BEAN_POST_PROCESSOR_IGNORED_IN_AOT;
}
}

View File

@@ -0,0 +1,55 @@
/*******************************************************************************
* 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.reconcilers;
import static org.springframework.ide.vscode.commons.java.SpringProjectUtil.springBootVersionGreaterOrEqual;
import java.net.URI;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.openrewrite.java.spring.boot2.search.EntityIdForRepositoryVisitor;
import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.java.SpringProjectUtil;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType;
public class EntityIdForRepoReconciler implements JdtAstReconciler {
private static final String ID = EntityIdForRepositoryVisitor.class.getName();
private QuickfixRegistry registry;
public EntityIdForRepoReconciler(QuickfixRegistry registry) {
this.registry = registry;
}
@Override
public void reconcile(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector,
boolean isCompleteAst) throws RequiredCompleteAstException {
final boolean considerIdField = SpringProjectUtil.hasSpecificLibraryOnClasspath(project, "spring-data-mongodb-", true);
cu.accept(new ASTVisitor() {
});
}
@Override
public boolean isApplicable(IJavaProject project) {
return springBootVersionGreaterOrEqual(2, 0, 0).test(project);
}
@Override
public ProblemType getProblemType() {
return Boot2JavaProblemType.DOMAIN_ID_FOR_REPOSITORY;
}
}

View File

@@ -0,0 +1,73 @@
/*******************************************************************************
* 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.reconcilers;
import java.util.Arrays;
import java.util.Collection;
import org.openrewrite.java.spring.boot2.HttpSecurityLambdaDsl;
import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.java.SpringProjectUtil;
import org.springframework.ide.vscode.commons.java.Version;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType;
public class HttpSecurityLambdaDslReconciler extends AbstractSecurityLamdaDslReconciler {
private static final Collection<String> APPLICABLE_METHOD_NAMES = Arrays.asList(
"anonymous", "authorizeRequests", "cors", "csrf", "exceptionHandling", "formLogin",
"headers", "httpBasic", "jee", "logout", "oauth2Client", "oauth2Login", "oauth2ResourceServer",
"openidLogin", "portMapper", "rememberMe", "requestCache", "requestMatchers", "requiresChannel",
"saml2Login", "securityContext", "servletApi", "sessionManagement", "x509");
public HttpSecurityLambdaDslReconciler(QuickfixRegistry registry) {
super(registry);
}
@Override
public boolean isApplicable(IJavaProject project) {
Version version = SpringProjectUtil.getDependencyVersion(project, "spring-security-config");
return version != null && version.compareTo(new Version(5, 2, 0, null)) >= 0;
}
@Override
public ProblemType getProblemType() {
return Boot2JavaProblemType.JAVA_LAMBDA_DSL;
}
@Override
protected String getFixLabel() {
return "Switch to 'HttpSecurity` Lambda DSL syntax";
}
@Override
protected String getRecipeId() {
return HttpSecurityLambdaDsl.class.getName();
}
@Override
protected String getProblemLabel() {
return "Consider switching to 'HttpSecurity' Lambda DSL syntax";
}
@Override
protected String getTargetTypeFqName() {
return "org.springframework.security.config.annotation.web.builders.HttpSecurity";
}
@Override
protected Collection<String> getApplicableMethodNames() {
return APPLICABLE_METHOD_NAMES;
}
}

View File

@@ -0,0 +1,207 @@
/*******************************************************************************
* 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.reconcilers;
import static org.springframework.ide.vscode.commons.java.SpringProjectUtil.springBootVersionGreaterOrEqual;
import java.net.URI;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.lsp4j.WorkspaceSymbol;
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.SpringAotJavaProblemType;
import org.springframework.ide.vscode.boot.java.beans.BeansSymbolAddOnInformation;
import org.springframework.ide.vscode.boot.java.beans.ConfigBeanSymbolAddOnInformation;
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.IClasspathUtil;
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.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.rewrite.config.RecipeScope;
import org.springframework.ide.vscode.commons.rewrite.java.DefineMethod;
import org.springframework.ide.vscode.commons.rewrite.java.FixDescriptor;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.ImmutableSet;
public class NotRegisteredBeansReconciler implements JdtAstReconciler, ApplicationContextAware {
private static final String DEFINE_METHOD_RECIPE = DefineMethod.class.getName();
private static final List<String> AOT_BEANS = List.of(
"org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor",
"org.springframework.beans.factory.aot.BeanRegistrationAotProcessor"
);
private ApplicationContext applicationContext;
private QuickfixRegistry registry;
public NotRegisteredBeansReconciler(QuickfixRegistry registry) {
this.registry = registry;
}
@Override
public void reconcile(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector,
boolean isCompleteAst) throws RequiredCompleteAstException {
cu.accept(new ASTVisitor() {
@Override
public boolean visit(TypeDeclaration node) {
if (!node.isInterface() && !Modifier.isAbstract(node.getModifiers())) {
ITypeBinding type = node.resolveBinding();
if (type != null && RewriteQuickFixUtils.implementsAnyType(AOT_BEANS, type)) {
String beanClassName =type.getQualifiedName();
SpringSymbolIndex index = applicationContext.getBean(SpringSymbolIndex.class);
List<WorkspaceSymbol> beanSymbols = index.getSymbols(data -> {
SymbolAddOnInformation[] additionalInformation = data.getAdditionalInformation();
if (additionalInformation != null) {
for (SymbolAddOnInformation info : additionalInformation) {
if (info instanceof BeansSymbolAddOnInformation) {
BeansSymbolAddOnInformation info2 = (BeansSymbolAddOnInformation) info;
return beanClassName.equals(info2.getBeanType());
}
}
}
return false;
}).limit(1).collect(Collectors.toList());
if (beanSymbols.isEmpty()) {
Builder<FixDescriptor> fixListBuilder = ImmutableList.builder();
for (EnhancedSymbolInformation s : index.getEnhancedSymbols(project)) {
if (s.getAdditionalInformation() != null) {
ConfigBeanSymbolAddOnInformation configInfo = Arrays.stream(s.getAdditionalInformation()).filter(ConfigBeanSymbolAddOnInformation.class::isInstance).map(ConfigBeanSymbolAddOnInformation.class::cast).findFirst().orElse(null);
if (configInfo != null) {
for (IMethodBinding constructor : type.getDeclaredMethods()) {
if (constructor.isConstructor()) {
String constructorParamsSignature = "(" + Arrays.stream(constructor.getParameterTypes()).map(pt -> typePattern(pt)).collect(Collectors.joining(",")) + ")";
String beanMethodName = "get" + type.getName();
String pattern = beanMethodName + constructorParamsSignature;
String contructorParamsLabel = "(" + Arrays.stream(constructor.getParameterTypes()).map(NotRegisteredBeansReconciler::typeStr).collect(Collectors.joining(", ")) + ")";
Builder<String> paramBuilder = ImmutableList.builder();
for (int i = 0; i < constructor.getParameterNames().length && i < constructor.getParameterTypes().length; i++) {
ITypeBinding paramType = constructor.getParameterTypes()[i];
String paramName = constructor.getParameterNames()[i];
paramBuilder.add(typeStr(paramType) + ' ' + paramName);
}
String paramsStr = String.join(", ", paramBuilder.build().toArray(String[]::new));
fixListBuilder.add(new FixDescriptor(DEFINE_METHOD_RECIPE, List.of(s.getSymbol().getLocation().getLeft().getUri()), "Define bean in config '" + configInfo.getBeanID() + "' with constructor " + contructorParamsLabel)
.withRecipeScope(RecipeScope.FILE)
.withParameters(Map.of(
"targetFqName", configInfo.getBeanType(),
"signature", pattern,
"template", "@Bean\n"
+ type.getName() + " " + beanMethodName + "(" + paramsStr + ") {\n"
+ "return new " + type.getName() + "(" + Arrays.stream(constructor.getParameterNames()).collect(Collectors.joining(", ")) + ");\n"
+ "}\n",
"imports", allFQTypes(constructor).toArray(String[]::new),
"typeStubs", new String[0]/*new String[] { source.printAll() }*/,
"classpath", IClasspathUtil.getAllBinaryRoots(project.getClasspath()).stream().map(f -> f.toPath().toString()).toArray(String[]::new)
))
);
}
}
}
}
}
ReconcileProblemImpl problem = new ReconcileProblemImpl(getProblemType(), getProblemType().getLabel(), node.getName().getStartPosition(), node.getName().getLength());
RewriteQuickFixUtils.setRewriteFixes(registry, problem, fixListBuilder.build());
problemCollector.accept(problem);
}
}
}
return super.visit(node);
}
});
}
@Override
public boolean isApplicable(IJavaProject project) {
return springBootVersionGreaterOrEqual(3, 0, 0).test(project);
}
@Override
public ProblemType getProblemType() {
return SpringAotJavaProblemType.JAVA_BEAN_NOT_REGISTERED_IN_AOT;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
private static Set<String> allFQTypes(IBinding binding) {
ImmutableSet.Builder<String> b = ImmutableSet.builder();
if (binding instanceof IMethodBinding) {
IMethodBinding methodBinding = (IMethodBinding) binding;
b.addAll(allFQTypes(methodBinding.getDeclaringClass()));
for (ITypeBinding paramType : methodBinding.getParameterTypes()) {
b.addAll(allFQTypes(paramType));
}
} else if (binding instanceof ITypeBinding) {
ITypeBinding typeBinding = (ITypeBinding) binding;
if (!typeBinding.isPrimitive()) {
for (ITypeBinding paramType : typeBinding.getTypeParameters()) {
b.addAll(allFQTypes(paramType));
}
for (ITypeBinding argType : typeBinding.getTypeArguments()) {
b.addAll(allFQTypes(argType));
}
b.addAll(allFQTypes(typeBinding.getBound()));
ITypeBinding erasure = typeBinding.getErasure();
if (erasure == typeBinding) {
b.add(typeBinding.getQualifiedName());
} else {
b.addAll(allFQTypes(erasure));
}
}
}
return b.build();
}
private static String typePattern(ITypeBinding type) {
if (type.isArray()) {
return typePattern(type.getErasure()) + "[]";
} else {
return RewriteQuickFixUtils.getDeepErasureType(type).getQualifiedName();
}
}
private static String typeStr(ITypeBinding type) {
return type.getName();
}
}

View File

@@ -12,12 +12,17 @@ package org.springframework.ide.vscode.boot.java.reconcilers;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.SimpleType;
import org.openrewrite.Tree;
import org.openrewrite.marker.Range;
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
@@ -90,6 +95,87 @@ public class RewriteQuickFixUtils {
return type;
}
public static boolean isApplicableTypeWithoutResolving(CompilationUnit cu, Collection<String> types, Name typeNameNode) {
String typeName = typeNameNode.getFullyQualifiedName();
if (cu.getPackage().getName() != null && types.contains(cu.getPackage().getName().getFullyQualifiedName() + "." + typeName)) {
return true;
}
if (types.contains(typeName)) {
return true;
} else if (types.stream().anyMatch(t -> t.endsWith(typeName))) {
for (Object im : cu.imports()) {
ImportDeclaration importDecl = (ImportDeclaration) im;
String importFqName = importDecl.getName().getFullyQualifiedName();
if (importDecl.isOnDemand()) {
if (types.contains(importFqName + "." + typeName)) {
return true;
}
} else {
String importSimpleName = getSimpleName(importFqName);
String firstTokenOfTypeName = getFirstTokenBeforeDot(typeName);
if (importSimpleName.equals(firstTokenOfTypeName)) {
if (types.contains(importFqName + typeName.substring(firstTokenOfTypeName.length()))) {
return true;
}
}
}
}
}
return false;
}
public static boolean isAnyTypeUsed(CompilationUnit cu, Collection<String> types) {
AtomicBoolean typeUsed = new AtomicBoolean(false);
cu.accept(new ASTVisitor() {
@Override
public boolean visit(ImportDeclaration node) {
String fqName = node.getName().getFullyQualifiedName();
if (types.contains(fqName)) {
typeUsed.set(true);
}
return !typeUsed.get();
}
@Override
public boolean visit(SimpleType node) {
if (RewriteQuickFixUtils.isApplicableTypeWithoutResolving(cu, types, node.getName())) {
typeUsed.set(true);
}
return !typeUsed.get();
}
});
return typeUsed.get();
}
public static boolean implementsType(String fqName, ITypeBinding type) {
if (fqName.equals(type.getQualifiedName())) {
return true;
} else {
for (ITypeBinding t : type.getInterfaces()) {
if (implementsType(fqName, t)) {
return true;
}
}
}
return false;
}
public static boolean implementsAnyType(Collection<String> fqNames, ITypeBinding type) {
if (fqNames.contains(type.getQualifiedName())) {
return true;
} else {
for (ITypeBinding t : type.getInterfaces()) {
if (implementsAnyType(fqNames, t)) {
return true;
}
}
}
return false;
}
public static String getSimpleName(String fqName) {
int idx = fqName.lastIndexOf('.');
if (idx >= 0 && idx < fqName.length() - 1) {

View File

@@ -0,0 +1,71 @@
/*******************************************************************************
* 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.reconcilers;
import java.util.Arrays;
import java.util.Collection;
import org.openrewrite.java.spring.boot2.ServerHttpSecurityLambdaDsl;
import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.java.SpringProjectUtil;
import org.springframework.ide.vscode.commons.java.Version;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType;
public class ServerHttpSecurityLambdaDslReconciler extends AbstractSecurityLamdaDslReconciler {
private static final Collection<String> APPLICABLE_METHOD_NAMES = Arrays.asList("anonymous", "authorizeExchange",
"cors", "csrf", "exceptionHandling", "formLogin", "headers", "httpBasic", "logout", "oauth2Client",
"oauth2Login", "oauth2ResourceServer", "redirectToHttps", "requestCache", "x509");
public ServerHttpSecurityLambdaDslReconciler(QuickfixRegistry registry) {
super(registry);
// TODO Auto-generated constructor stub
}
@Override
public boolean isApplicable(IJavaProject project) {
Version version = SpringProjectUtil.getDependencyVersion(project, "spring-security-config");
return version != null && version.compareTo(new Version(5, 2, 0, null)) >= 0;
}
@Override
public ProblemType getProblemType() {
return Boot2JavaProblemType.JAVA_LAMBDA_DSL;
}
@Override
protected String getFixLabel() {
return "Switch to 'ServerHttpSecurity` Lambda DSL syntax";
}
@Override
protected String getRecipeId() {
return ServerHttpSecurityLambdaDsl.class.getName();
}
@Override
protected String getProblemLabel() {
return "Consider switching to 'ServerHttpSecurity' Lambda DSL syntax";
}
@Override
protected String getTargetTypeFqName() {
return "org.springframework.security.config.web.server.ServerHttpSecurity";
}
@Override
protected Collection<String> getApplicableMethodNames() {
return APPLICABLE_METHOD_NAMES;
}
}

View File

@@ -0,0 +1,198 @@
/*******************************************************************************
* 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.reconcilers.test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import java.util.List;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType;
import org.springframework.ide.vscode.boot.java.reconcilers.AuthorizeHttpRequestsReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.JdtAstReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.RequiredCompleteAstException;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem;
public class AuthorizeHttpRequestsReconcilerTest extends BaseReconcilerTest {
@Override
protected String getFolder() {
return "authorizerequests";
}
@Override
protected String getProjectName() {
return "test-spring-indexing";
}
@Override
protected JdtAstReconciler getReconciler() {
return new AuthorizeHttpRequestsReconciler(new QuickfixRegistry());
}
@BeforeEach
void setup() throws Exception {
super.setup();
}
@AfterEach
void tearDown() throws Exception {
super.tearDown();
}
@Test
void requireFullAst_1() throws Exception {
String source = """
package example.demo;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
class A {
void something(HttpSecurity security) {
};
}
""";
try {
reconcile("A.java", source, false);
fail("Should require full AST with method bodies");
} catch (RequiredCompleteAstException e) {
// pass
}
}
@Test
void requireFullAst_2() throws Exception {
String source = """
package example.demo;
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
class A {
void something() {
};
}
""";
try {
reconcile("A.java", source, false);
fail("Should require full AST with method bodies");
} catch (RequiredCompleteAstException e) {
// pass
}
}
@Test
void requireFullAst_3() throws Exception {
String source = """
package example.demo;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
class A {
void something(ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry reg) {
};
}
""";
try {
reconcile("A.java", source, false);
fail("Should require full AST with method bodies");
} catch (RequiredCompleteAstException e) {
// pass
}
}
@Test
void sanityTest() throws Exception {
String source = """
package example.demo;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
class A {
void something(HttpSecurity security) {
security.authorizeRequests().mvcMatchers();
};
}
""";
List<ReconcileProblem> problems = reconcile("A.java", source, true);
assertEquals(1, problems.size());
ReconcileProblem problem = problems.get(0);
assertEquals(Boot2JavaProblemType.HTTP_SECURITY_AUTHORIZE_HTTP_REQUESTS, problem.getType());
String markedStr = source.substring(problem.getOffset(), problem.getOffset() + problem.getLength());
assertEquals("authorizeRequests", markedStr);
assertEquals(2, problem.getQuickfixes().size());
}
@Test
void multipleProblems() throws Exception {
String source = """
package example.demo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class JdbcSecurityConfiguration {
@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry reqs = http.authorizeRequests();
reqs.antMatchers("/ll").authenticated();
return http.build();
}
}
""";
List<ReconcileProblem> problems = reconcile("A.java", source, true);
assertEquals(2, problems.size());
ReconcileProblem problem = problems.get(0);
assertEquals(Boot2JavaProblemType.HTTP_SECURITY_AUTHORIZE_HTTP_REQUESTS, problem.getType());
String markedStr = source.substring(problem.getOffset(), problem.getOffset() + problem.getLength());
assertEquals("ExpressionUrlAuthorizationConfigurer", markedStr);
assertEquals(2, problem.getQuickfixes().size());
problem = problems.get(1);
assertEquals(Boot2JavaProblemType.HTTP_SECURITY_AUTHORIZE_HTTP_REQUESTS, problem.getType());
markedStr = source.substring(problem.getOffset(), problem.getOffset() + problem.getLength());
assertEquals("authorizeRequests", markedStr);
assertEquals(2, problem.getQuickfixes().size());
}
}

View File

@@ -21,6 +21,7 @@ import java.nio.file.Paths;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FileASTRequestor;
@@ -42,7 +43,7 @@ public abstract class BaseReconcilerTest {
abstract protected JdtAstReconciler getReconciler();
private Path createFile(String name, String content) throws IOException {
protected Path createFile(String name, String content) throws IOException {
Path filePath = Paths.get(project.getLocationUri()).resolve("src/main/java").resolve(getFolder()).resolve(name);
Files.createDirectories(filePath.getParent());
Files.createFile(filePath);
@@ -75,6 +76,10 @@ public abstract class BaseReconcilerTest {
}
List<ReconcileProblem> reconcile(String fileName, String source, boolean isCompleteAst) throws Exception {
return reconcile(this::getReconciler, fileName, source, isCompleteAst);
}
List<ReconcileProblem> reconcile(Supplier<JdtAstReconciler> reconcilerFactory, String fileName, String source, boolean isCompleteAst) throws Exception {
Path path = createFile(fileName, source);
TestProblemCollector problemCollector = new TestProblemCollector();
AtomicBoolean requiredCompleteAst = new AtomicBoolean(false);
@@ -83,7 +88,7 @@ public abstract class BaseReconcilerTest {
@Override
public void acceptAST(String sourceFilePath, CompilationUnit cu) {
try {
getReconciler().reconcile(project, path.toUri(), cu, problemCollector, isCompleteAst);
reconcilerFactory.get().reconcile(project, path.toUri(), cu, problemCollector, isCompleteAst);
} catch (RequiredCompleteAstException e) {
requiredCompleteAst.set(true);
}
@@ -97,5 +102,4 @@ public abstract class BaseReconcilerTest {
return problemCollector.getCollectedProblems();
}
}

View File

@@ -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.reconcilers.test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import java.util.List;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.ide.vscode.boot.java.SpringAotJavaProblemType;
import org.springframework.ide.vscode.boot.java.reconcilers.BeanPostProcessingIgnoreInAotReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.JdtAstReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.RequiredCompleteAstException;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem;
public class BeanPostProcessingIgnoreInAotReconcilerTest extends BaseReconcilerTest {
@Override
protected String getFolder() {
return "beanpostprocessingaot";
}
@Override
protected String getProjectName() {
return "test-spring-validations";
}
@Override
protected JdtAstReconciler getReconciler() {
return new BeanPostProcessingIgnoreInAotReconciler(new QuickfixRegistry());
}
@BeforeEach
void setup() throws Exception {
super.setup();
}
@AfterEach
void tearDown() throws Exception {
super.tearDown();
}
@Test
void noMethod() throws Exception {
String source = """
package example.demo;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
class A implements BeanPostProcessor, BeanRegistrationAotProcessor{
A() {};
}
""";
List<ReconcileProblem> problems = reconcile("A.java", source, false);
assertEquals(1, problems.size());
ReconcileProblem problem = problems.get(0);
assertEquals(SpringAotJavaProblemType.JAVA_BEAN_POST_PROCESSOR_IGNORED_IN_AOT, problem.getType());
String markedStr = source.substring(problem.getOffset(), problem.getOffset() + problem.getLength());
assertEquals("A", markedStr);
assertEquals(1, problem.getQuickfixes().size());
}
@Test
void withMethodReturningTrue_IncompleteAst() throws Exception {
String source = """
package example.demo;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
class A implements BeanPostProcessor, BeanRegistrationAotProcessor{
A() {};
public boolean isBeanExcludedFromAotProcessing() { return true; }
}
""";
try {
reconcile("A.java", source, false);
fail("Should require complete AST");
} catch (RequiredCompleteAstException e) {
// good
}
}
@Test
void withMethodReturningTrue_CompleteAst() throws Exception {
String source = """
package example.demo;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
class A implements BeanPostProcessor, BeanRegistrationAotProcessor{
A() {};
public boolean isBeanExcludedFromAotProcessing() { return true; }
}
""";
List<ReconcileProblem> problems = reconcile("A.java", source, true);
assertEquals(1, problems.size());
ReconcileProblem problem = problems.get(0);
assertEquals(SpringAotJavaProblemType.JAVA_BEAN_POST_PROCESSOR_IGNORED_IN_AOT, problem.getType());
String markedStr = source.substring(problem.getOffset(), problem.getOffset() + problem.getLength());
assertEquals("A", markedStr);
assertEquals(1, problem.getQuickfixes().size());
}
@Test
void withMethodReturningFalse() throws Exception {
String source = """
package example.demo;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
class A implements BeanPostProcessor, BeanRegistrationAotProcessor{
A() {};
public boolean isBeanExcludedFromAotProcessing() { return false; }
}
""";
List<ReconcileProblem> problems = reconcile("A.java", source, true);
assertEquals(0, problems.size());
}
@Test
void noBeanPostProcessor() throws Exception {
String source = """
package example.demo;
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
class A implements BeanRegistrationAotProcessor{
A() {};
}
""";
List<ReconcileProblem> problems = reconcile("A.java", source, true);
assertEquals(0, problems.size());
}
}

View File

@@ -0,0 +1,182 @@
/*******************************************************************************
* 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.reconcilers.test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import java.util.List;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType;
import org.springframework.ide.vscode.boot.java.reconcilers.HttpSecurityLambdaDslReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.JdtAstReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.RequiredCompleteAstException;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem;
public class HttpSecurityLambdaDslReconcilerTest extends BaseReconcilerTest {
@Override
protected String getFolder() {
return "httpsecuritydsl";
}
@Override
protected String getProjectName() {
return "test-spring-indexing";
}
@Override
protected JdtAstReconciler getReconciler() {
return new HttpSecurityLambdaDslReconciler(new QuickfixRegistry());
}
@BeforeEach
void setup() throws Exception {
super.setup();
}
@AfterEach
void tearDown() throws Exception {
super.tearDown();
}
@Test
void requireFullAst_1() throws Exception {
String source = """
package example.demo;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
class A {
void something(HttpSecurity security) {
};
}
""";
try {
reconcile("A.java", source, false);
fail("Should require full AST with method bodies");
} catch (RequiredCompleteAstException e) {
// pass
}
}
@Test
void requireFullAst_2() throws Exception {
String source = """
package example.demo;
import org.springframework.security.config.annotation.web.builders.*;
class A {
void something(HttpSecurity security) {
};
}
""";
try {
reconcile("A.java", source, false);
fail("Should require full AST with method bodies");
} catch (RequiredCompleteAstException e) {
// pass
}
}
@Test
void requireFullAst_3() throws Exception {
String source = """
package example.demo;
class A {
void something(org.springframework.security.config.annotation.web.builders.HttpSecurity security) {
};
}
""";
try {
reconcile("A.java", source, false);
fail("Should require full AST with method bodies");
} catch (RequiredCompleteAstException e) {
// pass
}
}
@Test
void sanityTest() throws Exception {
String source = """
package example.demo;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
class A {
void something(HttpSecurity security) {
security.authorizeRequests().mvcMatchers();
};
}
""";
List<ReconcileProblem> problems = reconcile("A.java", source, true);
assertEquals(1, problems.size());
ReconcileProblem problem = problems.get(0);
assertEquals(Boot2JavaProblemType.JAVA_LAMBDA_DSL, problem.getType());
String markedStr = source.substring(problem.getOffset(), problem.getOffset() + problem.getLength());
assertEquals("security.authorizeRequests().mvcMatchers()", markedStr);
assertEquals(3, problem.getQuickfixes().size());
}
@Test
void noProblem() throws Exception {
String source = """
package example.demo;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
class A {
void something(HttpSecurity http) {
http
.authorizeRequests(requests -> requests
.antMatchers("/blog/**").permitAll()
.anyRequest().authenticated())
.formLogin(login -> login
.loginPage("/login")
.permitAll())
.rememberMe(withDefaults());
};
}
""";
List<ReconcileProblem> problems = reconcile("A.java", source, true);
assertEquals(0, problems.size());
}
}

View File

@@ -0,0 +1,163 @@
/*******************************************************************************
* 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.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.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Stream;
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.java.SpringAotJavaProblemType;
import org.springframework.ide.vscode.boot.java.beans.ConfigBeanSymbolAddOnInformation;
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.JdtAstReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.NotRegisteredBeansReconciler;
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;
public class NotRegisteredBeansReconcilerTest extends BaseReconcilerTest {
@Override
protected String getFolder() {
return "notregisteredbeanaot";
}
@Override
protected String getProjectName() {
return "test-spring-validations";
}
@SuppressWarnings("unchecked")
@Override
protected JdtAstReconciler getReconciler() {
NotRegisteredBeansReconciler reconciler = new NotRegisteredBeansReconciler(new QuickfixRegistry());
SpringSymbolIndex mockSymbolIndex = mock(SpringSymbolIndex.class);
when(mockSymbolIndex.getSymbols(any(Predicate.class))).thenReturn(Stream.empty());
ApplicationContext context = mock(ApplicationContext.class);
when(context.getBean(SpringSymbolIndex.class)).thenReturn(mockSymbolIndex);
reconciler.setApplicationContext(context);
return reconciler;
}
@SuppressWarnings("unchecked")
private NotRegisteredBeansReconciler createReconciler(EnhancedSymbolInformation... beanSymbols) {
NotRegisteredBeansReconciler reconciler = new NotRegisteredBeansReconciler(new QuickfixRegistry());
SpringSymbolIndex mockSymbolIndex = mock(SpringSymbolIndex.class);
when(mockSymbolIndex.getSymbols(any(Predicate.class))).thenReturn(Stream.empty());
when(mockSymbolIndex.getEnhancedSymbols(any(IJavaProject.class))).thenReturn(Arrays.asList(beanSymbols));
ApplicationContext context = mock(ApplicationContext.class);
when(context.getBean(SpringSymbolIndex.class)).thenReturn(mockSymbolIndex);
reconciler.setApplicationContext(context);
return reconciler;
}
@BeforeEach
void setup() throws Exception {
super.setup();
}
@AfterEach
void tearDown() throws Exception {
super.tearDown();
}
@Test
void sanityTest() throws Exception {
String source = """
package example.demo;
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
class A implements BeanRegistrationAotProcessor {
public A(String k) {}
}
""";
List<ReconcileProblem> problems = reconcile(() -> createReconciler(), "A.java", source, true);
assertEquals(1, problems.size());
ReconcileProblem problem = problems.get(0);
assertEquals(SpringAotJavaProblemType.JAVA_BEAN_NOT_REGISTERED_IN_AOT, problem.getType());
String markedStr = source.substring(problem.getOffset(), problem.getOffset() + problem.getLength());
assertEquals("A", markedStr);
assertEquals(0, problem.getQuickfixes().size());
}
@Test
void sanityTestWithQuickFixes() throws Exception {
Path configClassPath = createFile("TestConfig.java", """
package example.demo;
import org.springframework.context.annotation.Configuration;
@Configuration
class TestConfig {
}
""");
String source = """
package example.demo;
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
class A implements BeanRegistrationAotProcessor {
public A(String k) {}
}
""";
WorkspaceSymbol workspaceSymbol = new WorkspaceSymbol("testConfig", SymbolKind.Class, Either.forLeft(new Location(configClassPath.toUri().toASCIIString(), new Range())));
ConfigBeanSymbolAddOnInformation configBeanAddOn = new ConfigBeanSymbolAddOnInformation("testConfig", "example.demo.TestConfig");
List<ReconcileProblem> problems = reconcile(() -> createReconciler(new EnhancedSymbolInformation(workspaceSymbol, new SymbolAddOnInformation[] { configBeanAddOn })), "A.java", source, true);
assertEquals(1, problems.size());
ReconcileProblem problem = problems.get(0);
assertEquals(SpringAotJavaProblemType.JAVA_BEAN_NOT_REGISTERED_IN_AOT, problem.getType());
String markedStr = source.substring(problem.getOffset(), problem.getOffset() + problem.getLength());
assertEquals("A", markedStr);
assertEquals(1, problem.getQuickfixes().size());
}
}

View File

@@ -0,0 +1,129 @@
/*******************************************************************************
* 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.reconcilers.test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import java.util.List;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType;
import org.springframework.ide.vscode.boot.java.reconcilers.JdtAstReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.RequiredCompleteAstException;
import org.springframework.ide.vscode.boot.java.reconcilers.ServerHttpSecurityLambdaDslReconciler;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem;
public class ServerHttpSecurityLambdaDslReconcilerTest extends BaseReconcilerTest {
@Override
protected String getFolder() {
return "serverhttpsecuritydsl";
}
@Override
protected String getProjectName() {
return "test-spring-indexing";
}
@Override
protected JdtAstReconciler getReconciler() {
return new ServerHttpSecurityLambdaDslReconciler(new QuickfixRegistry());
}
@BeforeEach
void setup() throws Exception {
super.setup();
}
@AfterEach
void tearDown() throws Exception {
super.tearDown();
}
@Test
void requireFullAst() throws Exception {
String source = """
package example.demo;
import org.springframework.security.config.web.server.ServerHttpSecurity;
class A {
void something(ServerHttpSecurity security) {
};
}
""";
try {
reconcile("A.java", source, false);
fail("Should require full AST with method bodies");
} catch (RequiredCompleteAstException e) {
// pass
}
}
@Test
void sanityTest() throws Exception {
String source = """
package example.demo;
import org.springframework.security.config.web.server.ServerHttpSecurity;
class A {
void something(ServerHttpSecurity http) {
http.authorizeExchange().pathMatchers("/blog/**").permitAll().anyExchange().authenticated();
};
}
""";
List<ReconcileProblem> problems = reconcile("A.java", source, true);
assertEquals(1, problems.size());
ReconcileProblem problem = problems.get(0);
assertEquals(Boot2JavaProblemType.JAVA_LAMBDA_DSL, problem.getType());
String markedStr = source.substring(problem.getOffset(), problem.getOffset() + problem.getLength());
assertEquals("http.authorizeExchange().pathMatchers(\"/blog/**\").permitAll().anyExchange().authenticated()", markedStr);
assertEquals(3, problem.getQuickfixes().size());
}
@Test
void noProblem() throws Exception {
String source = """
package example.demo;
import org.springframework.security.config.web.server.ServerHttpSecurity;
class A {
void something(ServerHttpSecurity http) {
http.authorizeExchange(exchange -> exchange.pathMatchers("/blog/**").permitAll().anyExchange().authenticated());
};
}
""";
List<ReconcileProblem> problems = reconcile("A.java", source, true);
assertEquals(0, problems.size());
}
}