From ddeff8882e8b8c57275d42dba10f1d37a7d0f9e3 Mon Sep 17 00:00:00 2001 From: Nieraj Singh Date: Mon, 7 Nov 2022 07:42:31 -0800 Subject: [PATCH] Update Boot Version validation Re-implemented boot version validation support using spring.io Generations --- .../commons/java/SpringProjectUtil.java | 65 +++--- .../boot/app/BootLanguageServerBootApp.java | 11 - .../app/BootLanguageServerInitializer.java | 55 +---- .../vscode/boot/app/BootVersionValidator.java | 70 +++++++ .../generations/BootDiagnosticProvider.java | 56 +++++ .../generations/ProjectValidation.java | 34 --- .../SpringBootProjectValidations.java | 50 +++++ ...ionInfo.java => SpringDependencyInfo.java} | 27 +-- .../generations/SpringProjectDiagnostic.java | 34 +++ .../SpringProjectsValidations.java | 86 -------- .../UnsupportedVersionDiagnostic.java | 193 ++++++++++++++++++ .../ProjectGenerationsValidationTest.java | 128 ------------ 12 files changed, 444 insertions(+), 365 deletions(-) create mode 100644 headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootVersionValidator.java create mode 100644 headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/BootDiagnosticProvider.java delete mode 100644 headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/ProjectValidation.java create mode 100644 headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SpringBootProjectValidations.java rename headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/{SpringVersionInfo.java => SpringDependencyInfo.java} (65%) create mode 100644 headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SpringProjectDiagnostic.java delete mode 100644 headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SpringProjectsValidations.java create mode 100644 headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/UnsupportedVersionDiagnostic.java diff --git a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/SpringProjectUtil.java b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/SpringProjectUtil.java index 49f695fa6..3240dda01 100644 --- a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/SpringProjectUtil.java +++ b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/SpringProjectUtil.java @@ -23,17 +23,14 @@ import org.slf4j.LoggerFactory; public class SpringProjectUtil { public static final String SPRING_BOOT = "spring-boot"; - + + // Pattern copied from https://semver.org/ private static final String VERSION_PATTERN_STR = "(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:(-|\\.)((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?"; + private static final String GENERATION_VERSION_STR = "([0-9]+)"; public static final Logger log = LoggerFactory.getLogger(SpringProjectUtil.class); - private static final Pattern MAJOR_MINOR_VERSION = Pattern.compile("(0|[1-9]\\d*)\\.(0|[1-9]\\d*)"); - - // Pattern copied from https://semver.org/ - private static final Pattern VERSION = Pattern.compile(VERSION_PATTERN_STR); - - private static final Pattern SPRING_NAME = Pattern.compile("([a-z]+)(-[a-z]+)*"); + private static final Pattern GENERATION_VERSION = Pattern.compile(GENERATION_VERSION_STR); public static boolean isSpringProject(IJavaProject jp) { return hasSpecificLibraryOnClasspath(jp, "spring-core", true); @@ -46,40 +43,40 @@ public class SpringProjectUtil { public static boolean hasBootActuators(IJavaProject jp) { return hasSpecificLibraryOnClasspath(jp, "spring-boot-actuator-", true); } - - public static String getMajMinVersion(String name) { - Matcher matcher = MAJOR_MINOR_VERSION.matcher(name); - if (matcher.find()) { - int start = matcher.start(); - int end = matcher.end(); - return name.substring(start, end); - } - return null; - } - - public static String getVersion(String name) { - Matcher matcher = VERSION.matcher(name); - if (matcher.find()) { - int start = matcher.start(); - int end = matcher.end(); - return name.substring(start, end); - } - return null; - } /** - * - * @param libName e.g. spring-boot-3.0.0.RELEASE.jar - * @return "slug" portion: "spring-boot" + * Parses version from the given generation name (e.g. "2.1.x" + * @param name + * @return Version if valid generation name with major and minor components + * @throws Exception if invalid generation name */ - public static String getProjectSlug(String libName) { - Matcher matcher = SPRING_NAME.matcher(libName); + public static Version getVersionFromGeneration(String name) throws Exception { + Matcher matcher = GENERATION_VERSION.matcher(name); + String major = null; + String minor = null; + if (matcher.find()) { int start = matcher.start(); int end = matcher.end(); - return libName.substring(start, end); + major = name.substring(start, end); } - return null; + + if (matcher.find()) { + int start = matcher.start(); + int end = matcher.end(); + minor = name.substring(start, end); + } + + if (major != null && minor != null) { + return new Version( + Integer.parseInt(major), + Integer.parseInt(minor), + 0, + null + ); + } + + throw new IllegalArgumentException("Invalid semver. Unable to parse major and minor version from: " + name); } public static List getLibrariesOnClasspath(IJavaProject jp, String libraryNamePrefix) { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java index d53da312a..46ab316e4 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java @@ -63,9 +63,6 @@ import org.springframework.ide.vscode.boot.metadata.ProjectBasedPropertyIndexPro import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndex; import org.springframework.ide.vscode.boot.metadata.ValueProviderRegistry; import org.springframework.ide.vscode.boot.properties.completions.SpringPropertiesCompletionEngine; -import org.springframework.ide.vscode.boot.validation.generations.SampleProjectsProvider; -import org.springframework.ide.vscode.boot.validation.generations.SpringIoProjectsProvider; -import org.springframework.ide.vscode.boot.validation.generations.SpringProjectsValidations; import org.springframework.ide.vscode.boot.xml.SpringXMLCompletionEngine; import org.springframework.ide.vscode.boot.yaml.completions.ApplicationYamlAssistContext; import org.springframework.ide.vscode.boot.yaml.completions.SpringYamlCompletionEngine; @@ -93,7 +90,6 @@ import org.springframework.ide.vscode.languageserver.starter.LanguageServerRunne import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.constructor.SafeConstructor; -import com.google.common.collect.ImmutableList; import com.google.gson.Gson; import com.google.gson.JsonElement; @@ -291,13 +287,6 @@ public class BootLanguageServerBootApp { }; } - @Bean SpringProjectsValidations springProjectsValidations(SimpleLanguageServer server) { - return new SpringProjectsValidations(server, ImmutableList.of( - new SpringIoProjectsProvider(), - new SampleProjectsProvider() - )); - } - @Bean FutureProjectFinder futureProjectFinder(JavaProjectFinder projectFinder, Optional projectObserver) { return new FutureProjectFinder(projectFinder, projectObserver); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerInitializer.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerInitializer.java index 187ebba19..f432f8299 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerInitializer.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerInitializer.java @@ -18,7 +18,6 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import org.eclipse.lsp4j.MessageType; import org.eclipse.lsp4j.TextDocumentIdentifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,11 +36,8 @@ import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache; import org.springframework.ide.vscode.boot.java.utils.SymbolCache; import org.springframework.ide.vscode.boot.metadata.ProjectBasedPropertyIndexProvider; import org.springframework.ide.vscode.boot.properties.BootPropertiesLanguageServerComponents; -import org.springframework.ide.vscode.boot.validation.generations.ProjectValidation; -import org.springframework.ide.vscode.boot.validation.generations.SpringProjectsValidations; import org.springframework.ide.vscode.boot.xml.SpringXMLLanguageServerComponents; import org.springframework.ide.vscode.commons.java.IJavaProject; -import org.springframework.ide.vscode.commons.languageserver.DiagnosticService; import org.springframework.ide.vscode.commons.languageserver.completion.CompositeCompletionEngine; import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionEngine; import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCompletionEngineAdapter; @@ -51,7 +47,6 @@ import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFin import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver; import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine; import org.springframework.ide.vscode.commons.languageserver.util.HoverHandler; -import org.springframework.ide.vscode.commons.languageserver.util.ShowMessageException; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -85,7 +80,6 @@ public class BootLanguageServerInitializer implements InitializingBean { @Autowired BootJavaConfig config; @Autowired SpringSymbolIndex springIndexer; @Autowired(required = false) List completionEngines; - @Autowired SpringProjectsValidations springProjectsValidations; @Autowired private JavaProjectFinder projectFinder; @Autowired private LanguageServerProperties configProps; @Autowired(required = false) private RewriteRecipeRepository recipesRepo; @@ -180,9 +174,7 @@ public class BootLanguageServerInitializer implements InitializingBean { if (recipesRepo != null) { recipesRepo.onRecipesLoaded(v -> reconcile()); } - - addSpringProjectsVersionValidation(params); - + server.getWorkspaceService().getFileObserver().onFilesChanged(FILES_TO_WATCH_GLOB, this::handleFiles); server.getWorkspaceService().getFileObserver().onFilesCreated(FILES_TO_WATCH_GLOB, this::handleFiles); @@ -210,51 +202,6 @@ public class BootLanguageServerInitializer implements InitializingBean { } } - private void addSpringProjectsVersionValidation(BootLanguageServerParams params2) { - params.projectObserver.addListener( - new ProjectObserver.Listener() { - - @Override - public void deleted(IJavaProject project) { - // TODO Auto-generated method stub - } - - @Override - public void created(IJavaProject project) { - - // TODO: PT 173730396 - As of 4.9.1, Spring Project Version validation is not yet - // available from https://spring.io/api/projects - // Commented out to disable this feature. Uncomment to test if necessary -// validateProjectVersion(project); - } - - @Override - public void changed(IJavaProject project) { - // TODO Auto-generated method stub - } - - private void validateProjectVersion(IJavaProject project) { - try { - ProjectValidation validation = springProjectsValidations.validateVersion(project); - if (validation != null && validation.getMessageType() == MessageType.Warning) { - - // TODO: replace this with a diagnostic message that has project-scope. - // At the moment of implementing this, it doesnt look like sending a diagnostic - // message to LSP4E without a resource context is possible. - DiagnosticService diagnosticService = server.getDiagnosticService(); - if (diagnosticService != null) { - diagnosticService.diagnosticEvent( - ShowMessageException.warning(validation.getMessage(), null)); - } - log.warn(validation.getMessage()); - } - } catch (Exception e) { - log.error("Failed validating Spring Project version", e); - } - } - }); - } - private void validateProject(IJavaProject project, IReconcileEngine reconcileEngine) { if (configProps.isReconcileOnlyOpenedDocs()) { return; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootVersionValidator.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootVersionValidator.java new file mode 100644 index 000000000..6ccb189f1 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootVersionValidator.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * 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.app; + +import java.util.List; + +import org.eclipse.lsp4j.TextDocumentIdentifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ide.vscode.boot.validation.generations.SpringBootProjectValidations; +import org.springframework.ide.vscode.boot.validation.generations.SpringIoProjectsProvider; +import org.springframework.ide.vscode.boot.validation.generations.SpringProjectDiagnostic; +import org.springframework.ide.vscode.boot.validation.generations.SpringProjectsClient; +import org.springframework.ide.vscode.boot.validation.generations.SpringProjectsProvider; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver; +import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; +import org.springframework.stereotype.Component; + +@Component +public class BootVersionValidator { + + private static final Logger log = LoggerFactory.getLogger(BootVersionValidator.class); + + + public BootVersionValidator(SimpleLanguageServer server, ProjectObserver observer) { + + observer.addListener(new ProjectObserver.Listener() { + + @Override + public void deleted(IJavaProject project) { + } + + @Override + public void created(IJavaProject project) { + + String url = "https://spring.io/api/projects"; + SpringProjectsClient client = new SpringProjectsClient(url); + SpringProjectsProvider provider = new SpringIoProjectsProvider(client); + + SpringBootProjectValidations validations = new SpringBootProjectValidations(provider); + try { + List diagnostics = validations.validateBootVersion(project); + if (diagnostics != null) { + for (SpringProjectDiagnostic springProjectDiagnostic : diagnostics) { + server.getTextDocumentService().publishDiagnostics(new TextDocumentIdentifier(springProjectDiagnostic.getUri().toString()), List.of(springProjectDiagnostic.getDiagnostic())); + } + } + } catch (Exception e) { + log.error("Failed validating Spring Project version", e); + } + + } + + @Override + public void changed(IJavaProject project) { + + } + }); + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/BootDiagnosticProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/BootDiagnosticProvider.java new file mode 100644 index 000000000..b1b17da95 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/BootDiagnosticProvider.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * 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.validation.generations; + +import java.io.File; +import java.net.URI; +import java.util.List; + +import org.springframework.ide.vscode.boot.validation.generations.json.Generation; +import org.springframework.ide.vscode.boot.validation.generations.json.Generations; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.java.SpringProjectUtil; +import org.springframework.ide.vscode.commons.java.Version; + +public abstract class BootDiagnosticProvider { + + public static final String BOOT_VERSION_VALIDATION_CODE = "BOOT_VERSION_VALIDATION_CODE";; + + + /** + * + * @param javaProject + * @param info Spring Dependency Info + * @param Spring project Generation information + * @return Diagnostic if applicable to the given version, or null + */ + abstract SpringProjectDiagnostic getDiagnostic(IJavaProject javaProject, SpringDependencyInfo info, Generations generations) throws Exception; + + protected URI getBuildFileUri(IJavaProject javaProject) throws Exception { + // TODO: add gradle support here + return getPomUri(javaProject); + + } + + private URI getPomUri(IJavaProject project) throws Exception { + return new URI(project.getLocationUri().toString() + "/pom.xml"); + } + + protected File getSpringBootDependency(IJavaProject project) { + List libs = SpringProjectUtil.getLibrariesOnClasspath(project, "spring-boot"); + return libs != null && libs.size() > 0 ? libs.get(0) : null; + } + + protected Version getVersion(Generation generation) throws Exception { + return SpringProjectUtil.getVersionFromGeneration(generation.getName()); + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/ProjectValidation.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/ProjectValidation.java deleted file mode 100644 index 2070e46ff..000000000 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/ProjectValidation.java +++ /dev/null @@ -1,34 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2021 Pivotal, 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: - * Pivotal, Inc. - initial API and implementation - *******************************************************************************/ -package org.springframework.ide.vscode.boot.validation.generations; - -import org.eclipse.lsp4j.MessageType; - -public class ProjectValidation { - - public static ProjectValidation OK = new ProjectValidation("", MessageType.Info); - - private final MessageType messageType; - private final String message; - - public ProjectValidation(String message, MessageType messageType) { - this.messageType = messageType; - this.message = message; - } - - public String getMessage() { - return this.message; - } - - public MessageType getMessageType() { - return messageType; - } -} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SpringBootProjectValidations.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SpringBootProjectValidations.java new file mode 100644 index 000000000..df91eed6b --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SpringBootProjectValidations.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2020, 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.validation.generations; + +import java.util.List; + +import org.springframework.ide.vscode.boot.validation.generations.json.Generations; +import org.springframework.ide.vscode.commons.java.IJavaProject; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableList.Builder; + +public class SpringBootProjectValidations { + + private final SpringProjectsProvider projectsProvider; + private static final String SPRING_BOOT_PROJECT_SLUG = "spring-boot"; + + private final BootDiagnosticProvider[] diagnosticProviders = new BootDiagnosticProvider[] { + new UnsupportedVersionDiagnostic() + }; + + public SpringBootProjectValidations(SpringProjectsProvider projectsProvider) { + this.projectsProvider = projectsProvider; + } + + public List validateBootVersion(IJavaProject project) throws Exception { + Builder builder = ImmutableList.builder(); + if (project != null) { + SpringDependencyInfo info = new SpringDependencyInfo(project, SPRING_BOOT_PROJECT_SLUG); + Generations generations = projectsProvider.getGenerations(SPRING_BOOT_PROJECT_SLUG); + + for (BootDiagnosticProvider provider : diagnosticProviders) { + SpringProjectDiagnostic diagnostic = provider.getDiagnostic(project, info, generations); + if (diagnostic != null) { + builder.add(diagnostic); + } + } + } + return builder.build(); + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SpringVersionInfo.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SpringDependencyInfo.java similarity index 65% rename from headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SpringVersionInfo.java rename to headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SpringDependencyInfo.java index e1223631b..4101070ef 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SpringVersionInfo.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SpringDependencyInfo.java @@ -10,10 +10,9 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.validation.generations; -import java.io.File; - -import org.apache.commons.io.FilenameUtils; +import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.java.SpringProjectUtil; +import org.springframework.ide.vscode.commons.java.Version; /** * Version info for a spring dependency. @@ -22,33 +21,25 @@ import org.springframework.ide.vscode.commons.java.SpringProjectUtil; * "spring-boot", the fullVersion "2.4.0-M4", and the majMin "2.4" * */ -public class SpringVersionInfo { +public class SpringDependencyInfo { private final String slug; - private final String majMin; - private final String fullVersion; + private final Version version; /** * * @param file spring for dependency, e.g. spring-boot-2.4.0-M4.jar */ - public SpringVersionInfo(File file) { - String fileName = FilenameUtils.getBaseName(file.getName()); - this.slug = SpringProjectUtil.getProjectSlug(fileName); - this.majMin = SpringProjectUtil.getMajMinVersion(fileName); - this.fullVersion = SpringProjectUtil.getVersion(fileName); + public SpringDependencyInfo(IJavaProject project, String slug) { + this.slug = slug; + this.version = SpringProjectUtil.getDependencyVersion(project, slug); } public String getSlug() { return slug; } - public String getMajMin() { - return majMin; + public Version getVersion() { + return version; } - - public String getFullVersion() { - return fullVersion; - } - } \ No newline at end of file diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SpringProjectDiagnostic.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SpringProjectDiagnostic.java new file mode 100644 index 000000000..64ca3e908 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SpringProjectDiagnostic.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * 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.validation.generations; + +import java.net.URI; + +import org.eclipse.lsp4j.Diagnostic; + +public class SpringProjectDiagnostic { + + private final Diagnostic diagnostic; + private final URI uri; + + public SpringProjectDiagnostic(Diagnostic diagnostic, URI uri) { + this.diagnostic = diagnostic; + this.uri = uri; + } + + public Diagnostic getDiagnostic() { + return diagnostic; + } + + public URI getUri() { + return uri; + } +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SpringProjectsValidations.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SpringProjectsValidations.java deleted file mode 100644 index 877c9f492..000000000 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SpringProjectsValidations.java +++ /dev/null @@ -1,86 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2020, 2021 Pivotal, 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: - * Pivotal, Inc. - initial API and implementation - *******************************************************************************/ -package org.springframework.ide.vscode.boot.validation.generations; - -import java.io.File; -import java.sql.Date; -import java.util.List; - -import org.eclipse.lsp4j.MessageType; -import org.springframework.ide.vscode.boot.validation.generations.json.Generation; -import org.springframework.ide.vscode.boot.validation.generations.json.Generations; -import org.springframework.ide.vscode.commons.java.IJavaProject; -import org.springframework.ide.vscode.commons.java.SpringProjectUtil; -import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; - -public class SpringProjectsValidations { - - private final List projectsProviders; - private final SimpleLanguageServer server; - - public SpringProjectsValidations(SimpleLanguageServer server, List projectsProviders) { - this.projectsProviders = projectsProviders; - this.server = server; - } - - public ProjectValidation validateVersion(IJavaProject jp) throws Exception { - StringBuilder builder = new StringBuilder(); - if (jp != null) { - List librariesOnClasspath = SpringProjectUtil.getLibrariesOnClasspath(jp, "spring"); - if (librariesOnClasspath != null) { - for (File file : librariesOnClasspath) { - SpringVersionInfo versionInfo = new SpringVersionInfo(file); - for (SpringProjectsProvider projectsProvider : projectsProviders) { - Generations generations = projectsProvider.getGenerations(versionInfo.getSlug()); - if (generations != null) { - List gens = generations.getGenerations(); - if (gens != null) { - for (Generation gen : gens) { - resolveWarnings(gen, builder, versionInfo); - } - } - } - } - } - } - } - - return builder.length() > 0 ? - new ProjectValidation(builder.toString(), MessageType.Warning) - : ProjectValidation.OK; - } - - private void resolveWarnings(Generation gen, StringBuilder messages, SpringVersionInfo versionInfo) { - if (isInGeneration(versionInfo.getMajMin(), gen)) { - Date currentDate = new Date(System.currentTimeMillis()); - Date ossEndDate = Date.valueOf(gen.getOssSupportEndDate()); - Date commercialEndDate = Date.valueOf(gen.getCommercialSupportEndDate()); - - messages.append("Using "); - messages.append(versionInfo.getSlug()); - messages.append(" version: "); - messages.append(versionInfo.getFullVersion()); - - if (currentDate.after(ossEndDate)) { - messages.append(" - OSS has ended on: "); - messages.append(gen.getOssSupportEndDate()); - } - if (currentDate.after(commercialEndDate)) { - messages.append(" - Commercial support has ended on: "); - messages.append(gen.getCommercialSupportEndDate()); - } - } - } - - private boolean isInGeneration(String version, Generation g) { - return g.getName().contains(version); - } -} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/UnsupportedVersionDiagnostic.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/UnsupportedVersionDiagnostic.java new file mode 100644 index 000000000..ff63634a6 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/UnsupportedVersionDiagnostic.java @@ -0,0 +1,193 @@ +/******************************************************************************* + * 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.validation.generations; + +import java.net.URI; +import java.sql.Date; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.lsp4j.CodeAction; +import org.eclipse.lsp4j.CodeActionKind; +import org.eclipse.lsp4j.Command; +import org.eclipse.lsp4j.Diagnostic; +import org.eclipse.lsp4j.DiagnosticSeverity; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; +import org.springframework.ide.vscode.boot.validation.generations.json.Generation; +import org.springframework.ide.vscode.boot.validation.generations.json.Generations; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.java.Version; + +import com.google.common.collect.ImmutableList; + +public class UnsupportedVersionDiagnostic extends BootDiagnosticProvider { + + + @Override + public SpringProjectDiagnostic getDiagnostic(IJavaProject project, SpringDependencyInfo dependency, Generations generations) throws Exception { + + URI uri = getBuildFileUri(project); + if (uri == null) { + return null; + } + + List genList = generations.getGenerations(); + + // The generation of the current dependency + Generation dependencyGeneration = null; + + int dependencyMajor = dependency.getVersion().getMajor(); + int dependencyMinor = dependency.getVersion().getMinor(); + + // Collect the latest versions of each major version that are still supported + List latestSupportedPerMajor = new ArrayList<>(); + + if (genList != null && genList.size() > 1) { + + // The very latest version is first in the list as the generations are ordered by version + latestSupportedPerMajor.add(genList.get(0)); + + for (int i = 1; i < genList.size(); i++) { + Generation toAdd = genList.get(i); + Version toAddVersion = getVersion(toAdd); + + Generation lastAdded = latestSupportedPerMajor.get(latestSupportedPerMajor.size() - 1); + Version lastAddedVersion = getVersion(lastAdded); + + if (isCommercialValid(toAdd) && isOssValid(toAdd) && toAddVersion.getMajor() < lastAddedVersion.getMajor()) { + latestSupportedPerMajor.add(toAdd); + } + if (toAddVersion.getMajor() == dependencyMajor + && toAddVersion.getMinor() == dependencyMinor) { + dependencyGeneration = toAdd; + } + } + } + + if (dependencyGeneration == null) { + throw new Exception("Unable to find Spring Generation for: " + dependency.getVersion().toString()); + } + + StringBuffer message = new StringBuffer(); + DiagnosticSeverity severity = DiagnosticSeverity.Information; + + if (isCommercialValid(dependencyGeneration) && isOssValid(dependencyGeneration)) { + message.append("OSS support ends on: "); + message.append(dependencyGeneration.getOssSupportEndDate()); + message.append('\n'); + message.append("Commercial supports ends on: "); + message.append(dependencyGeneration.getCommercialSupportEndDate()); + } else { + + Generation toUpgrade = null; + + // Calculate latest supported version for the same major version as the dependency + // For example, if the dependency version is 1.5.0 and the latest supported is 1.8.0, + // find the generation for 1.8.0 + for (Generation generation : latestSupportedPerMajor) { + Version dependencyVersion = getVersion(dependencyGeneration); + Version latest = getVersion(generation); + if (latest.getMajor() == dependencyVersion.getMajor()) { + toUpgrade = generation; + break; + } + } + + if (toUpgrade == null) { + // if there are no supported versions in the dependency major range, upgrade to the very + // latest version + toUpgrade = latestSupportedPerMajor.get(0); + } + + if (isCommercialValid(dependencyGeneration)) { + severity = DiagnosticSeverity.Warning; + message.append("Unsupported OSS. Support ended on: "); + message.append(dependencyGeneration.getOssSupportEndDate()); + message.append('\n'); + message.append("Commercial supports ends on: "); + message.append(dependencyGeneration.getCommercialSupportEndDate()); + + } else if (isOssValid(dependencyGeneration)) { + severity = DiagnosticSeverity.Warning; + message.append("OSS support ends on: "); + message.append(dependencyGeneration.getOssSupportEndDate()); + message.append('\n'); + message.append("Unsupported Commercial. Support ended on: "); + message.append(dependencyGeneration.getCommercialSupportEndDate()); + + } else { + // OSS and Commercial support have ended + severity = DiagnosticSeverity.Error; + + message.append("Unsupported OSS. Support ended on: "); + message.append(dependencyGeneration.getOssSupportEndDate()); + message.append('\n'); + message.append("Unsupported Commercial. Support ended on: "); + message.append(dependencyGeneration.getCommercialSupportEndDate()); + } + + message.append('\n'); + message.append("Please upgrade to a newer supported version: "); + message.append(getVersion(toUpgrade).toString()); + } + + Diagnostic diagnostic = new Diagnostic(); + diagnostic.setCode(BOOT_VERSION_VALIDATION_CODE); + diagnostic.setMessage(message.toString()); + + Range range = new Range(); + Position start = new Position(); + start.setLine(0); + start.setCharacter(0); + range.setStart(start); + Position end = new Position(); + end.setLine(0); + end.setCharacter(1); + range.setEnd(end); + diagnostic.setRange(range); + diagnostic.setSeverity(severity); + + + setQuickfix(diagnostic); + + return new SpringProjectDiagnostic(diagnostic, uri); + } + + + private void setQuickfix(Diagnostic diagnostic) { + // TODO: Fix this when open rewrite recipe quickfix becomes available. + Diagnostic refDiagnostic = new Diagnostic(diagnostic.getRange(), diagnostic.getMessage(), diagnostic.getSeverity(), diagnostic.getSource()); + CodeAction ca = new CodeAction(); + ca.setKind(CodeActionKind.QuickFix); + ca.setTitle("Validation FIX"); + ca.setDiagnostics(List.of(refDiagnostic)); + String commandId = ""; + ca.setCommand(new Command("Validation FIX", commandId, ImmutableList.of())); + diagnostic.setData(ca); + } + + + private boolean isOssValid(Generation gen) { + + Date currentDate = new Date(System.currentTimeMillis()); + Date ossEndDate = Date.valueOf(gen.getOssSupportEndDate()); + return currentDate.before(ossEndDate); + } + + private boolean isCommercialValid(Generation gen) { + + Date currentDate = new Date(System.currentTimeMillis()); + Date commercialEndDate = Date.valueOf(gen.getCommercialSupportEndDate()); + + return currentDate.before(commercialEndDate); + } +} diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/validation/test/ProjectGenerationsValidationTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/validation/test/ProjectGenerationsValidationTest.java index 5e7c3cbf1..7da1e56f9 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/validation/test/ProjectGenerationsValidationTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/validation/test/ProjectGenerationsValidationTest.java @@ -15,10 +15,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.io.File; import java.util.List; -import org.eclipse.lsp4j.MessageType; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -26,25 +24,20 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Import; import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest; import org.springframework.ide.vscode.boot.bootiful.HoverTestConf; -import org.springframework.ide.vscode.boot.validation.generations.ProjectValidation; import org.springframework.ide.vscode.boot.validation.generations.SampleProjectsProvider; import org.springframework.ide.vscode.boot.validation.generations.SpringIoProjectsProvider; import org.springframework.ide.vscode.boot.validation.generations.SpringProjectsClient; import org.springframework.ide.vscode.boot.validation.generations.SpringProjectsProvider; -import org.springframework.ide.vscode.boot.validation.generations.SpringProjectsValidations; import org.springframework.ide.vscode.boot.validation.generations.json.Generation; import org.springframework.ide.vscode.boot.validation.generations.json.Generations; import org.springframework.ide.vscode.boot.validation.generations.json.Link; import org.springframework.ide.vscode.boot.validation.generations.json.SpringProject; -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.project.harness.BootLanguageServerHarness; import org.springframework.ide.vscode.project.harness.ProjectsHarness; import org.springframework.test.context.junit4.SpringRunner; -import com.google.common.collect.ImmutableList; - @RunWith(SpringRunner.class) @BootLanguageServerTest @Import(HoverTestConf.class) @@ -61,89 +54,6 @@ public class ProjectGenerationsValidationTest { harness.intialize(null); } - @Test - public void testMajMinVersionParsing() throws Exception { - String version = SpringProjectUtil.getMajMinVersion("spring-boot-starter-batch-2.3.4.RELEASE"); - assertEquals("2.3", version); - - version = SpringProjectUtil.getMajMinVersion("spring-batch-core-2.4.0-M4"); - assertEquals("2.4", version); - - version = SpringProjectUtil.getMajMinVersion("spring-boot-4.4.0-RC2"); - assertEquals("4.4", version); - - version = SpringProjectUtil.getMajMinVersion("spring-integration-70.811.0.RELEASE"); - assertEquals("70.811", version); - - version = SpringProjectUtil.getMajMinVersion("another-java-"); - assertNull(version); - - version = SpringProjectUtil.getMajMinVersion("spring-core-5.f.2"); - assertNull(version); - - version = SpringProjectUtil.getMajMinVersion("springcore.f.b"); - assertNull(version); - } - - @Test - public void testVersionParsing() throws Exception { - String version = SpringProjectUtil.getVersion("spring-boot-starter-batch-2.3.0.RELEASE"); - assertEquals("2.3.0.RELEASE", version); - - version = SpringProjectUtil.getVersion("spring-batch-core-2.4.0-M4"); - assertEquals("2.4.0-M4", version); - - version = SpringProjectUtil.getVersion("spring-batch-core-2.4.0"); - assertEquals("2.4.0", version); - - version = SpringProjectUtil.getVersion("spring-boot-4.4.0-RC2"); - assertEquals("4.4.0-RC2", version); - - version = SpringProjectUtil.getVersion("spring-integration-70.811.0.RELEASE"); - assertEquals("70.811.0.RELEASE", version); - - version = SpringProjectUtil.getVersion("another-java-"); - assertNull(version); - - version = SpringProjectUtil.getVersion("spring-core-5.f.2"); - assertNull(version); - - version = SpringProjectUtil.getVersion("springcore.f.b"); - assertNull(version); - - } - - @Test - public void testProjectSlugParsing() throws Exception { - String slug = SpringProjectUtil.getProjectSlug("spring-batch-core-2.4.0-M4"); - assertEquals("spring-batch-core", slug); - - slug = SpringProjectUtil.getProjectSlug("spring-2.4.0-M4"); - assertEquals("spring", slug); - - slug = SpringProjectUtil.getProjectSlug("-4.4.0-RC2"); - assertNull(slug); - } - - @Test - public void testVersionAndLibsFromActualProject() throws Exception { - IJavaProject jp = projects.mavenProject("empty-boot-1.3.0-app"); - assertTrue(SpringProjectUtil.isBootProject(jp)); - - List springLibs = SpringProjectUtil.getLibrariesOnClasspath(jp, "spring"); - assertNotNull(springLibs); - assertTrue(springLibs.size() > 1); - - File file = getLib(springLibs, "spring-boot"); - assertNotNull(file); - assertTrue(file.exists()); - - String version = SpringProjectUtil.getMajMinVersion(file.getName()); - assertEquals("1.3", version); - - String slug = SpringProjectUtil.getProjectSlug(file.getName()); - assertEquals("spring-boot", slug); - } @Test public void testProjectsInfoFromSpringIo() throws Exception { @@ -195,22 +105,6 @@ public class ProjectGenerationsValidationTest { assertEquals("2021-01-01", generation.getCommercialSupportEndDate()); } - @Test - public void testWarningsFromSample() throws Exception { - - IJavaProject jp = projects.mavenProject("empty-boot-1.3.0-app"); - - SpringProjectsValidations validation = new SpringProjectsValidations(harness.getServer(), - ImmutableList.of( new SampleProjectsProvider()) - ); - - ProjectValidation versionValidation = validation.validateVersion(jp); - assertNotNull(versionValidation != null); - assertEquals(versionValidation.getMessageType(), MessageType.Warning); - // Check that the message mentions the boot version of the project and the OSS support end date - assertEquals("Using spring-boot version: 1.3.2.RELEASE - OSS has ended on: 2020-01-01 - Commercial support has ended on: 2021-01-01", versionValidation.getMessage()); - } - @Test public void testDependencyVersionCalculation() throws Exception { Version version = SpringProjectUtil.getDependencyVersion("spring-boot-1.2.3.jar", "spring-boot"); @@ -240,26 +134,4 @@ public class ProjectGenerationsValidationTest { version = SpringProjectUtil.getDependencyVersion("spring-boot-actuator-1.2.3.BUILD-SNAPSHOT.jar", "spring-boot"); assertNull(version); } - - /* - * - * - * Helper methods - * - * - * - */ - - - - private File getLib(List springLibs, String slug) { - for (File file : springLibs) { - String name = file.getName(); - String libSlug = SpringProjectUtil.getProjectSlug(name); - if (slug.equals(libSlug)) { - return file; - } - } - return null; - } }