Skip to content

Commit 5704b87

Browse files
authored
Merge branch 'main' into spike-webapp
2 parents e0004cb + 245af63 commit 5704b87

26 files changed

+1719
-105
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
---
2+
name: 3.0 Upgrade Recipe
3+
about: Template for Spring Boot 3 Upgrade Receipe
4+
title: '3.0 Upgrade Recipe: '
5+
labels: '3.0.0, good first issue, type: enhancement, upgrade:boot-recipe'
6+
assignees: ''
7+
8+
---
9+
10+
## What needs to be done
11+
12+
An automated migration recipe for [`{{Release Note title}}`]({{http://link-to-section.foo}}) should be created.
13+
14+
15+
### Condition
16+
{{Describe when the section should be rendered}}
17+
18+
19+
### Report Section Issue
20+
21+
{{GH id for report issue}}
22+
23+
24+
### DoD
25+
- [ ] Action implemented
26+
- [ ] Description provided
27+
- [ ] Tests (single and multi-module)
28+
- [ ] Condition implemented / reused
29+
- [ ] Description provided
30+
- [ ] Tests (single and multi-module)
31+
- [ ] Recipe yaml added
32+
- [ ] Test
33+
- [ ] Integration test (single and multi-module)
34+
35+
36+
37+
## Recipe YAML Example
38+
39+
This provides an example with information about how a automated migration recipe should be defined in `YAML`.
40+
41+
<details>
42+
<summary>Report Section YAML (example)</summary>
43+
44+
````yaml
45+
46+
````
47+
48+
</details>
49+
50+
## Additional Resources & Information
51+
- Example
52+
- Testing Actions
53+
- Testing Conditions
54+
- Testing Recipes
55+
- Integration Tests
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package org.springframework.sbm.build.api;
2+
3+
import lombok.NonNull;
4+
import org.apache.commons.lang3.tuple.Pair;
5+
import org.openrewrite.maven.tree.Scope;
6+
7+
import java.util.*;
8+
import java.util.stream.Collectors;
9+
10+
import static org.openrewrite.maven.tree.Scope.*;
11+
12+
/**
13+
* Resolve the dependency change spec. Their is a ascending
14+
* order of the scopes where a higher order covers its predecessor
15+
* and more. Below is the ascending order of the scopes.
16+
* <p>
17+
* Test (lowest), Runtime, Provided, Compile (highest)
18+
* <p>
19+
* Based on the above scope, the following rule decides the fate
20+
* of a proposed dependency change spec.
21+
* <p>
22+
* Rule 1 :- If the proposed dependency already exists
23+
* transitively but its scope is lesser than the proposed
24+
* scope, the proposed dependency will be added to the
25+
* build file.
26+
* <p>
27+
* Rule 2 :- If the proposed dependency already declared
28+
* directly but its scope is lesser than the the proposed
29+
* scope, the existing dependency will be replaced.
30+
* <p>
31+
* Rule 3 :- If there is no matching dependency already exists
32+
* the proposed dependency will beadded.
33+
*/
34+
public class DependencyChangeResolver {
35+
36+
public static final EnumSet<Scope> ALL_SCOPES = EnumSet.range(None, System);
37+
@NonNull
38+
private BuildFile buildFile;
39+
private @NonNull Dependency proposedChangeSpec;
40+
private List<Dependency> potentialMatches;
41+
42+
private static Map<Scope, List<Scope>> UPGRADE_GRAPH = new HashMap<>();
43+
44+
static {
45+
// For a given scope (key), SBM will upgrade ( upsert) if any of the listed scope
46+
// exists in the directly included dependencies
47+
UPGRADE_GRAPH.put(Compile, List.of(Test, Provided, Runtime));
48+
UPGRADE_GRAPH.put(Provided, List.of(Test, Runtime));
49+
UPGRADE_GRAPH.put(Runtime, List.of(Test));
50+
UPGRADE_GRAPH.put(Test, Collections.emptyList());
51+
}
52+
53+
public DependencyChangeResolver(BuildFile buildFile, @NonNull Dependency proposedChangeSpec) {
54+
this.buildFile = buildFile;
55+
this.proposedChangeSpec = proposedChangeSpec;
56+
this.potentialMatches = buildFile.getEffectiveDependencies()
57+
.stream()
58+
.filter(d -> d.equals(this.proposedChangeSpec))
59+
.collect(Collectors.toList());
60+
}
61+
62+
/**
63+
* Return a pair of dependencies to be removed ( left) and added ( right)
64+
* @return
65+
*/
66+
public Pair<List<Dependency>, Optional<Dependency>> apply() {
67+
if (potentialMatches.isEmpty())
68+
return Pair.of(Collections.emptyList(), Optional.of(proposedChangeSpec));
69+
70+
Scope proposedDependencyScope = Scope.fromName(proposedChangeSpec.getScope());
71+
List<Scope> supersededScopes = UPGRADE_GRAPH.get(proposedDependencyScope);
72+
73+
Optional<Dependency> right = potentialMatches
74+
.stream()
75+
.filter(d -> supersededScopes.contains(fromName(d.getScope())))
76+
.findAny()
77+
.map(any -> proposedChangeSpec);
78+
79+
List<Dependency> left = buildFile
80+
.getDeclaredDependencies(ALL_SCOPES.toArray(new Scope[0]))
81+
.stream()
82+
.filter(proposedChangeSpec::equals)
83+
.collect(Collectors.toList());
84+
85+
return Pair.of(left, right);
86+
87+
}
88+
89+
}

components/sbm-core/src/main/java/org/springframework/sbm/build/migration/actions/AddDependencies.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
package org.springframework.sbm.build.migration.actions;
1717

1818
import lombok.AllArgsConstructor;
19+
import org.apache.commons.lang3.tuple.Pair;
1920
import org.springframework.sbm.build.api.BuildFile;
2021
import org.springframework.sbm.build.api.Dependency;
22+
import org.springframework.sbm.build.api.DependencyChangeResolver;
2123
import org.springframework.sbm.engine.recipe.AbstractAction;
2224
import org.springframework.sbm.engine.context.ProjectContext;
2325
import lombok.Getter;
@@ -29,6 +31,9 @@
2931
import javax.validation.Valid;
3032
import java.util.ArrayList;
3133
import java.util.List;
34+
import java.util.Optional;
35+
import java.util.function.Predicate;
36+
import java.util.stream.Collectors;
3237

3338
@Setter
3439
@Getter
@@ -51,6 +56,23 @@ public AddDependencies(List<Dependency> dependencies) {
5156
@Override
5257
public void apply(ProjectContext context) {
5358
BuildFile buildFile = context.getBuildFile();
54-
buildFile.addDependencies(dependencies);
59+
List<Pair<List<Dependency>, Optional<Dependency>>> pairs = dependencies.stream()
60+
.map(d -> new DependencyChangeResolver(buildFile, d))
61+
.map(DependencyChangeResolver::apply)
62+
.collect(Collectors.toList());
63+
64+
List<Dependency> removeList = pairs.stream()
65+
.map(Pair::getLeft)
66+
.flatMap(List::stream)
67+
.collect(Collectors.toList());
68+
69+
List<Dependency> addList = pairs.stream()
70+
.map(Pair::getRight)
71+
.filter(Optional::isPresent)
72+
.map(Optional::get)
73+
.collect(Collectors.toList());
74+
75+
buildFile.removeDependencies(removeList);
76+
buildFile.addDependencies(addList);
5577
}
5678
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
package org.springframework.sbm.build.api;
2+
3+
import org.apache.commons.lang3.tuple.Pair;
4+
import org.junit.jupiter.api.Test;
5+
import org.junit.jupiter.api.extension.ExtendWith;
6+
import org.mockito.Mock;
7+
import org.mockito.junit.jupiter.MockitoExtension;
8+
import org.springframework.sbm.build.impl.OpenRewriteMavenBuildFile;
9+
10+
import java.util.Collections;
11+
import java.util.List;
12+
import java.util.Optional;
13+
import java.util.Set;
14+
15+
import static org.assertj.core.api.Assertions.assertThat;
16+
import static org.mockito.Mockito.any;
17+
import static org.mockito.Mockito.when;
18+
19+
@ExtendWith(MockitoExtension.class)
20+
public class DependencyChangeResolverTest {
21+
22+
@Mock
23+
OpenRewriteMavenBuildFile buildFile;
24+
25+
@Test
26+
public void givenBuildFile_addNewDependency_withScopeCompile_expectNewDependencyAdded(){
27+
Dependency proposedDependency =
28+
new Dependency.DependencyBuilder()
29+
.groupId("org.springframework.sbm")
30+
.artifactId("directDependency")
31+
.version("1.0")
32+
.scope("compile")
33+
.build();
34+
35+
when(buildFile.getEffectiveDependencies())
36+
.thenReturn(Collections.emptySet());
37+
38+
Pair<List<Dependency>, Optional<Dependency>> pair =
39+
new DependencyChangeResolver(buildFile, proposedDependency).apply();
40+
41+
assertThat(pair.getLeft().isEmpty());
42+
assertThat(pair.getRight().isPresent());
43+
assertThat(pair.getRight().get().equals(proposedDependency));
44+
}
45+
46+
@Test
47+
public void givenBuildFile_addExistingTransitiveDependency_withLowerScope_expectNoOp(){
48+
Dependency proposedDependency =
49+
new Dependency.DependencyBuilder()
50+
.groupId("org.springframework.sbm")
51+
.artifactId("directDependency")
52+
.version("1.0")
53+
.scope("test")
54+
.build();
55+
56+
Dependency existingDependency =
57+
new Dependency.DependencyBuilder()
58+
.groupId("org.springframework.sbm")
59+
.artifactId("directDependency")
60+
.version("1.0")
61+
.scope("compile")
62+
.build();
63+
64+
when(buildFile.getEffectiveDependencies())
65+
.thenReturn(Set.of(existingDependency));
66+
67+
when(buildFile.getDeclaredDependencies(any()))
68+
.thenReturn(Collections.emptyList());
69+
70+
Pair<List<Dependency>, Optional<Dependency>> pair =
71+
new DependencyChangeResolver(buildFile, proposedDependency).apply();
72+
73+
assertThat(pair.getLeft().isEmpty());
74+
assertThat(pair.getRight().isEmpty());
75+
}
76+
77+
@Test
78+
public void givenBuildFile_addExistingTransitiveDependency_withHigherScope_expectNoOp(){
79+
Dependency proposedDependency =
80+
new Dependency.DependencyBuilder()
81+
.groupId("org.springframework.sbm")
82+
.artifactId("directDependency")
83+
.version("1.0")
84+
.scope("compile")
85+
.build();
86+
87+
Dependency existingDependency =
88+
new Dependency.DependencyBuilder()
89+
.groupId("org.springframework.sbm")
90+
.artifactId("directDependency")
91+
.version("1.0")
92+
.scope("test")
93+
.build();
94+
95+
when(buildFile.getEffectiveDependencies())
96+
.thenReturn(Set.of(existingDependency));
97+
98+
when(buildFile.getDeclaredDependencies(any()))
99+
.thenReturn(Collections.emptyList());
100+
101+
Pair<List<Dependency>, Optional<Dependency>> pair =
102+
new DependencyChangeResolver(buildFile, proposedDependency).apply();
103+
104+
assertThat(pair.getLeft().isEmpty());
105+
assertThat(pair.getRight().isPresent());
106+
assertThat(pair.getRight().get().equals(proposedDependency));
107+
}
108+
109+
@Test
110+
public void givenBuildFile_addExistingDirectDependency_withHigherScope_expectNoOp(){
111+
Dependency proposedDependency =
112+
new Dependency.DependencyBuilder()
113+
.groupId("org.springframework.sbm")
114+
.artifactId("directDependency")
115+
.version("1.0")
116+
.scope("compile")
117+
.build();
118+
119+
Dependency existingDependency =
120+
new Dependency.DependencyBuilder()
121+
.groupId("org.springframework.sbm")
122+
.artifactId("directDependency")
123+
.version("1.0")
124+
.scope("test")
125+
.build();
126+
127+
when(buildFile.getEffectiveDependencies())
128+
.thenReturn(Set.of(existingDependency));
129+
130+
when(buildFile.getDeclaredDependencies(any()))
131+
.thenReturn(List.of(existingDependency));
132+
133+
Pair<List<Dependency>, Optional<Dependency>> pair =
134+
new DependencyChangeResolver(buildFile, proposedDependency).apply();
135+
136+
assertThat(!pair.getLeft().isEmpty());
137+
assertThat(pair.getLeft().get(0).getScope().equals("test"));
138+
assertThat(pair.getRight().isPresent());
139+
assertThat(pair.getRight().get().getScope().equals("compile"));
140+
}
141+
142+
}

components/sbm-recipes-boot-upgrade/src/main/java/org/springframework/sbm/boot/upgrade_27_30/CrudRepositoryExtension.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818

1919
import lombok.Setter;
20-
import lombok.Value;
2120
import org.jetbrains.annotations.NotNull;
2221
import org.openrewrite.ExecutionContext;
2322
import org.openrewrite.Recipe;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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.boot.upgrade_27_30.report;
17+
18+
import lombok.Getter;
19+
import lombok.Setter;
20+
import org.jetbrains.annotations.NotNull;
21+
import org.jetbrains.annotations.Nullable;
22+
23+
import javax.validation.constraints.NotEmpty;
24+
import java.util.ArrayList;
25+
import java.util.List;
26+
27+
/**
28+
* @author Fabian Krüger
29+
*/
30+
@Getter
31+
@Setter
32+
public class Remediation extends ResourceList{
33+
@NotEmpty
34+
private String description;
35+
@NotNull
36+
private List<RemediationPossibility> possibilities = new ArrayList<>();
37+
/**
38+
* The name of the recipe for automated migration or {@code null} of none exist.
39+
*/
40+
@Nullable
41+
private String recipe;
42+
}

0 commit comments

Comments
 (0)