From 2c37251e76aceec4b4cb199ca71d8559e34b2731 Mon Sep 17 00:00:00 2001 From: "Andy Xu(devdiv)" Date: Tue, 22 Sep 2020 17:37:31 +0800 Subject: [PATCH] Handle the dependency update due to spring boot 2.3.0-RELEASE (#4589) * Handle the dependency update due to spring boot 2.3.0-RELEASE * Update maven project if it is dirty, since idea introduces a new way of Maven and Gradle importing :https://blog.jetbrains.com/idea/2020/01/intellij-idea-2020-1-eap/#maven_and_gradle_importing_updates Co-authored-by: Andy Xu(devdiv) --- .../action/AddAzureDependencyAction.java | 221 +++++++++++------- .../maven/SpringCloudDependencyManager.java | 66 +++--- 2 files changed, 174 insertions(+), 113 deletions(-) diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/src/com/microsoft/azure/springcloud/dependency/action/AddAzureDependencyAction.java b/PluginsAndFeatures/azure-toolkit-for-intellij/src/com/microsoft/azure/springcloud/dependency/action/AddAzureDependencyAction.java index a329c5a8a1..68081e6eb9 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/src/com/microsoft/azure/springcloud/dependency/action/AddAzureDependencyAction.java +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/src/com/microsoft/azure/springcloud/dependency/action/AddAzureDependencyAction.java @@ -22,6 +22,7 @@ package com.microsoft.azure.springcloud.dependency.action; +import com.google.common.util.concurrent.SettableFuture; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.actionSystem.LangDataKeys; @@ -29,6 +30,8 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.EditorKind; +import com.intellij.openapi.externalSystem.autoimport.ExternalSystemProjectTracker; +import com.intellij.openapi.externalSystem.autoimport.ProjectNotificationAware; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleTypeId; @@ -60,16 +63,15 @@ import java.io.File; import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.concurrent.ExecutionException; public class AddAzureDependencyAction extends AzureAnAction { public static final String SPRING_CLOUD_GROUP_ID = "org.springframework.cloud"; public static final String SPRING_BOOT_GROUP_ID = "org.springframework.boot"; private static final String GROUP_ID = "com.microsoft.azure"; private static final String ARTIFACT_ID = "spring-cloud-starter-azure-spring-cloud-client"; + private static final String SPRING_CLOUD_COMMONS_KEY = "org.springframework.cloud:spring-cloud-commons"; @Override public boolean onActionPerformed(@NotNull AnActionEvent event, @Nullable Operation operation) { @@ -79,87 +81,83 @@ public boolean onActionPerformed(@NotNull AnActionEvent event, @Nullable Operati final MavenProject mavenProject = projectsManager.findProject(module); if (mavenProject == null) { PluginUtil.showErrorNotificationProject(project, "Error", - String.format("Project '%s' is not a maven project.", - project.getName())); + String.format("Project '%s' is not a maven project.", + project.getName())); return true; } - DefaultLoader.getIdeHelper().runInBackground(project, "Deleting Docker Host", false, true, "Update Azure Spring Cloud dependencies", () -> { - ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator(); - progressIndicator.setText("Syncing maven project " + project.getName()); - if (projectsManager.hasScheduledProjects()) { - projectsManager.forceUpdateProjects(Collections.singletonList(mavenProject)).get(); - } - try { - progressIndicator.setText("Check existing dependencies"); - final String evaluateEffectivePom = MavenUtils.evaluateEffectivePom(project, mavenProject); - ProgressManager.checkCanceled(); - if (StringUtils.isEmpty(evaluateEffectivePom)) { - PluginUtil.showErrorNotificationProject(project, "Error", "Failed to evaluate effective pom."); - return; - } - final String springBootVer = getMavenLibraryVersion(mavenProject, SPRING_BOOT_GROUP_ID, "spring-boot-autoconfigure"); - if (StringUtils.isEmpty(springBootVer)) { - throw new AzureExecutionException(String.format("Module %s is not a spring-boot application.", module.getName())); - } - progressIndicator.setText("Get latest versions ..."); - SpringCloudDependencyManager manager = new SpringCloudDependencyManager(evaluateEffectivePom); - Map versionMaps = manager.getDependencyVersions(); - List dep = new ArrayList<>(); - dep.add(getDependencyArtifact(GROUP_ID, ARTIFACT_ID, versionMaps)); - dep.add(getDependencyArtifact(SPRING_BOOT_GROUP_ID, "spring-boot-starter-actuator", versionMaps)); - dep.add(getDependencyArtifact(SPRING_CLOUD_GROUP_ID, "spring-cloud-config-client", versionMaps)); - dep.add(getDependencyArtifact(SPRING_CLOUD_GROUP_ID, - "spring-cloud-starter-netflix-eureka-client", - versionMaps)); - dep.add(getDependencyArtifact(SPRING_CLOUD_GROUP_ID, "spring-cloud-starter-zipkin", versionMaps)); - dep.add(getDependencyArtifact(SPRING_CLOUD_GROUP_ID, "spring-cloud-starter-sleuth", versionMaps)); - ProgressManager.checkCanceled(); - List versionChanges = SpringCloudDependencyManager.getCompatibleVersions(dep, springBootVer); - if (versionChanges.isEmpty()) { - PluginUtil.showInfoNotificationProject(project, "Your project is update-to-date.", - "No updates are needed."); - return; - } - progressIndicator.setText("Applying versions ..."); - File pomFile = new File(mavenProject.getFile().getCanonicalPath()); - ProgressManager.checkCanceled(); - Map managementVersions = manager.getDependencyManagementVersions(); - versionChanges.stream().filter(change -> managementVersions.containsKey(change.getKey())).forEach(change -> { - String managementVersion = managementVersions.get(change.getKey()).getCurrentVersion(); - if (StringUtils.equals(change.getCompatibleVersion(), managementVersion) - || SpringCloudDependencyManager.isCompatibleVersion(managementVersion, springBootVer)) { - change.setCompatibleVersion(""); - change.setManagementVersion(managementVersion); + DefaultLoader.getIdeHelper().runInBackground(project, + "Update Azure Spring Cloud dependencies", + false, + true, + "Update Azure Spring Cloud dependencies", + () -> { + ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator(); + progressIndicator.setText("Syncing maven project " + project.getName()); + final SettableFuture isDirty = SettableFuture.create(); + + ApplicationManager.getApplication().invokeAndWait(() -> { + ProjectNotificationAware notificationAware = ProjectNotificationAware.getInstance(project); + isDirty.set(notificationAware.isNotificationVisible()); + if (notificationAware.isNotificationVisible()) { + ExternalSystemProjectTracker projectTracker = ExternalSystemProjectTracker.getInstance(project); + projectTracker.scheduleProjectRefresh(); } }); - if (!manager.update(pomFile, versionChanges)) { - PluginUtil.showInfoNotificationProject(project, "Your project is update-to-date.", - "No updates are needed."); + try { + if (isDirty.get().booleanValue()) { + projectsManager.forceUpdateProjects(Collections.singletonList(mavenProject)).get(); + } + } catch (InterruptedException | ExecutionException e) { + PluginUtil.showErrorNotification("Error", + "Failed to update project due to error: " + + e.getMessage()); return; } - final VirtualFile vf = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(pomFile); - RefreshQueue.getInstance().refresh(true, false, null, new VirtualFile[]{vf}); - ApplicationManager.getApplication().invokeLater(() -> { - FileEditorManager.getInstance(project).closeFile(vf); - FileEditorManager.getInstance(project).openFile(vf, true, true); - if (versionChanges.stream().anyMatch(t -> StringUtils.isNotEmpty(t.getCurrentVersion()))) { - PluginUtil.showInfoNotificationProject(project, - "Azure Spring Cloud dependencies are updated successfully.", - summaryVersionChanges(versionChanges)); + try { + progressIndicator.setText("Check existing dependencies"); + final String evaluateEffectivePom = MavenUtils.evaluateEffectivePom(project, mavenProject); + ProgressManager.checkCanceled(); + if (StringUtils.isEmpty(evaluateEffectivePom)) { + PluginUtil.showErrorNotificationProject(project, "Error", "Failed to evaluate effective pom."); + return; + } + final String springBootVer = getMavenLibraryVersion(mavenProject, SPRING_BOOT_GROUP_ID, "spring-boot-autoconfigure"); + if (StringUtils.isEmpty(springBootVer)) { + throw new AzureExecutionException(String.format("Module %s is not a spring-boot application.", module.getName())); + } + progressIndicator.setText("Get latest versions ..."); + SpringCloudDependencyManager dependencyManager = new SpringCloudDependencyManager(evaluateEffectivePom); + Map versionMaps = dependencyManager.getDependencyVersions(); + Map managerDependencyVersionsMaps = dependencyManager.getDependencyManagementVersions(); + + // given the spring-cloud-commons is greater or equal to 2.2.5.RELEASE, we should not add spring-cloud-starter-azure-spring-cloud-client + // because the code is already merged into spring repo: https://github.com/spring-cloud/spring-cloud-commons/pull/803 + boolean noAzureSpringCloudClientDependency = shouldNotAddAzureSpringCloudClientDependency(versionMaps) || + shouldNotAddAzureSpringCloudClientDependency(managerDependencyVersionsMaps); + + ProgressManager.checkCanceled(); + final List versionChanges = calculateVersionChanges(springBootVer, noAzureSpringCloudClientDependency, versionMaps); + if (versionChanges.isEmpty()) { + PluginUtil.showInfoNotificationProject(project, "Your project is update-to-date.", + "No updates are needed."); + return; + } + progressIndicator.setText("Applying versions ..."); + final File pomFile = new File(mavenProject.getFile().getCanonicalPath()); + ProgressManager.checkCanceled(); + if (applyVersionChanges(dependencyManager, pomFile, springBootVer, managerDependencyVersionsMaps, versionChanges)) { + noticeUserVersionChanges(project, pomFile, versionChanges); } else { - PluginUtil.showInfoNotificationProject(project, - "Azure Spring Cloud dependencies are added to your project successfully.", - summaryVersionChanges(versionChanges)); + PluginUtil.showInfoNotificationProject(project, "Your project is update-to-date.", "No updates are needed."); } - }); - } catch (DocumentException | IOException | AzureExecutionException | MavenProcessCanceledException e) { - PluginUtil.showErrorNotification("Error", - "Failed to update Azure Spring Cloud dependencies due to error: " - + e.getMessage()); - } - }); + } catch (DocumentException | IOException | AzureExecutionException | MavenProcessCanceledException e) { + PluginUtil.showErrorNotification("Error", + "Failed to update Azure Spring Cloud dependencies due to error: " + + e.getMessage()); + } + }); return false; } @@ -207,12 +205,12 @@ private static String summaryVersionChanges(List changes) { for (DependencyArtifact change : changes) { boolean isUpdate = StringUtils.isNotEmpty(change.getCurrentVersion()); builder.append(String.format("%s dependency: Group: %s, Artifact: %s, Version: %s%s \n", - isUpdate ? "Update" : "Add ", - change.getGroupId(), - change.getArtifactId(), - isUpdate ? (change.getCurrentVersion() + " -> ") : "", - StringUtils.isNotEmpty(change.getCompatibleVersion()) ? change.getCompatibleVersion() : - change.getManagementVersion())); + isUpdate ? "Update" : "Add ", + change.getGroupId(), + change.getArtifactId(), + isUpdate ? (change.getCurrentVersion() + " -> ") : "", + StringUtils.isNotEmpty(change.getCompatibleVersion()) ? change.getCompatibleVersion() : + change.getManagementVersion())); } return builder.toString(); } @@ -230,4 +228,65 @@ private static String getMavenLibraryVersion(MavenProject project, String groupI private static boolean isMatch(MavenArtifact lib, String groupId, String artifactId) { return StringUtils.equals(lib.getArtifactId(), artifactId) && StringUtils.equals(lib.getGroupId(), groupId); } + + private static boolean shouldNotAddAzureSpringCloudClientDependency(Map versionMaps) { + if (versionMaps.containsKey(SPRING_CLOUD_COMMONS_KEY)) { + String version = versionMaps.get(SPRING_CLOUD_COMMONS_KEY).getCurrentVersion(); + return SpringCloudDependencyManager.isGreaterOrEqualVersion(version, "2.2.5.RELEASE"); + } + return false; + } + + private static List calculateVersionChanges(String springBootVer, + boolean noAzureSpringCloudClientDependency, + Map versionMaps) + throws AzureExecutionException, DocumentException, IOException { + List dep = new ArrayList<>(); + if (!noAzureSpringCloudClientDependency) { + dep.add(getDependencyArtifact(GROUP_ID, ARTIFACT_ID, versionMaps)); + } + dep.add(getDependencyArtifact(SPRING_BOOT_GROUP_ID, "spring-boot-starter-actuator", versionMaps)); + dep.add(getDependencyArtifact(SPRING_CLOUD_GROUP_ID, "spring-cloud-config-client", versionMaps)); + dep.add(getDependencyArtifact(SPRING_CLOUD_GROUP_ID, + "spring-cloud-starter-netflix-eureka-client", + versionMaps)); + dep.add(getDependencyArtifact(SPRING_CLOUD_GROUP_ID, "spring-cloud-starter-zipkin", versionMaps)); + dep.add(getDependencyArtifact(SPRING_CLOUD_GROUP_ID, "spring-cloud-starter-sleuth", versionMaps)); + + return SpringCloudDependencyManager.getCompatibleVersions(dep, springBootVer); + } + + private static boolean applyVersionChanges(SpringCloudDependencyManager dependencyManager, + File pomFile, + String springBootVer, + Map managerDependencyVersionsMaps, + List versionChanges) throws IOException, DocumentException { + versionChanges.stream().filter(change -> managerDependencyVersionsMaps.containsKey(change.getKey())).forEach(change -> { + String managementVersion = managerDependencyVersionsMaps.get(change.getKey()).getCurrentVersion(); + if (StringUtils.equals(change.getCompatibleVersion(), managementVersion) + || SpringCloudDependencyManager.isCompatibleVersion(managementVersion, springBootVer)) { + change.setCompatibleVersion(""); + change.setManagementVersion(managementVersion); + } + }); + return dependencyManager.update(pomFile, versionChanges); + } + + private static void noticeUserVersionChanges(Project project, File pomFile, List versionChanges) { + final VirtualFile vf = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(pomFile); + RefreshQueue.getInstance().refresh(true, false, null, new VirtualFile[]{vf}); + ApplicationManager.getApplication().invokeLater(() -> { + FileEditorManager.getInstance(project).closeFile(vf); + FileEditorManager.getInstance(project).openFile(vf, true, true); + if (versionChanges.stream().anyMatch(t -> StringUtils.isNotEmpty(t.getCurrentVersion()))) { + PluginUtil.showInfoNotificationProject(project, + "Azure Spring Cloud dependencies are updated successfully.", + summaryVersionChanges(versionChanges)); + } else { + PluginUtil.showInfoNotificationProject(project, + "Azure Spring Cloud dependencies are added to your project successfully.", + summaryVersionChanges(versionChanges)); + } + }); + } } diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/src/com/microsoft/intellij/maven/SpringCloudDependencyManager.java b/PluginsAndFeatures/azure-toolkit-for-intellij/src/com/microsoft/intellij/maven/SpringCloudDependencyManager.java index c63f1241db..7a74929918 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/src/com/microsoft/intellij/maven/SpringCloudDependencyManager.java +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/src/com/microsoft/intellij/maven/SpringCloudDependencyManager.java @@ -27,6 +27,7 @@ import org.apache.commons.collections4.IterableUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.DocumentFactory; @@ -40,13 +41,11 @@ import java.io.InputStreamReader; import java.net.URL; import java.net.URLConnection; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; public class SpringCloudDependencyManager { public static final String POM_NAMESPACE = "http://maven.apache.org/POM/4.0.0"; + private static final String LATEST_SPRING_BOOT_RELEASE = "2.3.0.RELEASE"; private Document doc; public SpringCloudDependencyManager(String effectivePomXml) throws DocumentException { @@ -84,51 +83,53 @@ public boolean update(File file, List des) throws IOExceptio return new PomXmlUpdater().updateDependencies(file, des); } - public static List getCompatibleVersions(List dependencies, String springBootVer) + public static List getCompatibleVersions(List dependencies, String springBootVersionStr) throws AzureExecutionException, IOException, DocumentException { List res = new ArrayList<>(); + DefaultArtifactVersion springBootVersion = new DefaultArtifactVersion(springBootVersionStr); for (DependencyArtifact dependency : dependencies) { - if (StringUtils.isNotEmpty(dependency.getCurrentVersion()) && isCompatibleVersion(dependency.getCurrentVersion(), springBootVer)) { + if (StringUtils.isNotEmpty(dependency.getCurrentVersion()) && isCompatibleVersion(dependency.getCurrentVersion(), springBootVersionStr)) { continue; } List latestVersions = getMavenCentralVersions(dependency.getGroupId(), dependency.getArtifactId()); - String targetVer = ""; - if (springBootVer.startsWith("2.2.")) { - targetVer = getCompatibleVersionWithBootVersion(latestVersions, "2.2."); - } else if (springBootVer.startsWith("2.1.")) { - targetVer = getCompatibleVersionWithBootVersion(latestVersions, "2.1."); - } else { - throw new AzureExecutionException("Unsupported spring-boot version: " + springBootVer); - } - if (StringUtils.isEmpty(targetVer)) { - throw new AzureExecutionException(String.format("Cannot get compatible version for %s:%s with Spring Boot with version %s", - dependency.getGroupId(), dependency.getArtifactId(), springBootVer)); + String targetVersionText = getCompatibleVersionWithBootVersion(latestVersions, springBootVersion); + if (StringUtils.isEmpty(targetVersionText)) { + if (isGreaterOrEqualVersion(springBootVersionStr, LATEST_SPRING_BOOT_RELEASE) && !latestVersions.isEmpty()) { + // to handle the ege case: spring-cloud-starter-config 2.2.5.RELEASE supports spring boot 2.3.0 + // here for newest spring boot versions, use the latest versions + targetVersionText = latestVersions.get(latestVersions.size() - 1); + } else { + throw new AzureExecutionException(String.format("Cannot get compatible version for %s:%s with Spring Boot with version %s", + dependency.getGroupId(), dependency.getArtifactId(), springBootVersionStr)); + } + } - dependency.setCompatibleVersion(targetVer); - if (!StringUtils.equals(dependency.getCurrentVersion(), targetVer)) { + dependency.setCompatibleVersion(targetVersionText); + if (!StringUtils.equals(dependency.getCurrentVersion(), targetVersionText)) { res.add(dependency); } } return res; } - public static boolean isCompatibleVersion(String version, String springBootVer) { - if (StringUtils.isNotEmpty(springBootVer)) { - if (springBootVer.startsWith("2.2.")) { - return isCompatibleVersionWithBootVersion(version, "2.2."); - } else if (springBootVer.startsWith("2.1.")) { - return isCompatibleVersionWithBootVersion(version, "2.1."); - } - } - return false; + public static boolean isCompatibleVersion(String versionStr, String springBootVersionStr) { + return isCompatibleVersion(versionStr, new DefaultArtifactVersion(springBootVersionStr)); + } + + public static boolean isGreaterOrEqualVersion(String versionStr1, String versionStr2) { + DefaultArtifactVersion version1 = new DefaultArtifactVersion(versionStr1); + DefaultArtifactVersion version2 = new DefaultArtifactVersion(versionStr2); + return version1.compareTo(version2) >= 0; } - private static String getCompatibleVersionWithBootVersion(List latestVersions, String bootVersionPrefix) { - return IterableUtils.find(IterableUtils.reversedIterable(latestVersions), version -> isCompatibleVersionWithBootVersion(version, bootVersionPrefix)); + private static boolean isCompatibleVersion(String versionStr, DefaultArtifactVersion springBootVersion) { + DefaultArtifactVersion version = new DefaultArtifactVersion(versionStr); + return springBootVersion.getMajorVersion() == version.getMajorVersion() + && springBootVersion.getMinorVersion() == version.getMinorVersion(); } - private static boolean isCompatibleVersionWithBootVersion(String version, String bootVersionPrefix) { - return version != null && version.startsWith(bootVersionPrefix); + private static String getCompatibleVersionWithBootVersion(List latestVersions, DefaultArtifactVersion springBootVersion) { + return IterableUtils.find(IterableUtils.reversedIterable(latestVersions), version -> isCompatibleVersion(version, springBootVersion)); } private static void collectDependencyVersionsFromNodes(List nodes, Map versionMap) { @@ -160,4 +161,5 @@ private static List getMavenCentralVersions(String groupId, String artif } return res; } + }