Skip to content

Commit

Permalink
Merge branch 'main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
timtebeek authored Aug 2, 2024
2 parents 2fb1404 + baf7482 commit 97ee63a
Show file tree
Hide file tree
Showing 42 changed files with 894 additions and 279 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ See the full documentation at [docs.openrewrite.org](https://docs.openrewrite.or
3. Commit & push changes.
4. Repeat periodically as new minor versions of Spring Boot are released.

## Why do artifact scanners detect vulnerabilities in recipe artifacts/JARs?

In order to modernize and upgrade old or vulnerable code, some OpenRewrite recipe modules bundle copies of old libraries. Libraries bundled into recipe modules are never executed.

OpenRewrite exercises the Java compiler internally to compile code patterns that exist in these old and/or vulnerable libraries. These patterns are then used to match old or vulnerable code for the sake of modernizing or repairing it.

Using a library in compilation in this way does not trigger class initialization in the way that reflection might, for example. In other words, code paths in libraries used in compilation are never executed, and thus the vulnerability is not exploitable.

The jar has libraries bundled inside of the [META-INF/rewrite/classpath directory](https://github.com/openrewrite/rewrite-spring/tree/main/src/main/resources/META-INF/rewrite/classpath). However, those JARs are not made into a Fat Jar or a shaded library in the traditional sense. It is not possible that by using rewrite-spring that one of those libraries gets called.

## Contributing

We appreciate all types of contributions. See the [contributing guide](https://github.com/openrewrite/.github/blob/main/CONTRIBUTING.md) for detailed instructions on how to get started.
4 changes: 3 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ plugins {
group = "org.openrewrite.recipe"
description = "Eliminate legacy Spring patterns and migrate between major Spring Boot versions. Automatically."

val springBootVersions: List<String> = listOf("1_5", "2_1", "2_2", "2_3", "2_4", "2_5", "2_6", "2_7", "3_0", "3_2")
val springBootVersions: List<String> = listOf("1_5", "2_1", "2_2", "2_3", "2_4", "2_5", "2_6", "2_7", "3_0", "3_2", "3_3")
val springSecurityVersions: List<String> = listOf("5_7", "5_8", "6_2")

val sourceSetNames: Map<String, List<String>> = mapOf(
Expand Down Expand Up @@ -144,6 +144,7 @@ dependencies {
"testWithSpringBoot_1_5RuntimeOnly"("org.springframework.boot:spring-boot:1.5.+")
"testWithSpringBoot_1_5RuntimeOnly"("org.springframework.boot:spring-boot-autoconfigure:1.5.+")
"testWithSpringBoot_1_5RuntimeOnly"("org.springframework.boot:spring-boot-test:1.5.+")
"testWithSpringBoot_1_5RuntimeOnly"("org.springframework.boot:spring-boot-starter-validation:1.5.+")
"testWithSpringBoot_1_5RuntimeOnly"("org.hamcrest:hamcrest:2.2")
"testWithSpringBoot_1_5RuntimeOnly"("junit:junit:latest.release")

Expand All @@ -158,6 +159,7 @@ dependencies {
"testWithSpringBoot_2_1RuntimeOnly"("org.springframework.security:spring-security-web:5.1.+")

"testWithSpringBoot_2_2RuntimeOnly"("org.springframework.boot:spring-boot:2.2.+")
"testWithSpringBoot_2_2RuntimeOnly"("org.springframework.boot:spring-boot-starter-validation:2.2.+")

"testWithSpringBoot_2_3RuntimeOnly"("org.springframework:spring-test:5.3.+")
"testWithSpringBoot_2_3RuntimeOnly"("junit:junit:latest.release")
Expand Down
Binary file modified gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
4 changes: 2 additions & 2 deletions gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionSha256Sum=a4b4158601f8636cdeeab09bd76afb640030bb5b144aafe261a5e8af027dc612
distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab
4 changes: 3 additions & 1 deletion gradlew
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#

##############################################################################
#
Expand Down Expand Up @@ -84,7 +86,7 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
Expand Down
2 changes: 2 additions & 0 deletions gradlew.bat
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem

@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,7 @@ private boolean methodArgumentHasSingleType(J.Assignment assignment) {
return newArray.getInitializer() != null && newArray.getInitializer().size() == 1;
}

@Nullable
private String requestMethodType(@Nullable J.Assignment assignment) {
private @Nullable String requestMethodType(@Nullable J.Assignment assignment) {
if(assignment == null) {
return null;
}
Expand Down Expand Up @@ -171,8 +170,7 @@ private String requestMethodType(@Nullable J.Assignment assignment) {
return null;
}

@Nullable
private String associatedRequestMapping(String method) {
private @Nullable String associatedRequestMapping(String method) {
switch (method) {
case "POST":
case "PUT":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,7 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx)
return j;
}

@Nullable
private J removeMethods(Expression expression, int depth, boolean isLambdaBody, Stack<Space> selectAfter) {
private @Nullable J removeMethods(Expression expression, int depth, boolean isLambdaBody, Stack<Space> selectAfter) {
if (!(expression instanceof J.MethodInvocation)) {
return expression;
}
Expand Down
16 changes: 5 additions & 11 deletions src/main/java/org/openrewrite/java/spring/RenameBean.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,12 @@ public String getDescription() {
return "Renames a Spring bean, both declaration and references.";
}


/**
* @param methodDeclaration, which may or may not declare a bean
* @param newName, for the potential bean
* @return a recipe for this methodDeclaration if it declares a bean, or null if it does not declare a bean
*/
@Nullable
public static RenameBean fromDeclaration(J.MethodDeclaration methodDeclaration, String newName) {
public static @Nullable RenameBean fromDeclaration(J.MethodDeclaration methodDeclaration, String newName) {
return methodDeclaration.getMethodType() == null ? null
: fromDeclaration(methodDeclaration, newName, methodDeclaration.getMethodType().getReturnType().toString());
}
Expand All @@ -92,8 +90,7 @@ public static RenameBean fromDeclaration(J.MethodDeclaration methodDeclaration,
* @param type, to override the type field on the returned RenameBean instance
* @return a recipe for this methodDeclaration if it declares a bean, or null if it does not declare a bean
*/
@Nullable
public static RenameBean fromDeclaration(J.MethodDeclaration methodDeclaration, String newName, @Nullable String type) {
public static @Nullable RenameBean fromDeclaration(J.MethodDeclaration methodDeclaration, String newName, @Nullable String type) {
BeanSearchResult beanSearchResult = isBean(methodDeclaration.getAllAnnotations(), BEAN_METHOD_ANNOTATIONS);
if (!beanSearchResult.isBean || methodDeclaration.getMethodType() == null) {
return null;
Expand All @@ -108,8 +105,7 @@ public static RenameBean fromDeclaration(J.MethodDeclaration methodDeclaration,
* @param newName, for the potential bean
* @return a recipe for this classDeclaration if it declares a bean, or null if it does not declare a bean
*/
@Nullable
public static RenameBean fromDeclaration(J.ClassDeclaration classDeclaration, String newName) {
public static @Nullable RenameBean fromDeclaration(J.ClassDeclaration classDeclaration, String newName) {
return classDeclaration.getType() == null ? null
: fromDeclaration(classDeclaration, newName, classDeclaration.getType().toString());
}
Expand All @@ -120,8 +116,7 @@ public static RenameBean fromDeclaration(J.ClassDeclaration classDeclaration, St
* @param type, to override the type field on the returned RenameBean instance
* @return a recipe for this classDeclaration if it declares a bean, or null if it does not declare a bean
*/
@Nullable
public static RenameBean fromDeclaration(J.ClassDeclaration classDeclaration, String newName, @Nullable String type) {
public static @Nullable RenameBean fromDeclaration(J.ClassDeclaration classDeclaration, String newName, @Nullable String type) {
BeanSearchResult beanSearchResult = isBean(classDeclaration.getAllAnnotations(), BEAN_TYPE_ANNOTATIONS);
if (!beanSearchResult.isBean || classDeclaration.getType() == null) {
return null;
Expand Down Expand Up @@ -302,8 +297,7 @@ private boolean maybeRenameBean(Collection<J.Annotation> annotations, Set<String
});
}

@Nullable
private static J.Assignment asBeanNameAssignment(Expression argumentExpression) {
private static @Nullable J.Assignment asBeanNameAssignment(Expression argumentExpression) {
if (argumentExpression instanceof J.Assignment) {
Expression variable = ((J.Assignment) argumentExpression).getVariable();
if (variable instanceof J.Identifier) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@
import lombok.EqualsAndHashCode;
import lombok.Value;
import org.openrewrite.*;
import org.openrewrite.internal.ListUtils;
import org.openrewrite.internal.lang.Nullable;
import org.openrewrite.java.AnnotationMatcher;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.JavaParser;
import org.openrewrite.java.JavaTemplate;
import org.openrewrite.java.RemoveAnnotation;
import org.openrewrite.java.search.FindAnnotations;
import org.openrewrite.java.tree.J;
import org.openrewrite.marker.Marker;
import org.openrewrite.text.PlainText;
Expand All @@ -33,10 +35,20 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.util.Comparator.comparing;

@Value
@EqualsAndHashCode(callSuper = false)
public class MoveAutoConfigurationToImportsFile extends ScanningRecipe<MoveAutoConfigurationToImportsFile.Accumulator> {
private static final String AUTOCONFIGURATION_FILE = "org.springframework.boot.autoconfigure.AutoConfiguration.imports";
private static final String ENABLE_AUTO_CONFIG_KEY = "org.springframework.boot.autoconfigure.EnableAutoConfiguration";

@Option(displayName = "Preserve `spring.factories` files",
description = "Don't delete the `spring.factories` for backward compatibility.",
required = false)
@Nullable
Boolean preserveFactoriesFile;

@Override
public String getDisplayName() {
return "Use `AutoConfiguration#imports`";
Expand All @@ -45,8 +57,8 @@ public String getDisplayName() {
@Override
public String getDescription() {
return "Use `AutoConfiguration#imports` instead of the deprecated entry " +
"`EnableAutoConfiguration` in `spring.factories` when defining " +
"autoconfiguration classes.";
"`EnableAutoConfiguration` in `spring.factories` when defining " +
"autoconfiguration classes.";
}

@Override
Expand Down Expand Up @@ -101,9 +113,9 @@ public Collection<SourceFile> generate(Accumulator acc, ExecutionContext ctx) {

PlainTextParser parser = new PlainTextParser();
PlainText brandNewFile = parser.parse(String.join("\n", finalList))
.map(PlainText.class::cast)
.findFirst()
.get();
.map(PlainText.class::cast)
.findFirst()
.get();
newImportFiles.add(brandNewFile
.withSourcePath(entry.getKey())
.withMarkers(brandNewFile.getMarkers().withMarkers(entry.getValue().getMarkers()))
Expand Down Expand Up @@ -147,8 +159,7 @@ public TreeVisitor<?, ExecutionContext> getVisitor(Accumulator acc) {
};
}

@Nullable
private static PlainText extractAutoConfigsFromSpringFactory(PlainText springFactory, Set<String> configs) {
private @Nullable PlainText extractAutoConfigsFromSpringFactory(PlainText springFactory, Set<String> configs) {
String contents = springFactory.getText();
int state = 0;
int index = 0;
Expand Down Expand Up @@ -216,11 +227,12 @@ private static PlainText extractAutoConfigsFromSpringFactory(PlainText springFac

if (ENABLE_AUTO_CONFIG_KEY.contentEquals(currentKey)) {
Stream.of(currentValue.toString().split(",")).map(String::trim).forEach(configs::add);
String newContent = contents.substring(0, keyIndexStart) + contents.substring(valueIndexEnd == 0 ? contents.length() : valueIndexEnd);
return newContent.isEmpty() ? null : springFactory.withText(newContent);
} else {
return springFactory;
if (Boolean.FALSE.equals(preserveFactoriesFile)) {
String newContent = contents.substring(0, keyIndexStart) + contents.substring(valueIndexEnd == 0 ? contents.length() : valueIndexEnd);
return newContent.isEmpty() ? null : springFactory.withText(newContent);
}
}
return springFactory;
}

private static int advancePastWhiteSpace(String contents, int index) {
Expand Down Expand Up @@ -267,22 +279,38 @@ private static SourceFile mergeEntries(SourceFile before, Set<String> configClas
@Value
@EqualsAndHashCode(callSuper = false)
private static class AddAutoConfigurationAnnotation extends JavaIsoVisitor<ExecutionContext> {
private static final String AUTO_CONFIGURATION_FQN = "org.springframework.boot.autoconfigure.AutoConfiguration";
private static final String CONFIGURATION_FQN = "org.springframework.context.annotation.Configuration";
private static final AnnotationMatcher CONFIGURATION_MATCHER = new AnnotationMatcher(CONFIGURATION_FQN);

Set<String> fullyQualifiedConfigClasses;

@Override
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) {
J.ClassDeclaration c = super.visitClassDeclaration(classDecl, ctx);

if (c.getType() != null && fullyQualifiedConfigClasses.contains(c.getType().getFullyQualifiedName())) {
JavaTemplate addAnnotationTemplate = JavaTemplate.builder("@AutoConfiguration")
c = maybeAddAutoconfiguration(c, ctx);
c = maybeRemoveConfiguration(c);
}
return c;
}

private J.ClassDeclaration maybeRemoveConfiguration(J.ClassDeclaration c) {
maybeRemoveImport(CONFIGURATION_FQN);
return c.withLeadingAnnotations(ListUtils.map(c.getLeadingAnnotations(), annotation ->
CONFIGURATION_MATCHER.matches(annotation) ? null : annotation));
}

private J.ClassDeclaration maybeAddAutoconfiguration(J.ClassDeclaration c, ExecutionContext ctx) {
if (FindAnnotations.find(c, AUTO_CONFIGURATION_FQN).isEmpty()) {
maybeAddImport(AUTO_CONFIGURATION_FQN);
return JavaTemplate.builder("@AutoConfiguration")
.javaParser(JavaParser.fromJavaVersion()
.classpathFromResources(ctx, "spring-boot-autoconfigure-2.7.*"))
.imports("org.springframework.boot.autoconfigure.AutoConfiguration")
.build();

doAfterVisit(new RemoveAnnotation("@org.springframework.context.annotation.Configuration").getVisitor());
c = addAnnotationTemplate.apply(getCursor(), c.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName)));
maybeAddImport("org.springframework.boot.autoconfigure.AutoConfiguration");
.imports(AUTO_CONFIGURATION_FQN)
.build()
.apply(getCursor(), c.getCoordinates().addAnnotation(comparing(J.Annotation::getSimpleName)));
}
return c;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,7 @@ private boolean isCollectedContextOrEnvironment(List<J.MethodInvocation> collect
&& SemanticallyEqual.areEqual(environmentNameToCheck, collectedEnvironmentName));
}

@Nullable
private Expression getEnvironmentNameArgument(J.MethodInvocation methodInvocation) {
private @Nullable Expression getEnvironmentNameArgument(J.MethodInvocation methodInvocation) {
if (methodInvocation.getArguments().size() < MINIMUM_ARGUMENT_COUNT_WITH_NAME) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ public static Expression resolveExpression(Expression expression, Cursor cursor)
}
}

@Nullable
private static JavaType getRootOwner(Cursor cursor) {
private static @Nullable JavaType getRootOwner(Cursor cursor) {
Cursor parent = cursor.dropParentUntil(is -> is instanceof J.MethodDeclaration || is instanceof J.ClassDeclaration || is instanceof SourceFile);
Object parentValue = parent.getValue();
if (parentValue instanceof SourceFile) {
Expand Down Expand Up @@ -83,13 +82,11 @@ private static JavaType getRootOwner(JavaType type) {
* Resolves a variable reference (by name) to the initializer expression of the corresponding declaration, provided that the
* variable is declared as `final`. In all other cases `null` will be returned.
*/
@Nullable
private static Expression resolveVariable(String name, Cursor cursor) {
private static @Nullable Expression resolveVariable(String name, Cursor cursor) {
return resolveVariable0(name, cursor.getValue(), cursor.getParentTreeCursor());
}

@Nullable
private static Expression resolveVariable0(String name, J prior, Cursor cursor) {
private static @Nullable Expression resolveVariable0(String name, J prior, Cursor cursor) {
Optional<VariableMatch> found = Optional.empty();
J value = cursor.getValue();
if (value instanceof SourceFile) {
Expand Down
Loading

0 comments on commit 97ee63a

Please sign in to comment.