Skip to content

Commit c3269ff

Browse files
committed
API Versioning reconcile problem Quick Fixes
Signed-off-by: BoykoAlex <alex.boyko@broadcom.com>
1 parent 0e93ff5 commit c3269ff

File tree

33 files changed

+2859
-91
lines changed

33 files changed

+2859
-91
lines changed

headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/SpringProjectUtil.java

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,16 @@
1111
package org.springframework.ide.vscode.commons.java;
1212

1313
import java.io.File;
14+
import java.io.IOException;
15+
import java.nio.file.Files;
16+
import java.nio.file.Path;
17+
import java.util.ArrayList;
18+
import java.util.List;
1419
import java.util.Optional;
1520
import java.util.function.Predicate;
1621
import java.util.regex.Matcher;
1722
import java.util.regex.Pattern;
23+
import java.util.stream.Stream;
1824

1925
import org.slf4j.Logger;
2026
import org.slf4j.LoggerFactory;
@@ -142,5 +148,60 @@ public static Predicate<IJavaProject> libraryVersionGreaterOrEqual(String librar
142148
return true;
143149
};
144150
}
151+
152+
/**
153+
* Finds Spring Boot application.properties and application.yml files in the project's resource folders.
154+
* Prioritizes main application.properties/yml files over profile-specific files (application-*.properties/yml).
155+
* Excludes test resources.
156+
*
157+
* @param project the Java project
158+
* @return list of Boot properties/YAML file paths, with main files prioritized over profile-specific files
159+
*/
160+
public static List<Path> findBootPropertiesFiles(IJavaProject project) {
161+
List<Path> mainFiles = new ArrayList<>();
162+
List<Path> profileFiles = new ArrayList<>();
163+
164+
try {
165+
project.getClasspath().getClasspathEntries().stream()
166+
.filter(Classpath::isSource)
167+
.filter(cpe -> !cpe.isTest()) // Exclude test resources
168+
.filter(cpe -> !cpe.isJavaContent()) // Only resource folders
169+
.map(cpe -> new File(cpe.getPath()).toPath())
170+
.flatMap(folder -> {
171+
try {
172+
return Files.list(folder);
173+
} catch (IOException e) {
174+
return Stream.empty();
175+
}
176+
})
177+
.forEach(path -> {
178+
String fileName = path.getFileName().toString();
179+
180+
// Main application files - highest priority
181+
if ("application.properties".equals(fileName)) {
182+
mainFiles.add(path);
183+
} else if ("application.yml".equals(fileName) || "application.yaml".equals(fileName)) {
184+
mainFiles.add(path);
185+
}
186+
// Profile-specific files - lower priority
187+
else if (fileName.matches("application-.*\\.properties")) {
188+
profileFiles.add(path);
189+
} else if (fileName.matches("application-.*\\.ya?ml")) {
190+
profileFiles.add(path);
191+
}
192+
});
193+
} catch (Exception e) {
194+
// ignore
195+
}
196+
197+
// Return main files first, then profile files if no main files exist
198+
List<Path> result = new ArrayList<>();
199+
result.addAll(mainFiles);
200+
if (mainFiles.isEmpty()) {
201+
result.addAll(profileFiles);
202+
}
203+
return result;
204+
}
205+
145206

146207
}

headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/AddSpringProperty.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import org.openrewrite.yaml.MergeYaml;
2424
import org.openrewrite.yaml.tree.Yaml;
2525

26+
import com.fasterxml.jackson.annotation.JsonCreator;
27+
2628
import java.nio.file.Path;
2729
import java.util.List;
2830
import java.util.Objects;
@@ -65,7 +67,7 @@ public class AddSpringProperty extends Recipe {
6567
@Nullable
6668
List<String> pathExpressions;
6769

68-
70+
@JsonCreator
6971
public AddSpringProperty(String property, String value, @Nullable String comment,
7072
@Nullable List<String> pathExpressions) {
7173
this.property = property;

headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/AutowiredFieldIntoConstructorParameterVisitor.java

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,37 @@
1515
*/
1616
package org.openrewrite.java.spring;
1717

18+
import java.util.Collections;
19+
import java.util.List;
20+
import java.util.Objects;
21+
import java.util.Optional;
22+
import java.util.stream.Collectors;
23+
import java.util.stream.Stream;
24+
1825
import org.openrewrite.Cursor;
1926
import org.openrewrite.ExecutionContext;
2027
import org.openrewrite.Tree;
2128
import org.openrewrite.internal.ListUtils;
22-
import org.openrewrite.java.*;
23-
import org.openrewrite.java.tree.*;
29+
import org.openrewrite.java.AnnotationMatcher;
30+
import org.openrewrite.java.JavaIsoVisitor;
31+
import org.openrewrite.java.JavaTemplate;
32+
import org.openrewrite.java.JavaVisitor;
33+
import org.openrewrite.java.RemoveAnnotationVisitor;
34+
import org.openrewrite.java.tree.Expression;
35+
import org.openrewrite.java.tree.J;
2436
import org.openrewrite.java.tree.J.Block;
2537
import org.openrewrite.java.tree.J.ClassDeclaration;
2638
import org.openrewrite.java.tree.J.MethodDeclaration;
2739
import org.openrewrite.java.tree.J.VariableDeclarations;
40+
import org.openrewrite.java.tree.JavaType;
2841
import org.openrewrite.java.tree.JavaType.FullyQualified;
42+
import org.openrewrite.java.tree.Space;
43+
import org.openrewrite.java.tree.Statement;
44+
import org.openrewrite.java.tree.TypeTree;
45+
import org.openrewrite.java.tree.TypeUtils;
2946
import org.openrewrite.marker.Markers;
3047

31-
import java.util.Collections;
32-
import java.util.List;
33-
import java.util.Objects;
34-
import java.util.Optional;
35-
import java.util.stream.Collectors;
36-
import java.util.stream.Stream;
48+
import com.fasterxml.jackson.annotation.JsonCreator;
3749

3850

3951
public class AutowiredFieldIntoConstructorParameterVisitor extends JavaVisitor<ExecutionContext> {
@@ -43,7 +55,7 @@ public class AutowiredFieldIntoConstructorParameterVisitor extends JavaVisitor<E
4355
private final String fieldName;
4456

4557

46-
58+
@JsonCreator
4759
public AutowiredFieldIntoConstructorParameterVisitor(String classFqName, String fieldName) {
4860
this.classFqName = classFqName;
4961
this.fieldName = fieldName;

headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/ChangeSpringPropertyValue.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,18 @@
1818
import java.util.Objects;
1919

2020
import org.jspecify.annotations.Nullable;
21-
import org.openrewrite.*;
21+
import org.openrewrite.ExecutionContext;
22+
import org.openrewrite.Option;
23+
import org.openrewrite.Recipe;
24+
import org.openrewrite.Tree;
25+
import org.openrewrite.TreeVisitor;
26+
import org.openrewrite.Validated;
2227
import org.openrewrite.internal.StringUtils;
2328
import org.openrewrite.properties.tree.Properties;
2429
import org.openrewrite.yaml.tree.Yaml;
2530

31+
import com.fasterxml.jackson.annotation.JsonCreator;
32+
2633
public class ChangeSpringPropertyValue extends Recipe {
2734

2835
@Override
@@ -65,6 +72,7 @@ public String getDescription() {
6572
@Nullable
6673
Boolean relaxedBinding;
6774

75+
@JsonCreator
6876
public ChangeSpringPropertyValue(String propertyKey, String newValue, @Nullable String oldValue,
6977
@Nullable Boolean regex, @Nullable Boolean relaxedBinding) {
7078
this.propertyKey = propertyKey;

headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/CommentOutSpringPropertyKey.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,16 @@
1919

2020
import org.jspecify.annotations.NonNull;
2121
import org.jspecify.annotations.Nullable;
22-
import org.openrewrite.*;
22+
import org.openrewrite.ExecutionContext;
23+
import org.openrewrite.Option;
24+
import org.openrewrite.Recipe;
25+
import org.openrewrite.Tree;
26+
import org.openrewrite.TreeVisitor;
2327
import org.openrewrite.properties.tree.Properties;
2428
import org.openrewrite.yaml.tree.Yaml;
2529

30+
import com.fasterxml.jackson.annotation.JsonCreator;
31+
2632
public class CommentOutSpringPropertyKey extends Recipe {
2733

2834
@Override
@@ -45,9 +51,8 @@ public String getDescription() {
4551
example = "This property is deprecated and no longer applicable starting from Spring Boot 3.0.x")
4652
String comment;
4753

48-
54+
@JsonCreator
4955
public CommentOutSpringPropertyKey(String propertyKey, String comment) {
50-
super();
5156
this.propertyKey = propertyKey;
5257
this.comment = comment;
5358
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 Broadcom, Inc.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Broadcom, Inc. - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.ide.vscode.commons.rewrite.java;
12+
13+
import java.util.List;
14+
15+
import org.openrewrite.NlsRewrite.Description;
16+
import org.openrewrite.NlsRewrite.DisplayName;
17+
import org.openrewrite.config.CompositeRecipe;
18+
import org.springframework.ide.vscode.commons.rewrite.java.AddApiVersioningConfigurationCall.ConfigType;
19+
20+
import com.fasterxml.jackson.annotation.JsonCreator;
21+
import com.fasterxml.jackson.annotation.JsonProperty;
22+
23+
public class AddApiVersionConfig extends CompositeRecipe {
24+
25+
@JsonCreator
26+
public AddApiVersionConfig(
27+
@JsonProperty("filePath") String filePath,
28+
@JsonProperty("pkgName") String pkgName,
29+
@JsonProperty("isFlux") boolean isFlux,
30+
@JsonProperty("configType") ConfigType configType,
31+
@JsonProperty("value") String value) {
32+
super(List.of(
33+
new AddWebConfigurerBean(filePath, pkgName, isFlux),
34+
new AddApiVersioningConfigMethod(filePath),
35+
new AddApiVersioningConfigurationCall(filePath, configType, value)
36+
));
37+
}
38+
39+
@Override
40+
public @DisplayName String getDisplayName() {
41+
return "Add API versioning configuration to Web Configurer";
42+
}
43+
44+
@Override
45+
public @Description String getDescription() {
46+
return "Creates or updates a Web Configurer class with API versioning configuration. "
47+
+ "This composite recipe performs three steps: "
48+
+ "1) Creates a WebMvcConfigurer or WebFluxConfigurer @Configuration class if it doesn't exist, "
49+
+ "2) Adds the configureApiVersioning method if not present, and "
50+
+ "3) Adds the appropriate versioning configuration call (useRequestHeader, useQueryParam, usePathSegment, or useMediaTypeParameter).";
51+
}
52+
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 Broadcom, Inc.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Broadcom, Inc. - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.ide.vscode.commons.rewrite.java;
12+
13+
import java.nio.file.Path;
14+
import java.nio.file.Paths;
15+
16+
import org.openrewrite.ExecutionContext;
17+
import org.openrewrite.NlsRewrite.Description;
18+
import org.openrewrite.NlsRewrite.DisplayName;
19+
import org.openrewrite.Option;
20+
import org.openrewrite.Recipe;
21+
import org.openrewrite.TreeVisitor;
22+
import org.openrewrite.java.JavaIsoVisitor;
23+
import org.openrewrite.java.JavaParser;
24+
import org.openrewrite.java.JavaTemplate;
25+
import org.openrewrite.java.MethodMatcher;
26+
import org.openrewrite.java.tree.J;
27+
import org.openrewrite.java.tree.J.CompilationUnit;
28+
import org.openrewrite.java.tree.TypeUtils;
29+
30+
import com.fasterxml.jackson.annotation.JsonCreator;
31+
import com.fasterxml.jackson.annotation.JsonProperty;
32+
33+
public class AddApiVersioningConfigMethod extends Recipe {
34+
35+
private static final String WEB_MVC_CONFIGURER = "org.springframework.web.servlet.config.annotation.WebMvcConfigurer";
36+
private static final String WEB_FLUX_CONFIGURER = "org.springframework.web.reactive.config.WebFluxConfigurer";
37+
private static final String API_VERSION_CONFIGURER = "org.springframework.web.servlet.config.annotation.ApiVersionConfigurer";
38+
39+
private static final MethodMatcher CONFIG_METHOD_MATCHER = new MethodMatcher(
40+
"* configureApiVersioning(org.springframework.web.servlet.config.annotation.ApiVersionConfigurer)");
41+
42+
@Option(description = "Path to the Java file", example = "com/example/config/WebConfig.java")
43+
private String filePath;
44+
45+
@JsonCreator
46+
public AddApiVersioningConfigMethod(@JsonProperty("filePath") String filePath) {
47+
this.filePath = filePath;
48+
}
49+
50+
@Override
51+
public @DisplayName String getDisplayName() {
52+
return "Add configureApiVersioning method";
53+
}
54+
55+
@Override
56+
public @Description String getDescription() {
57+
return "Adds configureApiVersioning method to WebMvcConfigurer or WebFluxConfigurer class if it doesn't exist.";
58+
}
59+
60+
@Override
61+
public TreeVisitor<?, ExecutionContext> getVisitor() {
62+
final Path file = Paths.get(filePath);
63+
return new JavaIsoVisitor<ExecutionContext>() {
64+
65+
@Override
66+
public CompilationUnit visitCompilationUnit(CompilationUnit cu, ExecutionContext p) {
67+
Path cuPath = cu.getSourcePath();
68+
// Only process if this is the target file
69+
if (cuPath == null || !cuPath.equals(file)) {
70+
return cu;
71+
} else {
72+
return super.visitCompilationUnit(cu, p);
73+
}
74+
}
75+
76+
@Override
77+
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) {
78+
J.ClassDeclaration c = super.visitClassDeclaration(classDecl, ctx);
79+
80+
// Check if class implements WebMvcConfigurer or WebFluxConfigurer
81+
if (!isWebConfigurer(c)) {
82+
return c;
83+
}
84+
85+
// Check if configureApiVersioning method already exists
86+
if (hasConfigureApiVersioningMethod(c)) {
87+
return c;
88+
}
89+
90+
// Add the configureApiVersioning method
91+
return addConfigureApiVersioningMethod(c, ctx);
92+
}
93+
94+
private boolean isWebConfigurer(J.ClassDeclaration classDecl) {
95+
return TypeUtils.isAssignableTo(WEB_MVC_CONFIGURER, classDecl.getType())
96+
|| TypeUtils.isAssignableTo(WEB_FLUX_CONFIGURER, classDecl.getType());
97+
}
98+
99+
private boolean hasConfigureApiVersioningMethod(J.ClassDeclaration classDecl) {
100+
return classDecl.getBody().getStatements().stream()
101+
.filter(J.MethodDeclaration.class::isInstance)
102+
.map(J.MethodDeclaration.class::cast)
103+
.anyMatch(method -> CONFIG_METHOD_MATCHER.matches(method.getMethodType()));
104+
}
105+
106+
private J.ClassDeclaration addConfigureApiVersioningMethod(J.ClassDeclaration classDecl, ExecutionContext ctx) {
107+
JavaTemplate template = JavaTemplate.builder("""
108+
@Override
109+
public void configureApiVersioning(ApiVersionConfigurer configurer) {
110+
}
111+
""")
112+
.contextSensitive()
113+
.javaParser(JavaParser.fromJavaVersion()
114+
.dependsOn("""
115+
package org.springframework.web.servlet.config.annotation;
116+
public interface ApiVersionConfigurer {}
117+
""")
118+
)
119+
.imports(API_VERSION_CONFIGURER)
120+
.build();
121+
122+
maybeAddImport(API_VERSION_CONFIGURER);
123+
124+
return template.apply(
125+
getCursor(),
126+
classDecl.getBody().getCoordinates().lastStatement()
127+
);
128+
}
129+
};
130+
}
131+
}

0 commit comments

Comments
 (0)