Skip to content

Commit abac483

Browse files
committed
GH-1652: take path prefix into account when indexing and displaying web config information
1 parent 6db4981 commit abac483

File tree

8 files changed

+153
-76
lines changed

8 files changed

+153
-76
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ public class Annotations {
118118

119119
public static final String WEB_MVC_CONFIGURER_INTERFACE = "org.springframework.web.servlet.config.annotation.WebMvcConfigurer";
120120
public static final String WEB_MVC_API_VERSION_CONFIGURER_INTERFACE = "org.springframework.web.servlet.config.annotation.ApiVersionConfigurer";
121+
public static final String WEB_MVC_PATH_MATCH_CONFIGURER_INTERFACE = "org.springframework.web.servlet.config.annotation.PathMatchConfigurer";
121122

122123
public static final Map<String, String> AOP_ANNOTATIONS = Map.of(
123124
"org.aspectj.lang.annotation.Pointcut", "Pointcut",

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,13 @@ private CodeLens createCodeLens(WebConfigIndexElement webConfig, TypeDeclaration
9191

9292
// Display label
9393
String label = "Web Config";
94+
if (webConfig.getPathPrefix() != null) {
95+
label += " - " + webConfig.getPathPrefix();
96+
}
97+
9498
if (webConfig.isVersioningSupported()) {
9599
label += " - Versioning via " + webConfig.getVersionSupportStrategy();
96-
label += " - supported versions: " + String.join(", ", webConfig.getSupportedVersions());
100+
label += " - Supported Versions: " + String.join(", ", webConfig.getSupportedVersions());
97101
}
98102

99103
Location targetLocation = webConfig.getLocation();

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,25 @@
1818

1919
public class WebConfigIndexElement extends AbstractSpringIndexElement {
2020

21+
private final String pathPrefix;
22+
2123
private final boolean isVersioningSupported;
2224
private final String versionSupportStrategy;
2325
private final String[] supportedVersions;
2426
private final Location location;
2527

26-
public WebConfigIndexElement(boolean isVersioningSupported, String versionSupportStrategy, String[] supportedVersions, Location location) {
28+
public WebConfigIndexElement(String pathPrefix, boolean isVersioningSupported, String versionSupportStrategy, String[] supportedVersions, Location location) {
29+
this.pathPrefix = pathPrefix;
2730
this.isVersioningSupported = isVersioningSupported;
2831
this.versionSupportStrategy = versionSupportStrategy;
2932
this.supportedVersions = supportedVersions;
3033
this.location = location;
3134
}
3235

36+
public String getPathPrefix() {
37+
return pathPrefix;
38+
}
39+
3340
public boolean isVersioningSupported() {
3441
return isVersioningSupported;
3542
}
@@ -48,27 +55,30 @@ public Location getLocation() {
4855

4956
public static class Builder {
5057

58+
private String pathPrefix = null;
5159
private boolean isVersioningSupported = false;
5260
private String versionSupportStrategy = null;
5361
private List<String> supportedVersions = new ArrayList<>(10);
5462

55-
public Builder isVersionSupported(boolean isVersionSupported) {
56-
this.isVersioningSupported = isVersionSupported;
63+
public Builder pathPrefix(String pathPrefix) {
64+
this.pathPrefix = pathPrefix;
5765
return this;
5866
}
5967

6068
public Builder versionStrategy(String versionSupportStrategy) {
6169
this.versionSupportStrategy = versionSupportStrategy;
70+
this.isVersioningSupported = true;
6271
return this;
6372
}
6473

6574
public Builder supportedVersion(String supportedVersion) {
6675
this.supportedVersions.add(supportedVersion);
76+
this.isVersioningSupported = true;
6777
return this;
6878
}
6979

7080
public WebConfigIndexElement buildFor(Location location) {
71-
return new WebConfigIndexElement(this.isVersioningSupported, this.versionSupportStrategy, (String[]) this.supportedVersions.toArray(new String[this.supportedVersions.size()]), location);
81+
return new WebConfigIndexElement(this.pathPrefix, this.isVersioningSupported, this.versionSupportStrategy, (String[]) this.supportedVersions.toArray(new String[this.supportedVersions.size()]), location);
7282
}
7383

7484
}

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

Lines changed: 108 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import java.util.List;
1515
import java.util.Map;
1616
import java.util.Set;
17-
import java.util.function.Consumer;
17+
import java.util.function.BiConsumer;
1818

1919
import org.eclipse.jdt.core.dom.ASTVisitor;
2020
import org.eclipse.jdt.core.dom.Block;
@@ -35,6 +35,11 @@
3535

3636
public class WebConfigIndexer {
3737

38+
private static final String CONFIGURE_API_VERSIONING_METHOD = "configureApiVersioning";
39+
private static final String CONFIGURE_PATH_MATCHING_METHOD = "configurePathMatch";
40+
41+
private static Map<String, MethodInvocationExtractor> methodExtractors = initializeMethodExtractors();
42+
3843
public static void indexWebConfig(Bean beanDefinition, TypeDeclaration type, SpringIndexerJavaContext context, TextDocument doc) {
3944
AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(type);
4045

@@ -54,106 +59,61 @@ public static void indexWebConfig(Bean beanDefinition, TypeDeclaration type, Spr
5459
throw new RequiredCompleteAstException();
5560
}
5661

57-
Builder builder = new WebConfigIndexElement.Builder();
62+
MethodDeclaration configureVersioningMethod = findMethod(type, inTypeHierarchy, CONFIGURE_API_VERSIONING_METHOD);
63+
MethodDeclaration configurePathMethod = findMethod(type, inTypeHierarchy, CONFIGURE_PATH_MATCHING_METHOD);
5864

59-
MethodDeclaration configureVersioningMethod = findConfigureVersioningMethod(type, inTypeHierarchy);
60-
if (configureVersioningMethod != null) {
61-
scanConfigureApiVersioningMethodBody(builder, configureVersioningMethod.getBody(), context, doc);
62-
}
65+
66+
if (configureVersioningMethod != null || configurePathMethod != null) {
67+
Builder builder = new WebConfigIndexElement.Builder();
68+
69+
if (configureVersioningMethod != null) scanMethodBody(builder, configureVersioningMethod.getBody(), context, doc);
70+
if (configurePathMethod != null) scanMethodBody(builder, configurePathMethod.getBody(), context, doc);
6371

64-
WebConfigIndexElement webConfigIndexElement = builder.buildFor(beanDefinition.getLocation());
65-
if (webConfigIndexElement != null) {
66-
beanDefinition.addChild(webConfigIndexElement);
72+
WebConfigIndexElement webConfigIndexElement = builder.buildFor(beanDefinition.getLocation());
73+
if (webConfigIndexElement != null) {
74+
beanDefinition.addChild(webConfigIndexElement);
75+
}
6776
}
6877

6978
}
7079

71-
private static void scanConfigureApiVersioningMethodBody(Builder builder, Block body, SpringIndexerJavaContext context, TextDocument doc) {
80+
private static void scanMethodBody(Builder builder, Block body, SpringIndexerJavaContext context, TextDocument doc) {
7281
if (body == null) {
7382
return;
7483
}
7584

76-
builder.isVersionSupported(true);
77-
78-
Map<String, Consumer<MethodInvocation>> apiVersionConfigurerMethods = new HashMap<>();
79-
apiVersionConfigurerMethods.put("addSupportedVersions", (invocation) -> {
80-
81-
@SuppressWarnings("unchecked")
82-
List<Expression> arguments = invocation.arguments();
83-
for (Expression arg : arguments) {
84-
String[] expressionValueAsArray = ASTUtils.getExpressionValueAsArray(arg, (dep) -> {});
85-
for (String supportedVersion : expressionValueAsArray) {
86-
builder.supportedVersion(supportedVersion);
87-
}
88-
}
89-
});
90-
91-
apiVersionConfigurerMethods.put("useRequestHeader", (invocation) -> {
92-
93-
@SuppressWarnings("unchecked")
94-
List<Expression> arguments = invocation.arguments();
95-
if (arguments != null && arguments.size() == 1) {
96-
String value = ASTUtils.getExpressionValueAsString(arguments.get(0), (d) -> {});
97-
if (value != null) {
98-
builder.versionStrategy("Request Header: " + value);
99-
}
100-
}
101-
});
102-
103-
apiVersionConfigurerMethods.put("usePathSegment", (invocation) -> {
104-
105-
@SuppressWarnings("unchecked")
106-
List<Expression> arguments = invocation.arguments();
107-
if (arguments != null && arguments.size() == 1) {
108-
String value = ASTUtils.getExpressionValueAsString(arguments.get(0), (d) -> {});
109-
if (value != null) {
110-
builder.versionStrategy("Path Segment: " + value);
111-
}
112-
}
113-
});
114-
115-
apiVersionConfigurerMethods.put("useQueryParam", (invocation) -> {
116-
117-
@SuppressWarnings("unchecked")
118-
List<Expression> arguments = invocation.arguments();
119-
if (arguments != null && arguments.size() == 1) {
120-
String value = ASTUtils.getExpressionValueAsString(arguments.get(0), (d) -> {});
121-
if (value != null) {
122-
builder.versionStrategy("Query Param: " + value);
123-
}
124-
}
125-
});
126-
12785
body.accept(new ASTVisitor() {
12886

12987
@Override
13088
public boolean visit(MethodInvocation methodInvocation) {
13189
String methodName = methodInvocation.getName().toString();
132-
if (apiVersionConfigurerMethods.containsKey(methodName)) {
90+
91+
MethodInvocationExtractor invocationExtractor = methodExtractors.get(methodName);
92+
if (invocationExtractor != null) {
13393

13494
IMethodBinding methodBinding = methodInvocation.resolveMethodBinding();
13595
ITypeBinding declaringClass = methodBinding.getDeclaringClass();
13696

137-
if (declaringClass != null && Annotations.WEB_MVC_API_VERSION_CONFIGURER_INTERFACE.equals(declaringClass.getQualifiedName())) {
138-
apiVersionConfigurerMethods.get(methodName).accept(methodInvocation);
97+
if (declaringClass != null && invocationExtractor.getTargetInvocationType().equals(declaringClass.getQualifiedName())) {
98+
invocationExtractor.extractParameters(methodInvocation, builder);
13999
}
140100
}
141101

142102
return super.visit(methodInvocation);
143103
}
144104
});
145-
146-
}
147105

148-
private static MethodDeclaration findConfigureVersioningMethod(TypeDeclaration type, ITypeBinding webmvcConfigurerType) {
106+
}
107+
108+
private static MethodDeclaration findMethod(TypeDeclaration type, ITypeBinding webmvcConfigurerType, String methodName) {
149109
IMethodBinding[] webConfigurerMethods = webmvcConfigurerType.getDeclaredMethods();
150110
if (webConfigurerMethods == null) {
151111
return null;
152112
}
153113

154114
IMethodBinding configureVersioningMethod = null;
155115
for (IMethodBinding method : webConfigurerMethods) {
156-
if ("configureApiVersioning".equals(method.getName())) {
116+
if (methodName.equals(method.getName())) {
157117
configureVersioningMethod = method;
158118
}
159119
}
@@ -174,5 +134,85 @@ private static MethodDeclaration findConfigureVersioningMethod(TypeDeclaration t
174134

175135
return null;
176136
}
137+
138+
private static Map<String, MethodInvocationExtractor> initializeMethodExtractors() {
139+
Map<String, MethodInvocationExtractor> result = new HashMap<>();
140+
141+
result.put("addSupportedVersions", new MultipleArgumentsExtractor(Annotations.WEB_MVC_API_VERSION_CONFIGURER_INTERFACE, (expression, webconfigBuilder) -> {
142+
String[] expressionValueAsArray = ASTUtils.getExpressionValueAsArray(expression, (dep) -> {});
143+
for (String supportedVersion : expressionValueAsArray) {
144+
webconfigBuilder.supportedVersion(supportedVersion);
145+
}
146+
}));
147+
148+
result.put("useRequestHeader", new SingleArgumentExtractor(Annotations.WEB_MVC_API_VERSION_CONFIGURER_INTERFACE, 0, (expression, webconfigBuilder) -> {
149+
String value = ASTUtils.getExpressionValueAsString(expression, (d) -> {});
150+
if (value != null) {
151+
webconfigBuilder.versionStrategy("Request Header: " + value);
152+
}
153+
}));
154+
155+
result.put("usePathSegment", new SingleArgumentExtractor(Annotations.WEB_MVC_API_VERSION_CONFIGURER_INTERFACE, 0, (expression, webconfigBuilder) -> {
156+
String value = ASTUtils.getExpressionValueAsString(expression, (d) -> {});
157+
if (value != null) {
158+
webconfigBuilder.versionStrategy("Path Segment: " + value);
159+
}
160+
}));
161+
162+
result.put("useQueryParam", new SingleArgumentExtractor(Annotations.WEB_MVC_API_VERSION_CONFIGURER_INTERFACE, 0, (expression, webconfigBuilder) -> {
163+
String value = ASTUtils.getExpressionValueAsString(expression, (d) -> {});
164+
if (value != null) {
165+
webconfigBuilder.versionStrategy("Query Param: " + value);
166+
}
167+
}));
168+
169+
result.put("addPathPrefix", new SingleArgumentExtractor(Annotations.WEB_MVC_PATH_MATCH_CONFIGURER_INTERFACE, 0, (expression, webconfigBuilder) -> {
170+
String value = ASTUtils.getExpressionValueAsString(expression, (d) -> {});
171+
if (value != null) {
172+
webconfigBuilder.pathPrefix("Path Prefix: " + value);
173+
}
174+
}));
175+
176+
177+
return result;
178+
}
179+
180+
interface MethodInvocationExtractor {
181+
String getTargetInvocationType();
182+
void extractParameters(MethodInvocation methodInvocation, WebConfigIndexElement.Builder builder);
183+
}
177184

185+
record SingleArgumentExtractor (String invocationTargetType, int argumentNo, BiConsumer<Expression, WebConfigIndexElement.Builder> consumer) implements MethodInvocationExtractor {
186+
187+
@Override
188+
public String getTargetInvocationType() {
189+
return invocationTargetType;
190+
}
191+
192+
public void extractParameters(MethodInvocation methodInvocation, WebConfigIndexElement.Builder builder) {
193+
@SuppressWarnings("unchecked")
194+
List<Expression> arguments = methodInvocation.arguments();
195+
Expression expression = arguments.get(argumentNo);
196+
consumer.accept(expression, builder);
197+
}
198+
199+
}
200+
201+
record MultipleArgumentsExtractor (String invocationTargetType, BiConsumer<Expression, WebConfigIndexElement.Builder> consumer) implements MethodInvocationExtractor {
202+
203+
@Override
204+
public String getTargetInvocationType() {
205+
return invocationTargetType;
206+
}
207+
208+
public void extractParameters(MethodInvocation methodInvocation, WebConfigIndexElement.Builder builder) {
209+
@SuppressWarnings("unchecked")
210+
List<Expression> arguments = methodInvocation.arguments();
211+
for (Expression expression : arguments) {
212+
consumer.accept(expression, builder);
213+
}
214+
}
215+
216+
}
217+
178218
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ public class SpringIndexerJava implements SpringIndexer {
9494

9595
// whenever the implementation of the indexer changes in a way that the stored data in the cache is no longer valid,
9696
// we need to change the generation - this will result in a re-indexing due to no up-to-date cache data being found
97-
private static final String GENERATION = "GEN-29";
97+
private static final String GENERATION = "GEN-30";
9898
private static final String INDEX_FILES_TASK_ID = "index-java-source-files-task-";
9999

100100
private static final String SYMBOL_KEY = "symbols";

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ void codeLensOverMethod() throws Exception {
7676
Editor editor = harness.newEditor(LanguageId.JAVA, new String(Files.readAllBytes(filePath), StandardCharsets.UTF_8), filePath.toUri().toASCIIString());
7777

7878
List<CodeLens> cls = editor.getCodeLenses("MappingClassWithMultipleVersions", 1);
79-
assertEquals("Web Config - Versioning via Request Header: X-API-Version - supported versions: 1", cls.get(0).getCommand().getTitle());
79+
assertEquals("Web Config - Versioning via Request Header: X-API-Version - Supported Versions: 1", cls.get(0).getCommand().getTitle());
80+
assertEquals("Web Config - Path Prefix: /{version} - Versioning via Path Segment: 0 - Supported Versions: 1.1, 1.2", cls.get(1).getCommand().getTitle());
8081
}
8182

8283
}

headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-version-support-symbols/src/main/java/org/test/versions/WebConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
@Configuration
88
public class WebConfig implements WebMvcConfigurer {
9-
9+
1010
@Override
1111
public void configureApiVersioning(ApiVersionConfigurer configurer) {
1212
configurer.useRequestHeader("X-API-Version").addSupportedVersions("1");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package org.test.versions;
2+
3+
import org.springframework.context.annotation.Configuration;
4+
import org.springframework.web.servlet.config.annotation.ApiVersionConfigurer;
5+
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
6+
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
7+
8+
@Configuration
9+
public class WebConfigForPathVersioning implements WebMvcConfigurer {
10+
11+
@Override
12+
public void configurePathMatch(PathMatchConfigurer configurer) {
13+
configurer.addPathPrefix("/{version}", (aClass) -> true);
14+
}
15+
16+
@Override
17+
public void configureApiVersioning(ApiVersionConfigurer configurer) {
18+
configurer.usePathSegment(0).addSupportedVersions("1.1", "1.2");
19+
}
20+
21+
}

0 commit comments

Comments
 (0)