Skip to content

Commit a413929

Browse files
committed
GH-1654: added validation of version syntax to functional router definitions (both webflux and webmvc)
1 parent 05b9cd4 commit a413929

File tree

7 files changed

+992
-25
lines changed

7 files changed

+992
-25
lines changed

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeansIndexer.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.springframework.ide.vscode.boot.java.Annotations;
2727
import org.springframework.ide.vscode.boot.java.reconcilers.RequiredCompleteAstException;
2828
import org.springframework.ide.vscode.boot.java.requestmapping.WebfluxRouterSymbolProvider;
29+
import org.springframework.ide.vscode.boot.java.requestmapping.WebfluxUtils;
2930
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
3031
import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext;
3132
import org.springframework.ide.vscode.commons.protocol.spring.AnnotationMetadata;
@@ -55,7 +56,7 @@ public static void indexBeanMethod(SpringIndexElement parentNode, Annotation nod
5556
MethodDeclaration method = (MethodDeclaration) parent;
5657
if (isMethodAbstract(method)) return;
5758

58-
boolean isWebfluxRouter = WebfluxRouterSymbolProvider.isWebfluxRouterBean(method);
59+
boolean isWebfluxRouter = WebfluxUtils.isFunctionalWebRouterBean(method);
5960
if (isWebfluxRouter) {
6061
if (!context.isFullAst()) {
6162
throw new RequiredCompleteAstException();

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/WebApiVersionSyntaxReconciler.java

Lines changed: 85 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,16 @@
1616
import java.util.Arrays;
1717
import java.util.List;
1818

19+
import org.eclipse.jdt.core.dom.ASTNode;
1920
import org.eclipse.jdt.core.dom.ASTVisitor;
2021
import org.eclipse.jdt.core.dom.CompilationUnit;
2122
import org.eclipse.jdt.core.dom.Expression;
2223
import org.eclipse.jdt.core.dom.IAnnotationBinding;
24+
import org.eclipse.jdt.core.dom.IMethodBinding;
2325
import org.eclipse.jdt.core.dom.ITypeBinding;
2426
import org.eclipse.jdt.core.dom.MemberValuePair;
27+
import org.eclipse.jdt.core.dom.MethodDeclaration;
28+
import org.eclipse.jdt.core.dom.MethodInvocation;
2529
import org.eclipse.jdt.core.dom.NormalAnnotation;
2630
import org.eclipse.jdt.core.dom.TypeDeclaration;
2731
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
@@ -30,6 +34,7 @@
3034
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
3135
import org.springframework.ide.vscode.boot.java.requestmapping.WebConfigIndexElement;
3236
import org.springframework.ide.vscode.boot.java.requestmapping.WebConfigJavaIndexer;
37+
import org.springframework.ide.vscode.boot.java.requestmapping.WebfluxUtils;
3338
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
3439
import org.springframework.ide.vscode.commons.java.IJavaProject;
3540
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType;
@@ -93,23 +98,77 @@ public boolean visit(NormalAnnotation annotation) {
9398

9499
Expression valueExpression = pair.getValue();
95100
String versionValue = ASTUtils.getExpressionValueAsString(valueExpression, (d) -> {});
96-
versionValue = updateVersion(versionValue);
97101

98-
SemanticApiVersionParser parser = new SemanticApiVersionParser();
99-
try {
100-
parser.parseVersion(versionValue);
101-
}
102-
catch (IllegalStateException e) {
103-
ReconcileProblemImpl problem = new ReconcileProblemImpl(getProblemType(), PROBLEM_LABEL,
104-
valueExpression.getStartPosition(), valueExpression.getLength());
105-
106-
context.getProblemCollector().accept(problem);
107-
}
102+
validateVersion(context, valueExpression, versionValue);
108103
}
109104
);
110105

111106
return super.visit(annotation);
112107
}
108+
109+
@Override
110+
public boolean visit(MethodDeclaration method) {
111+
boolean isWebfluxRouter = WebfluxUtils.isFunctionalWebRouterBean(method);
112+
if (isWebfluxRouter) {
113+
if (!context.isCompleteAst()) {
114+
throw new RequiredCompleteAstException();
115+
}
116+
}
117+
118+
return super.visit(method);
119+
}
120+
121+
/**
122+
* this is the piece of the reconciler that validates version in functional endpoint definitions
123+
*/
124+
@Override
125+
public boolean visit(MethodInvocation methodInvocation) {
126+
IMethodBinding methodBinding = methodInvocation.resolveMethodBinding();
127+
if (methodBinding == null) return super.visit(methodInvocation);
128+
129+
// Check if this is a call to RequestPredicates.version() method
130+
String methodName = methodBinding.getName();
131+
if (!"version".equals(methodName)) {
132+
return super.visit(methodInvocation);
133+
}
134+
135+
// Check if the declaring class is RequestPredicates (for both WebMVC and WebFlux)
136+
ITypeBinding declaringClass = methodBinding.getDeclaringClass();
137+
if (declaringClass == null) {
138+
return super.visit(methodInvocation);
139+
}
140+
141+
String declaringClassName = declaringClass.getQualifiedName();
142+
boolean isWebMvcVersion = WebfluxUtils.MVC_REQUEST_PREDICATES_TYPE.equals(declaringClassName);
143+
boolean isWebFluxVersion = WebfluxUtils.REQUEST_PREDICATES_TYPE.equals(declaringClassName);
144+
145+
if (!isWebMvcVersion && !isWebFluxVersion) {
146+
return super.visit(methodInvocation);
147+
}
148+
149+
// Extract the version argument
150+
@SuppressWarnings("unchecked")
151+
List<Expression> arguments = methodInvocation.arguments();
152+
if (arguments == null || arguments.isEmpty() || arguments.size() > 1) {
153+
return super.visit(methodInvocation);
154+
}
155+
156+
if (!context.isIndexComplete()) {
157+
throw new RequiredCompleteIndexException();
158+
}
159+
160+
if (!isApiVersioningConfiguredWithStanardVersionParser(project)) {
161+
return super.visit(methodInvocation);
162+
}
163+
164+
Expression expression = arguments.get(0);
165+
String versionValue = ASTUtils.getExpressionValueAsString(expression, (d) -> {});
166+
167+
// Validate the version
168+
validateVersion(context, expression, versionValue);
169+
170+
return super.visit(methodInvocation);
171+
}
113172

114173
/**
115174
* this is the piece of the reconciler that looks for changes to web configs and then marks all the potentially affected
@@ -136,6 +195,21 @@ private String updateVersion(String version) {
136195
return (baselineVersion ? version.substring(0, version.length() - 1) : version);
137196
}
138197

198+
private void validateVersion(ReconcilingContext context, ASTNode valueExpression, String versionValue) {
199+
versionValue = updateVersion(versionValue);
200+
201+
SemanticApiVersionParser parser = new SemanticApiVersionParser();
202+
try {
203+
parser.parseVersion(versionValue);
204+
}
205+
catch (IllegalStateException e) {
206+
ReconcileProblemImpl problem = new ReconcileProblemImpl(getProblemType(), PROBLEM_LABEL,
207+
valueExpression.getStartPosition(), valueExpression.getLength());
208+
209+
context.getProblemCollector().accept(problem);
210+
}
211+
}
212+
139213
private boolean isApiVersioningConfiguredWithStanardVersionParser(IJavaProject project) {
140214
List<WebConfigIndexElement> webConfigs = springIndex.getNodesOfType(project.getElementName(), WebConfigIndexElement.class);
141215
for (WebConfigIndexElement webConfig : webConfigs) {

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxRouterSymbolProvider.java

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,11 @@
2020
import org.eclipse.jdt.core.dom.Block;
2121
import org.eclipse.jdt.core.dom.ExpressionMethodReference;
2222
import org.eclipse.jdt.core.dom.IMethodBinding;
23-
import org.eclipse.jdt.core.dom.ITypeBinding;
2423
import org.eclipse.jdt.core.dom.MethodDeclaration;
2524
import org.eclipse.jdt.core.dom.MethodInvocation;
2625
import org.eclipse.jdt.core.dom.QualifiedName;
2726
import org.eclipse.jdt.core.dom.SimpleName;
2827
import org.eclipse.jdt.core.dom.StringLiteral;
29-
import org.eclipse.jdt.core.dom.Type;
3028
import org.eclipse.jdt.core.dom.TypeDeclaration;
3129
import org.eclipse.lsp4j.Location;
3230
import org.eclipse.lsp4j.Range;
@@ -47,17 +45,6 @@ public class WebfluxRouterSymbolProvider {
4745

4846
private static final Logger log = LoggerFactory.getLogger(WebfluxRouterSymbolProvider.class);
4947

50-
public static boolean isWebfluxRouterBean(MethodDeclaration method) {
51-
Type returnType = method.getReturnType2();
52-
if (returnType != null) {
53-
ITypeBinding resolvedBinding = returnType.resolveBinding();
54-
if (resolvedBinding != null && WebfluxUtils.ROUTER_FUNCTION_TYPE.equals(resolvedBinding.getBinaryName())) {
55-
return true;
56-
}
57-
}
58-
return false;
59-
}
60-
6148
public static void createWebfluxElements(Bean beanDefinition, MethodDeclaration methodDeclaration, SpringIndexerJavaContext context, TextDocument doc) {
6249
Block methodBody = methodDeclaration.getBody();
6350
if (methodBody != null && methodBody.statements() != null && methodBody.statements().size() > 0) {

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxUtils.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,13 @@
1717
import java.util.function.Function;
1818

1919
import org.eclipse.jdt.core.dom.IMethodBinding;
20+
import org.eclipse.jdt.core.dom.ITypeBinding;
21+
import org.eclipse.jdt.core.dom.MethodDeclaration;
2022
import org.eclipse.jdt.core.dom.MethodInvocation;
2123
import org.eclipse.jdt.core.dom.QualifiedName;
2224
import org.eclipse.jdt.core.dom.SimpleName;
2325
import org.eclipse.jdt.core.dom.StringLiteral;
26+
import org.eclipse.jdt.core.dom.Type;
2427

2528
/**
2629
* @author Martin Lippert
@@ -31,6 +34,9 @@ public class WebfluxUtils {
3134
public static final String ROUTER_FUNCTIONS_TYPE = "org.springframework.web.reactive.function.server.RouterFunctions";
3235
public static final String REQUEST_PREDICATES_TYPE = "org.springframework.web.reactive.function.server.RequestPredicates";
3336

37+
public static final String MVC_ROUTER_FUNCTION_TYPE = "org.springframework.web.servlet.function.RouterFunction";
38+
public static final String MVC_REQUEST_PREDICATES_TYPE = "org.springframework.web.servlet.function.RequestPredicates";
39+
3440
public static final String REQUEST_PREDICATE_PATH_METHOD = "path";
3541
public static final String REQUEST_PREDICATE_METHOD_METHOD = "method";
3642
public static final String REQUEST_PREDICATE_ACCEPT_TYPE_METHOD = "accept";
@@ -41,6 +47,19 @@ public class WebfluxUtils {
4147
public static final Set<String> REQUEST_PREDICATE_ALL_PATH_METHODS = new HashSet<>(Arrays.asList(REQUEST_PREDICATE_PATH_METHOD, "GET", "POST", "DELETE", "PUT", "PATCH", "HEAD", "OPTIONS"));
4248

4349

50+
public static boolean isFunctionalWebRouterBean(MethodDeclaration method) {
51+
Type returnType = method.getReturnType2();
52+
if (returnType != null) {
53+
ITypeBinding resolvedBinding = returnType.resolveBinding();
54+
if (resolvedBinding != null && (
55+
WebfluxUtils.ROUTER_FUNCTION_TYPE.equals(resolvedBinding.getBinaryName())
56+
|| WebfluxUtils.MVC_ROUTER_FUNCTION_TYPE.equals(resolvedBinding.getBinaryName()))) {
57+
return true;
58+
}
59+
}
60+
return false;
61+
}
62+
4463
public static StringLiteral extractStringLiteralArgument(MethodInvocation node) {
4564
List<?> arguments = node.arguments();
4665
if (arguments != null && arguments.size() > 0) {

0 commit comments

Comments
 (0)