Skip to content

Commit a4e0f05

Browse files
committed
GH-1650: read web config details from properties files and show additional code lens
1 parent f53e828 commit a4e0f05

File tree

6 files changed

+138
-23
lines changed

6 files changed

+138
-23
lines changed

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

Lines changed: 96 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,18 @@
1010
*******************************************************************************/
1111
package org.springframework.ide.vscode.boot.java.requestmapping;
1212

13+
import java.io.File;
14+
import java.io.IOException;
15+
import java.nio.charset.Charset;
16+
import java.nio.file.Path;
17+
import java.util.HashMap;
1318
import java.util.List;
1419
import java.util.Map;
1520
import java.util.Optional;
21+
import java.util.function.BiConsumer;
22+
import java.util.stream.Stream;
1623

24+
import org.apache.commons.io.FileUtils;
1725
import org.eclipse.jdt.core.dom.ASTVisitor;
1826
import org.eclipse.jdt.core.dom.CompilationUnit;
1927
import org.eclipse.jdt.core.dom.ITypeBinding;
@@ -22,20 +30,32 @@
2230
import org.eclipse.lsp4j.CodeLens;
2331
import org.eclipse.lsp4j.Command;
2432
import org.eclipse.lsp4j.Location;
33+
import org.eclipse.lsp4j.Position;
2534
import org.eclipse.lsp4j.Range;
2635
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
36+
import org.slf4j.Logger;
37+
import org.slf4j.LoggerFactory;
2738
import org.springframework.ide.vscode.boot.app.BootJavaConfig;
2839
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
2940
import org.springframework.ide.vscode.boot.java.Annotations;
3041
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
3142
import org.springframework.ide.vscode.boot.java.handlers.CodeLensProvider;
43+
import org.springframework.ide.vscode.boot.java.requestmapping.WebConfigIndexElement.ConfigType;
44+
import org.springframework.ide.vscode.boot.java.value.ValuePropertyReferencesProvider;
45+
import org.springframework.ide.vscode.boot.properties.BootPropertiesLanguageServerComponents;
46+
import org.springframework.ide.vscode.commons.java.IClasspathUtil;
3247
import org.springframework.ide.vscode.commons.java.IJavaProject;
3348
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
3449
import org.springframework.ide.vscode.commons.util.BadLocationException;
3550
import org.springframework.ide.vscode.commons.util.text.TextDocument;
51+
import org.springframework.ide.vscode.java.properties.antlr.parser.AntlrParser;
52+
import org.springframework.ide.vscode.java.properties.parser.ParseResults;
53+
import org.springframework.ide.vscode.java.properties.parser.Parser;
3654

3755
public class WebConfigCodeLensProvider implements CodeLensProvider {
3856

57+
private static final Logger log = LoggerFactory.getLogger(WebConfigCodeLensProvider.class);
58+
3959
private final SpringMetamodelIndex springIndex;
4060
private final BootJavaConfig config;
4161
private final JavaProjectFinder projectFinder;
@@ -75,7 +95,10 @@ private void provideCodeLens(CancelChecker cancelToken, TypeDeclaration node, Te
7595
if (optional.isEmpty()) return;
7696

7797
IJavaProject project = optional.get();
98+
7899
List<WebConfigIndexElement> webConfigs = springIndex.getNodesOfType(project.getElementName(), WebConfigIndexElement.class);
100+
List<WebConfigIndexElement> webConfigFromProperties = findWebConfigFromProperties(project);
101+
webConfigs.addAll(webConfigFromProperties);
79102

80103
for (WebConfigIndexElement webConfig : webConfigs) {
81104
CodeLens codeLens = createCodeLens(webConfig, node, doc);
@@ -90,13 +113,17 @@ private CodeLens createCodeLens(WebConfigIndexElement webConfig, TypeDeclaration
90113
Command command = new Command();
91114

92115
// Display label
93-
String label = "Web Config";
94-
if (webConfig.getPathPrefix() != null) {
95-
label += " - " + webConfig.getPathPrefix();
116+
String label = webConfig.getConfigType().getLabel();
117+
118+
if (webConfig.getPathPrefix() != null && webConfig.getPathPrefix().trim().length() > 0) {
119+
label += " - Path Prefix: " + webConfig.getPathPrefix();
96120
}
97121

98-
if (webConfig.isVersioningSupported()) {
122+
if (webConfig.getVersionSupportStrategies() != null && webConfig.getVersionSupportStrategies().size() > 0) {
99123
label += " - Versioning via " + String.join(", ", webConfig.getVersionSupportStrategies());
124+
}
125+
126+
if (webConfig.getSupportedVersions() != null && webConfig.getSupportedVersions().size() > 0) {
100127
label += " - Supported Versions: " + String.join(", ", webConfig.getSupportedVersions());
101128
}
102129

@@ -128,6 +155,71 @@ private CodeLens createCodeLens(WebConfigIndexElement webConfig, TypeDeclaration
128155
}
129156

130157
}
158+
159+
private List<WebConfigIndexElement> findWebConfigFromProperties(IJavaProject project) {
160+
Map<String, BiConsumer<String, WebConfigIndexElement.Builder>> converters = new HashMap<>();
161+
converters.put("spring.mvc.apiversion.use.header", (value, configBuilder) -> configBuilder.versionStrategy("Request Header: " + value));
162+
converters.put("spring.mvc.apiversion.use.path-segment", (value, configBuilder) -> configBuilder.versionStrategy("Path Segment: " + value));
163+
converters.put("spring.mvc.apiversion.supported", (value, configBuilder) -> configBuilder.supportedVersion(value));
164+
165+
return IClasspathUtil.getClasspathResourcesFullPaths(project.getClasspath())
166+
.filter(path -> ValuePropertyReferencesProvider.isPropertiesFile(path))
167+
.map(path -> {
168+
WebConfigIndexElement.Builder builder = new WebConfigIndexElement.Builder(ConfigType.PROPERTIES);
169+
170+
getProperties(path)
171+
.filter(pair -> converters.containsKey(pair.key()))
172+
.forEach(pair -> {
173+
converters.get(pair.key()).accept(pair.value(), builder);
174+
});
175+
176+
Location location = new Location(path.toUri().toASCIIString(), new Range(new Position(0, 0), new Position(0, 0)));
177+
return builder.buildFor(location);
178+
})
179+
.toList();
180+
}
181+
182+
private Stream<PropertyKeyValue> getProperties(Path path) {
183+
String fileName = path.getFileName().toString();
184+
185+
if (fileName.endsWith(BootPropertiesLanguageServerComponents.PROPERTIES)) {
186+
return getPropertiesFromPropertiesFile(path.toFile());
187+
}
188+
else {
189+
for (String ymlExtension : BootPropertiesLanguageServerComponents.YML) {
190+
if (fileName.endsWith(ymlExtension)) {
191+
return getPropertiesFromYamlFile(path.toFile());
192+
}
193+
}
194+
}
195+
196+
return Stream.empty();
197+
}
198+
199+
private Stream<PropertyKeyValue> getPropertiesFromPropertiesFile(File file) {
200+
try {
201+
String fileContent = FileUtils.readFileToString(file, Charset.defaultCharset());
202+
203+
Parser parser = new AntlrParser();
204+
ParseResults parseResults = parser.parse(fileContent);
205+
206+
if (parseResults != null && parseResults.ast != null) {
207+
return parseResults.ast.getPropertyValuePairs().stream()
208+
.map(pair -> new PropertyKeyValue(pair.getKey().decode(), pair.getValue().decode()));
209+
}
210+
} catch (IOException e) {
211+
log.error("", e);
212+
}
213+
214+
return Stream.empty();
215+
}
216+
217+
private Stream<PropertyKeyValue> getPropertiesFromYamlFile(File file) {
218+
return Stream.empty();
219+
220+
// TODO !!!
221+
}
131222

223+
record PropertyKeyValue (String key, String value) {}
132224

133225
}

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

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,27 +18,28 @@
1818

1919
public class WebConfigIndexElement extends AbstractSpringIndexElement {
2020

21+
private final ConfigType configType;
22+
2123
private final String pathPrefix;
2224

23-
private final boolean isVersioningSupported;
2425
private final List<String> versionSupportStrategies;
2526
private final List<String> supportedVersions;
2627
private final Location location;
2728

28-
public WebConfigIndexElement(String pathPrefix, boolean isVersioningSupported, List<String> versionSupportStrategies, List<String> supportedVersions, Location location) {
29+
public WebConfigIndexElement(ConfigType configType, String pathPrefix, List<String> versionSupportStrategies, List<String> supportedVersions, Location location) {
30+
this.configType = configType;
2931
this.pathPrefix = pathPrefix;
30-
this.isVersioningSupported = isVersioningSupported;
3132
this.versionSupportStrategies = versionSupportStrategies;
3233
this.supportedVersions = supportedVersions;
3334
this.location = location;
3435
}
3536

36-
public String getPathPrefix() {
37-
return pathPrefix;
37+
public ConfigType getConfigType() {
38+
return configType;
3839
}
3940

40-
public boolean isVersioningSupported() {
41-
return isVersioningSupported;
41+
public String getPathPrefix() {
42+
return pathPrefix;
4243
}
4344

4445
public List<String> getVersionSupportStrategies() {
@@ -55,33 +56,50 @@ public Location getLocation() {
5556

5657
public static class Builder {
5758

59+
private ConfigType configType;
60+
5861
private String pathPrefix = null;
59-
60-
private boolean isVersioningSupported = false;
6162
private List<String> versionSupportStrategies = new ArrayList<>(2);
6263
private List<String> supportedVersions = new ArrayList<>(2);
6364

65+
public Builder(ConfigType configType) {
66+
this.configType = configType;
67+
}
68+
6469
public Builder pathPrefix(String pathPrefix) {
6570
this.pathPrefix = pathPrefix;
6671
return this;
6772
}
6873

6974
public Builder versionStrategy(String versionSupportStrategy) {
7075
this.versionSupportStrategies.add(versionSupportStrategy);
71-
this.isVersioningSupported = true;
7276
return this;
7377
}
7478

7579
public Builder supportedVersion(String supportedVersion) {
7680
this.supportedVersions.add(supportedVersion);
77-
this.isVersioningSupported = true;
7881
return this;
7982
}
8083

8184
public WebConfigIndexElement buildFor(Location location) {
82-
return new WebConfigIndexElement(this.pathPrefix, this.isVersioningSupported, this.versionSupportStrategies, this.supportedVersions, location);
85+
return new WebConfigIndexElement(this.configType, this.pathPrefix, this.versionSupportStrategies, this.supportedVersions, location);
8386
}
8487

8588
}
89+
90+
public enum ConfigType {
91+
WEB_CONFIG ("Web Config"),
92+
PROPERTIES ("Properties Config");
93+
94+
private final String label;
95+
96+
private ConfigType(String label) {
97+
this.label = label;
98+
}
99+
100+
String getLabel() {
101+
return this.label;
102+
}
103+
};
86104

87105
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
2929
import org.springframework.ide.vscode.boot.java.reconcilers.RequiredCompleteAstException;
3030
import org.springframework.ide.vscode.boot.java.requestmapping.WebConfigIndexElement.Builder;
31+
import org.springframework.ide.vscode.boot.java.requestmapping.WebConfigIndexElement.ConfigType;
3132
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
3233
import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext;
3334
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
@@ -64,7 +65,7 @@ public static void indexWebConfig(Bean beanDefinition, TypeDeclaration type, Spr
6465

6566

6667
if (configureVersioningMethod != null || configurePathMethod != null) {
67-
Builder builder = new WebConfigIndexElement.Builder();
68+
Builder builder = new WebConfigIndexElement.Builder(ConfigType.WEB_CONFIG);
6869

6970
if (configureVersioningMethod != null) scanMethodBody(builder, configureVersioningMethod.getBody(), context, doc);
7071
if (configurePathMethod != null) scanMethodBody(builder, configurePathMethod.getBody(), context, doc);
@@ -169,7 +170,7 @@ private static Map<String, MethodInvocationExtractor> initializeMethodExtractors
169170
result.put("addPathPrefix", new SingleArgumentExtractor(Annotations.WEB_MVC_PATH_MATCH_CONFIGURER_INTERFACE, 0, (expression, webconfigBuilder) -> {
170171
String value = ASTUtils.getExpressionValueAsString(expression, (d) -> {});
171172
if (value != null) {
172-
webconfigBuilder.pathPrefix("Path Prefix: " + value);
173+
webconfigBuilder.pathPrefix(value);
173174
}
174175
}));
175176

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-30";
97+
private static final String GENERATION = "GEN-31";
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: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
package org.springframework.ide.vscode.boot.java.requestmapping.test;
1212

1313
import static org.junit.Assert.assertEquals;
14+
import static org.junit.Assert.assertTrue;
1415

1516
import java.nio.charset.StandardCharsets;
1617
import java.nio.file.Files;
@@ -76,10 +77,14 @@ void codeLensOverMethod() throws Exception {
7677
Editor editor = harness.newEditor(LanguageId.JAVA, new String(Files.readAllBytes(filePath), StandardCharsets.UTF_8), filePath.toUri().toASCIIString());
7778

7879
List<CodeLens> cls = editor.getCodeLenses("MappingClassWithMultipleVersions", 1);
79-
assertEquals(1, cls.size());
80+
assertEquals(2, cls.size());
8081

81-
assertEquals("Web Config - Path Prefix: /{version} - Versioning via Request Header: X-API-Version, Path Segment: 0 - Supported Versions: 1.1, 1.2",
82-
cls.get(0).getCommand().getTitle());
82+
assertTrue(contains(cls, "Web Config - Path Prefix: /{version} - Versioning via Request Header: X-API-Version, Path Segment: 0 - Supported Versions: 1.1, 1.2"));
83+
assertTrue(contains(cls, "Properties Config - Versioning via Request Header: X-API-Version - Supported Versions: 1"));
84+
}
85+
86+
private boolean contains(List<CodeLens> cls, String title) {
87+
return cls.stream().filter(cl -> cl.getCommand().getTitle().equals(title)).findAny().isPresent();
8388
}
8489

8590
}

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,6 @@ void testWebConfigIndexElement() throws Exception {
129129
assertEquals(1, webConfigElements.size());
130130

131131
WebConfigIndexElement webConfigElement = webConfigElements.get(0);
132-
assertTrue(webConfigElement.isVersioningSupported());
133132

134133
List<String> versionSupportStrategies = webConfigElement.getVersionSupportStrategies();
135134
assertEquals(2, versionSupportStrategies.size());

0 commit comments

Comments
 (0)