Skip to content

Commit 0c57214

Browse files
authored
Merge branch 'main' into main
2 parents e491dd5 + 555a0f7 commit 0c57214

File tree

17 files changed

+1200
-4
lines changed

17 files changed

+1200
-4
lines changed

applications/spring-shell/src/test/java/org/springframework/sbm/recipes/MigrateJaxRsAnnotationsRecipeIntegrationTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public String getHelloWorldJSON(@PathVariable("name") String name) throws Except
5050
}
5151
5252
@RequestMapping(value = "/json", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET)
53-
public String getAllPersons(@RequestParam(required = false, value = "q") String searchBy) throws Exception {
53+
public String getAllPersons(@RequestParam(required = false, value = "q") String searchBy, @RequestParam(required = false, defaultValue = "0", value = "page") int page) throws Exception {
5454
return "{\\"message\\":\\"No person here...\\"";
5555
}
5656

applications/spring-shell/src/test/resources/testcode/bootify-jaxrs/src/main/java/com/example/jee/app/PersonController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public String getHelloWorldJSON(@PathParam("name") String name) throws Exception
2020
@Path("/json")
2121
@Produces(MediaType.APPLICATION_JSON)
2222
@Consumes(MediaType.APPLICATION_JSON)
23-
public String getAllPersons(@QueryParam("q") String searchBy) throws Exception {
23+
public String getAllPersons(@QueryParam("q") String searchBy, @DefaultValue("0") @QueryParam("page") int page) throws Exception {
2424
return "{\"message\":\"No person here...\"";
2525
}
2626

components/sbm-core/src/main/java/org/springframework/sbm/build/api/Dependency.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@
1717

1818
import io.micrometer.core.lang.Nullable;
1919
import lombok.*;
20+
import org.openrewrite.semver.LatestRelease;
2021

2122
import javax.validation.constraints.NotNull;
2223
import java.util.ArrayList;
24+
import java.util.Comparator;
2325
import java.util.List;
2426

2527
@Getter
@@ -66,6 +68,15 @@ public String toString() {
6668
"</dependency>";
6769
}
6870

71+
public boolean isRecentThen(Dependency that){
72+
return this.equals(that) && comparator().compare(this, that) >= 0;
73+
}
74+
75+
private Comparator<Dependency> comparator(){
76+
LatestRelease latestRelease = new LatestRelease(null);
77+
return Comparator.comparing(Dependency::getVersion, latestRelease::compare);
78+
}
79+
6980
private String exclusionString() {
7081
if (exclusions.isEmpty()) {
7182
return "";
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package org.springframework.sbm.build.api;
2+
3+
import org.openrewrite.maven.internal.MavenPomDownloader;
4+
import org.openrewrite.maven.tree.GroupArtifactVersion;
5+
import org.openrewrite.maven.tree.MavenRepository;
6+
import org.springframework.sbm.openrewrite.RewriteExecutionContext;
7+
8+
import java.util.Collections;
9+
import java.util.HashMap;
10+
import java.util.List;
11+
import java.util.Map;
12+
import java.util.stream.Stream;
13+
14+
/**
15+
* This class holds all the dependencies included in a spring artifact
16+
*/
17+
public class SpringManagedDependencies {
18+
19+
private static List<MavenRepository> SPRING_REPOSITORIES = List.of(
20+
new MavenRepository("spring-release", "https://repo.spring.io/release", true, false, null, null)
21+
);
22+
23+
private List<org.openrewrite.maven.tree.Dependency> dependencies;
24+
private static Map<GroupArtifactVersion, SpringManagedDependencies> INSTANCES = new HashMap<>();
25+
26+
public static SpringManagedDependencies by(String groupId, String artifact, String version){
27+
final GroupArtifactVersion groupArtifactVersion =
28+
new GroupArtifactVersion(groupId, artifact, version);
29+
30+
INSTANCES.computeIfAbsent(groupArtifactVersion, SpringManagedDependencies::new);
31+
return INSTANCES.get(groupArtifactVersion);
32+
}
33+
34+
private SpringManagedDependencies(GroupArtifactVersion groupArtifactVersion){
35+
dependencies = new MavenPomDownloader(Collections.emptyMap(), new RewriteExecutionContext())
36+
.download(groupArtifactVersion, null, null, SPRING_REPOSITORIES)
37+
.getDependencies();
38+
}
39+
40+
public Stream<Dependency> stream(){
41+
return dependencies.stream()
42+
.map(d -> Dependency.builder()
43+
.groupId(d.getGroupId())
44+
.artifactId(d.getArtifactId())
45+
.version(d.getVersion())
46+
.build()
47+
);
48+
}
49+
50+
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package org.springframework.sbm.build.migration.actions;
2+
3+
import org.springframework.sbm.build.api.Dependency;
4+
import org.springframework.sbm.build.api.SpringManagedDependencies;
5+
import org.springframework.sbm.engine.context.ProjectContext;
6+
import org.springframework.sbm.engine.recipe.AbstractAction;
7+
8+
import java.util.List;
9+
import java.util.function.Predicate;
10+
import java.util.stream.Collectors;
11+
12+
import static org.openrewrite.maven.tree.Scope.Compile;
13+
14+
/**
15+
* The action removes the dependencies directly managed by Spring from the project dependencies
16+
* Add this action at the end of recipe so that any spring artifact inclusions as part of the
17+
* other actions are also included while removing the dependencies.
18+
*/
19+
public class RemoveManagedDependencies extends AbstractAction {
20+
21+
@Override
22+
public void apply(ProjectContext context) {
23+
//FIXME handle multi-module projects
24+
final List<Dependency> springManagedDependencies = context.getBuildFile()
25+
.getDeclaredDependencies(Compile)
26+
.stream()
27+
.filter(this::isSpringFrameworkDependency)
28+
.map(d -> SpringManagedDependencies.by(d.getGroupId(),d.getArtifactId(),d.getVersion()))
29+
.flatMap(SpringManagedDependencies::stream)
30+
.distinct()
31+
.collect(Collectors.toList());
32+
33+
Predicate<Dependency> isAlreadyManagedBySpring = d -> springManagedDependencies
34+
.stream()
35+
.filter(d::equals)
36+
.anyMatch(s -> s.isRecentThen(d));
37+
38+
final List<Dependency> dependenciesToBeRemoved = context.getBuildFile()
39+
.getDeclaredDependencies(Compile)
40+
.stream()
41+
.filter(isAlreadyManagedBySpring)
42+
.collect(Collectors.toList());
43+
44+
RemoveDependencies removeDependenciesAction = new RemoveDependencies();
45+
removeDependenciesAction.setDependencies(dependenciesToBeRemoved);
46+
removeDependenciesAction.apply(context);
47+
}
48+
49+
private boolean isSpringFrameworkDependency(Dependency dependency){
50+
return dependency.getGroupId().startsWith("org.springframework");
51+
}
52+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.springframework.sbm.build.api;
2+
3+
import org.junit.jupiter.api.Test;
4+
import static org.assertj.core.api.Assertions.assertThat;
5+
6+
public class SpringManagedDependenciesTest {
7+
8+
@Test
9+
public void pullBootStarter274Dependencies_expectJakartaAnnotationDependency(){
10+
String jakartaCoordinates = "jakarta.annotation:jakarta.annotation-api:1.3.5";
11+
12+
assertThat( SpringManagedDependencies.by("org.springframework.boot", "spring-boot-starter", "2.7.4")
13+
.stream()
14+
.map(Dependency::getCoordinates)
15+
.anyMatch(jakartaCoordinates::equals)
16+
).isTrue();
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package org.springframework.sbm.build.migration.actions;
2+
3+
import org.junit.jupiter.api.Test;
4+
import org.openrewrite.semver.LatestRelease;
5+
import org.springframework.sbm.build.api.Dependency;
6+
import org.springframework.sbm.engine.context.ProjectContext;
7+
import org.springframework.sbm.project.resource.TestProjectContext;
8+
9+
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
10+
11+
public class RemoveManagedDependenciesTest {
12+
13+
@Test
14+
public void givenProjectWithManagedDependency_removeSpringManagedDependencies_expectHibernateDependencyRemoved(){
15+
16+
LatestRelease latestRelease = new LatestRelease(null);
17+
System.out.println(latestRelease.compare(null, "5.6.11.Final", "5.6.11.Final"));
18+
19+
final String hibernateCoordinates = "org.hibernate:hibernate-core:5.6.11.Final";
20+
final String springBootDataJpaCoordinates = "org.springframework.boot:spring-boot-starter-data-jpa:2.7.4";
21+
22+
final ProjectContext projectContext = TestProjectContext.buildProjectContext()
23+
.withBuildFileHavingDependencies(hibernateCoordinates, springBootDataJpaCoordinates)
24+
.build();
25+
26+
RemoveManagedDependencies removeManagedDependencies = new RemoveManagedDependencies();
27+
removeManagedDependencies.apply(projectContext);
28+
29+
assertThat(projectContext.getBuildFile()
30+
.getDeclaredDependencies()
31+
.stream()
32+
.map(Dependency::getCoordinates)
33+
.anyMatch(hibernateCoordinates::equals)
34+
).isFalse();
35+
}
36+
37+
@Test
38+
public void givenProjectWithLowerVersionedManagedDependency_removeSpringManagedDependencies_expectDependencyRemoved(){
39+
final String hibernateCoordinates = "org.hibernate:hibernate-core:5.3.2.Final";
40+
final String springBootDataJpaCoordinates = "org.springframework.boot:spring-boot-starter-data-jpa:2.7.4";
41+
42+
final ProjectContext projectContext = TestProjectContext.buildProjectContext()
43+
.withBuildFileHavingDependencies(hibernateCoordinates, springBootDataJpaCoordinates)
44+
.build();
45+
46+
RemoveManagedDependencies removeManagedDependencies = new RemoveManagedDependencies();
47+
removeManagedDependencies.apply(projectContext);
48+
49+
assertThat(projectContext.getBuildFile()
50+
.getDeclaredDependencies()
51+
.stream()
52+
.map(Dependency::getCoordinates)
53+
.anyMatch(hibernateCoordinates::equals)
54+
).isFalse();
55+
}
56+
57+
@Test
58+
public void givenProjectWithHigherVersionedManagedDependency_removeSpringManagedDependencies_expectDependencyRemoved(){
59+
final String hibernateCoordinates = "org.hibernate:hibernate-core:5.12.2.Final";
60+
final String springBootDataJpaCoordinates = "org.springframework.boot:spring-boot-starter-data-jpa:2.7.4";
61+
62+
final ProjectContext projectContext = TestProjectContext.buildProjectContext()
63+
.withBuildFileHavingDependencies(hibernateCoordinates, springBootDataJpaCoordinates)
64+
.build();
65+
66+
RemoveManagedDependencies removeManagedDependencies = new RemoveManagedDependencies();
67+
removeManagedDependencies.apply(projectContext);
68+
69+
assertThat(projectContext.getBuildFile()
70+
.getDeclaredDependencies()
71+
.stream()
72+
.map(Dependency::getCoordinates)
73+
.anyMatch(hibernateCoordinates::equals)
74+
).isTrue();
75+
}
76+
}

components/sbm-recipes-jee-to-boot/src/main/java/org/springframework/sbm/jee/jaxrs/MigrateJaxRsRecipe.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
*/
1616
package org.springframework.sbm.jee.jaxrs;
1717

18-
import lombok.RequiredArgsConstructor;
1918
import org.openrewrite.java.ChangeType;
2019
import org.openrewrite.java.JavaParser;
2120
import org.springframework.context.annotation.Bean;
@@ -35,10 +34,10 @@
3534
import org.springframework.sbm.java.migration.conditions.HasTypeAnnotation;
3635
import org.springframework.sbm.jee.jaxrs.actions.ConvertJaxRsAnnotations;
3736
import org.springframework.sbm.jee.jaxrs.recipes.ReplaceMediaType;
37+
import org.springframework.sbm.jee.jaxrs.recipes.ReplaceRequestParameterProperties;
3838
import org.springframework.sbm.jee.jaxrs.recipes.SwapCacheControl;
3939
import org.springframework.sbm.jee.jaxrs.recipes.SwapHttHeaders;
4040
import org.springframework.sbm.jee.jaxrs.recipes.SwapResponseWithResponseEntity;
41-
import org.springframework.sbm.support.openrewrite.java.AddOrReplaceAnnotationAttribute;
4241

4342
import java.util.List;
4443
import java.util.function.Supplier;
@@ -128,6 +127,12 @@ public Recipe jaxRs(RewriteRecipeLoader rewriteRecipeLoader, RewriteRecipeRunner
128127
.recipe(new SwapResponseWithResponseEntity(javaParserSupplier))
129128
.build(),
130129

130+
JavaRecipeAction.builder()
131+
.condition(HasAnnotation.builder().annotation("org.springframework.web.bind.annotation.RequestParam").build())
132+
.description("Replace the JAX-RS properties of a request parameter (like default value) with it's Spring equivalent.")
133+
.recipe(new ReplaceRequestParameterProperties())
134+
.build(),
135+
131136
OpenRewriteDeclarativeRecipeAdapter.builder()
132137
.condition(HasAnnotation.builder().annotation("org.springframework.web.bind.annotation.RequestParam").build())
133138
.description("Adds required=false to all @RequestParam annotations")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright 2021 - 2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.sbm.jee.jaxrs.recipes;
17+
18+
import lombok.EqualsAndHashCode;
19+
import lombok.Value;
20+
import org.jetbrains.annotations.NotNull;
21+
import org.openrewrite.ExecutionContext;
22+
import org.openrewrite.Option;
23+
import org.openrewrite.Recipe;
24+
import org.openrewrite.TreeVisitor;
25+
import org.openrewrite.java.JavaIsoVisitor;
26+
import org.openrewrite.java.search.UsesType;
27+
import org.springframework.sbm.jee.jaxrs.recipes.visitors.CopyAnnotationAttributeVisitor;
28+
29+
/**
30+
* @author Vincent Botteman
31+
*/
32+
@Value
33+
@EqualsAndHashCode(callSuper = true)
34+
public class CopyAnnotationAttribute extends Recipe {
35+
@Option(displayName = "Source Annotation Type",
36+
description = "The fully qualified name of the source annotation.",
37+
example = "org.junit.Test")
38+
String sourceAnnotationType;
39+
40+
@Option(displayName = "Source Attribute name",
41+
description = "The name of the attribute on the source annotation containing the value to copy.",
42+
example = "timeout")
43+
String sourceAttributeName;
44+
45+
@Option(displayName = "Target Annotation Type",
46+
description = "The fully qualified name of the target annotation.",
47+
example = "org.junit.Test")
48+
String targetAnnotationType;
49+
50+
@Option(displayName = "Target Attribute name",
51+
description = "The name of the attribute on the target annotation which must be set to the value of the source attribute.",
52+
example = "timeout")
53+
String targetAttributeName;
54+
55+
@Override
56+
protected TreeVisitor<?, ExecutionContext> getSingleSourceApplicableTest() {
57+
return new UsesType<>(sourceAnnotationType);
58+
}
59+
60+
@Override
61+
public @NotNull String getDisplayName() {
62+
return "Copy an annotation attribute to another annotation";
63+
}
64+
65+
@Override
66+
public @NotNull String getDescription() {
67+
return "Copy the value of an annotation attribute to another annotation attribute.";
68+
}
69+
70+
@Override
71+
protected @NotNull JavaIsoVisitor<ExecutionContext> getVisitor() {
72+
return new CopyAnnotationAttributeVisitor(
73+
sourceAnnotationType,
74+
sourceAttributeName,
75+
targetAnnotationType,
76+
targetAttributeName
77+
);
78+
}
79+
}

0 commit comments

Comments
 (0)