From 455feb3f11e83d77b5e7f3d3f235ab003e409e65 Mon Sep 17 00:00:00 2001 From: aboyko Date: Mon, 14 Nov 2022 18:19:19 -0500 Subject: [PATCH] Draft Spring Boot Upgrade command for version quick fix --- .../ide/vscode/commons/java/Version.java | 23 +++- .../ide/vscode/boot/app/RewriteConfig.java | 6 + .../java/rewrite/RewriteRecipeRepository.java | 6 +- .../boot/java/rewrite/SpringBootUpgrade.java | 109 ++++++++++++++++++ 4 files changed, 140 insertions(+), 4 deletions(-) create mode 100644 headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/SpringBootUpgrade.java diff --git a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/Version.java b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/Version.java index 484416bdc..2aeef0550 100644 --- a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/Version.java +++ b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/Version.java @@ -10,7 +10,7 @@ *******************************************************************************/ package org.springframework.ide.vscode.commons.java; -public final class Version { +public final class Version implements Comparable { private int major; private int minor; @@ -39,6 +39,14 @@ public final class Version { public String getQualifier() { return qualifier; } + + public String toMajorMinorVersionStr() { + StringBuilder sb = new StringBuilder(); + sb.append(major); + sb.append('.'); + sb.append(minor); + return sb.toString(); + } @Override public String toString() { @@ -53,6 +61,19 @@ public final class Version { sb.append(qualifier); } return sb.toString(); + } + + @Override + public int compareTo(Version o) { + if (major == o.major) { + if (minor == o.minor) { + return patch - o.patch; + } else { + return minor - o.minor; + } + } else { + return major - o.major; + } } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/RewriteConfig.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/RewriteConfig.java index 797bf85e8..0d7e8e634 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/RewriteConfig.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/RewriteConfig.java @@ -18,6 +18,8 @@ import org.springframework.ide.vscode.boot.java.rewrite.RewriteCodeActionHandler import org.springframework.ide.vscode.boot.java.rewrite.RewriteCompilationUnitCache; import org.springframework.ide.vscode.boot.java.rewrite.RewriteRecipeRepository; import org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings; +import org.springframework.ide.vscode.boot.java.rewrite.SpringBootUpgrade; +import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; @@ -35,6 +37,10 @@ public class RewriteConfig implements InitializingBean { return new RewriteCodeActionHandler(cuCache, recipeRepo, config); } + @Bean SpringBootUpgrade springBootUpgrade(SimpleLanguageServer server, RewriteRecipeRepository recipeRepo, JavaProjectFinder projectFinder) { + return new SpringBootUpgrade(server, recipeRepo, projectFinder); + } + @Override public void afterPropertiesSet() throws Exception { QuickfixRegistry registry = server.getQuickfixRegistry(); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRecipeRepository.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRecipeRepository.java index ac9687c7f..954b1c5d1 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRecipeRepository.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRecipeRepository.java @@ -99,7 +99,7 @@ public class RewriteRecipeRepository implements ApplicationContextAware { private ApplicationContext applicationContext; - private CompletableFuture loaded; + CompletableFuture loaded; private Set scanFiles; private Set scanDirs; @@ -336,10 +336,10 @@ public class RewriteRecipeRepository implements ApplicationContextAware { final String progressToken = params.getWorkDoneToken() == null || params.getWorkDoneToken().getLeft() == null ? r.getName() : params.getWorkDoneToken().getLeft(); String uri = ((JsonElement) params.getArguments().get(1)).getAsString(); return apply(r, uri, progressToken); - }); + }); } - private CompletableFuture apply(Recipe r, String uri, String progressToken) { + CompletableFuture apply(Recipe r, String uri, String progressToken) { return CompletableFuture.supplyAsync(() -> { server.getProgressService().progressBegin(progressToken, r.getDisplayName(), "Initiated..."); return projectFinder.find(new TextDocumentIdentifier(uri)); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/SpringBootUpgrade.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/SpringBootUpgrade.java new file mode 100644 index 000000000..01c83b5a0 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/SpringBootUpgrade.java @@ -0,0 +1,109 @@ +/******************************************************************************* + * Copyright (c) 2022 VMware, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VMware, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.rewrite; + +import java.util.Collections; +import java.util.Map; +import java.util.UUID; + +import org.eclipse.lsp4j.TextDocumentIdentifier; +import org.openrewrite.Recipe; +import org.openrewrite.config.DeclarativeRecipe; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.java.SpringProjectUtil; +import org.springframework.ide.vscode.commons.java.Version; +import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; +import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; +import org.springframework.ide.vscode.commons.util.Assert; + +import com.google.gson.JsonElement; + +public class SpringBootUpgrade { + + final public static String CMD_UPGRADE_SPRING_BOOT = "sts/upgrade/spring-boot"; + + private static final Map VERSION_TO_RECIPE_ID = Map.of( + "2.0", "org.openrewrite.java.spring.boot2.SpringBoot1To2Migration", + "2.1", "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_1", + "2.2", "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_2", + "2.3", "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_3", + "2.4", "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_4", + "2.5", "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_5", + "2.6", "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_6", + "2.7", "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_7", + "3.0", "org.springframework.sts.java.spring.boot3.UpgradeSpringBoot_3_0" + ); + + private RewriteRecipeRepository recipeRepo; + + public SpringBootUpgrade(SimpleLanguageServer server, RewriteRecipeRepository recipeRepo, JavaProjectFinder projectFinder) { + this.recipeRepo = recipeRepo; + + server.onCommand(CMD_UPGRADE_SPRING_BOOT, params -> { + String uri = ((JsonElement) params.getArguments().get(0)).getAsString(); + Assert.isLegal(uri != null, "Project URI parameter must not be 'null'"); + Version targetVersion = SpringProjectUtil.getVersion(((JsonElement) params.getArguments().get(1)).getAsString()); + Assert.isLegal(targetVersion != null, "Target Spring Boot version must not be 'null'"); + + IJavaProject project = projectFinder.find(new TextDocumentIdentifier(uri)).orElse(null); + Assert.isLegal(project != null, "No Spring Boot project found for uri: " + uri); + + Version version = SpringProjectUtil.getDependencyVersion(project, SpringProjectUtil.SPRING_BOOT); + + // Version upgrade is not supposed to work for patch version. Only for the major and minor versions. + + Assert.isLegal( + version.getMajor() < targetVersion.getMajor() || (version.getMajor() == targetVersion.getMajor() + && version.getMinor() < targetVersion.getMinor()), + "Cannot upgrade Spring Boot Project '" + project.getElementName() + "' because its version '" + + version.toMajorMinorVersionStr() + "' is newer than target version '" + + targetVersion.toMajorMinorVersionStr() + "'"); + + return recipeRepo.loaded.thenComposeAsync(loade -> recipeRepo.apply( + createUpgradeRecipe(version.getMajor(), version.getMinor(), targetVersion.getMajor(), targetVersion.getMinor()), + uri, + UUID.randomUUID().toString() + )); + }); + } + + private Recipe createUpgradeRecipe(int major, int minor, int targetMajor, int targetMinor) { + Recipe recipe = new DeclarativeRecipe("upgrade-spring-boot", "Upgrade Spring Boot from " + createVersionString(major, minor) + " to " + createVersionString(targetMajor, targetMinor), + "", Collections.emptySet(), null, null, false); + for (int currentMajor = major, currentMinor = minor + 1; targetMajor != currentMajor && currentMinor != targetMinor;) { + String recipeId = VERSION_TO_RECIPE_ID.get(createVersionString(currentMajor, currentMinor)); + if (recipeId == null) { + currentMajor++; + currentMinor = 0; + } else { + recipeRepo.getRecipe(recipeId).ifPresent(recipe::doNext); + currentMinor++; + } + } + if (recipe.getRecipeList().isEmpty()) { + throw new IllegalStateException("No upgrade recipes found!"); + } else if (recipe.getRecipeList().size() == 1) { + return recipe.getRecipeList().get(0); + } else { + return recipe; + } + } + + private static String createVersionString(int major, int minor) { + StringBuilder sb = new StringBuilder(); + sb.append(major); + sb.append('.'); + sb.append(minor); + return sb.toString(); + } + + +}