From 9384f712239668bb1f36d46535b4e1da080363d3 Mon Sep 17 00:00:00 2001 From: Nieraj Singh Date: Tue, 26 Jan 2021 00:29:15 -0800 Subject: [PATCH] PT 173730396 - Further changes for project version validation --- .../boot/app/BootLanguagServerBootApp.java | 19 ++++-- .../app/BootLanguageServerInitializer.java | 56 ++++++++++++++++- .../generations/ProjectValidation.java | 34 +++++++++++ .../generations/SpringIoProjectsProvider.java | 60 +++++++++++++----- .../SpringProjectsValidations.java | 61 ++++++------------- .../ProjectGenerationsValidationTest.java | 10 +-- 6 files changed, 172 insertions(+), 68 deletions(-) create mode 100644 headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/ProjectValidation.java diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguagServerBootApp.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguagServerBootApp.java index 38caaf1c4..92aed3db1 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguagServerBootApp.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguagServerBootApp.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2018, 2020 Pivotal, Inc. + * Copyright (c) 2018, 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 @@ -15,8 +15,6 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; @@ -45,10 +43,11 @@ import org.springframework.ide.vscode.boot.metadata.ClassReferenceProvider; import org.springframework.ide.vscode.boot.metadata.LoggerNameProvider; import org.springframework.ide.vscode.boot.metadata.ProjectBasedPropertyIndexProvider; import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndex; -import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndexProvider; import org.springframework.ide.vscode.boot.metadata.ValueProviderRegistry; -import org.springframework.ide.vscode.boot.metadata.types.TypeUtilProvider; 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; @@ -72,6 +71,8 @@ import org.springframework.ide.vscode.languageserver.starter.LanguageServerAutoC import org.springframework.ide.vscode.languageserver.starter.LanguageServerRunnerAutoConf; import org.yaml.snakeyaml.Yaml; +import com.google.common.collect.ImmutableList; + import reactor.core.publisher.Hooks; @SpringBootConfiguration(proxyBeanMethods = false) @@ -215,5 +216,11 @@ public class BootLanguagServerBootApp { } }; } - + + @Bean SpringProjectsValidations springProjectsValidations(SimpleLanguageServer server) { + return new SpringProjectsValidations(server, ImmutableList.of( + new SpringIoProjectsProvider(), + new SampleProjectsProvider() + )); + } } 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 4cc146a1e..ffe42d682 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2018, 2019 Pivotal, Inc. + * Copyright (c) 2018, 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 @@ -13,6 +13,7 @@ package org.springframework.ide.vscode.boot.app; import java.util.List; import java.util.Optional; +import org.eclipse.lsp4j.MessageType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; @@ -26,7 +27,11 @@ 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; @@ -34,6 +39,7 @@ import org.springframework.ide.vscode.commons.languageserver.composable.Composit import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver; 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; @@ -59,6 +65,7 @@ public class BootLanguageServerInitializer implements InitializingBean { @Autowired BootJavaConfig config; @Autowired SpringSymbolIndex springIndexer; @Autowired(required = false) List completionEngines; + @Autowired SpringProjectsValidations springProjectsValidations; @Qualifier("adHocProperties") @Autowired ProjectBasedPropertyIndexProvider adHocProperties; @@ -120,6 +127,8 @@ public class BootLanguageServerInitializer implements InitializingBean { } }); }); + + addSpringProjectsVersionValidation(params); } public CompositeLanguageServerComponents getComponents() { @@ -132,5 +141,50 @@ public class BootLanguageServerInitializer implements InitializingBean { completionEngineAdapter.setMaxCompletions(number); } } + + 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); + } + } + }); + } } 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 new file mode 100644 index 000000000..2070e46ff --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/ProjectValidation.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * 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/SpringIoProjectsProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SpringIoProjectsProvider.java index 113435cb9..abeb01666 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SpringIoProjectsProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SpringIoProjectsProvider.java @@ -25,14 +25,14 @@ import com.google.common.collect.ImmutableMap.Builder; /** * Provides Spring project definitions from a source like "https://spring.io/api/projects" *

- * If a client is not provider that can fetch information from a source, a default client + * If a client is not provided, a default client * will be used instead that will point to "https://spring.io/api/projects" * */ public class SpringIoProjectsProvider implements SpringProjectsProvider { private final SpringProjectsClient client; - private Map cache; + private Map cache; public SpringIoProjectsProvider(SpringProjectsClient client) { this.client = client; @@ -50,25 +50,20 @@ public class SpringIoProjectsProvider implements SpringProjectsProvider { */ @Override public SpringProject getProject(String projectSlug) throws Exception { - return cache().get(projectSlug); + SpringIoProject prj = cache().get(projectSlug); + return prj != null ? prj.getProject() : null; } @Override public Generations getGenerations(String projectSlug) throws Exception { - SpringProject project = getProject(projectSlug); + SpringIoProject project = cache().get(projectSlug); if (project != null) { - Links _links = project.get_links(); - if (_links != null) { - Link genLink = _links.getGenerations(); - if (genLink != null) { - return client.getGenerations(genLink.getHref()); - } - } + return project.getGenerations(); } return null; } - private Map cache() throws Exception { + private Map cache() throws Exception { if (cache == null) { SpringProjects springProjects = client.getSpringProjects(); cache = asMap(springProjects); @@ -76,14 +71,14 @@ public class SpringIoProjectsProvider implements SpringProjectsProvider { return cache != null ? cache : ImmutableMap.of(); } - private Map asMap(SpringProjects springProjects) { - Builder builder = ImmutableMap.builder(); + private Map asMap(SpringProjects springProjects) { + Builder builder = ImmutableMap.builder(); if (springProjects != null) { List projects = springProjects.getProjects(); if (projects != null) { for (SpringProject project : projects) { - builder.put(project.getSlug(), project); + builder.put(project.getSlug(), new SpringIoProject(project, this.client)); } } } @@ -94,4 +89,39 @@ public class SpringIoProjectsProvider implements SpringProjectsProvider { String url = "https://spring.io/api/projects"; return new SpringProjectsClient(url); } + + /** + * Wrapper around the JSON SpringProject that also contains its generations + * + */ + private final static class SpringIoProject { + + private final SpringProject project; + private final SpringProjectsClient client; + private Generations generations; + + + public SpringIoProject(SpringProject project, SpringProjectsClient client) { + this.project = project; + this.client = client; + } + + public SpringProject getProject() { + return this.project; + } + + public Generations getGenerations() throws Exception { + // cache the generations to prevent frequent calls to the client + if (this.generations == null) { + Links _links = project.get_links(); + if (_links != null) { + Link genLink = _links.getGenerations(); + if (genLink != null) { + this.generations = client.getGenerations(genLink.getHref()); + } + } + } + return this.generations; + } + } } 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 index 1a6ff7c1a..877c9f492 100644 --- 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 @@ -12,26 +12,17 @@ package org.springframework.ide.vscode.boot.validation.generations; import java.io.File; import java.sql.Date; -import java.time.Duration; import java.util.List; -import java.util.concurrent.CompletableFuture; +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; -import org.springframework.ide.vscode.commons.util.AsyncRunner; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableList.Builder; - -import reactor.core.publisher.Mono; public class SpringProjectsValidations { - private static final long TIMEOUT_SECS = 30; - private final List projectsProviders; private final SimpleLanguageServer server; @@ -39,22 +30,9 @@ public class SpringProjectsValidations { this.projectsProviders = projectsProviders; this.server = server; } - - public CompletableFuture> getValidationMessagesAsync(IJavaProject jp) { - AsyncRunner async = this.server.getAsync(); - if (async != null) { - return async.invoke(Duration.ofSeconds(TIMEOUT_SECS), () -> { - return getWarningMessages(jp); - }); - } else { - return Mono.fromCallable(() -> getWarningMessages(jp)).timeout(Duration.ofSeconds(TIMEOUT_SECS)) - .toFuture(); - } - } - - public List getWarningMessages(IJavaProject jp) throws Exception { - ImmutableList.Builder messages = ImmutableList.builder(); - + + public ProjectValidation validateVersion(IJavaProject jp) throws Exception { + StringBuilder builder = new StringBuilder(); if (jp != null) { List librariesOnClasspath = SpringProjectUtil.getLibrariesOnClasspath(jp, "spring"); if (librariesOnClasspath != null) { @@ -66,7 +44,7 @@ public class SpringProjectsValidations { List gens = generations.getGenerations(); if (gens != null) { for (Generation gen : gens) { - resolveWarnings(gen, messages, versionInfo); + resolveWarnings(gen, builder, versionInfo); } } } @@ -74,32 +52,31 @@ public class SpringProjectsValidations { } } } - return messages.build(); + + return builder.length() > 0 ? + new ProjectValidation(builder.toString(), MessageType.Warning) + : ProjectValidation.OK; } - private void resolveWarnings(Generation gen, Builder messages, SpringVersionInfo versionInfo) { + 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()); - - StringBuilder msg = new StringBuilder(); - - msg.append("Using "); - msg.append(versionInfo.getSlug()); - msg.append(" version: "); - msg.append(versionInfo.getFullVersion()); + + messages.append("Using "); + messages.append(versionInfo.getSlug()); + messages.append(" version: "); + messages.append(versionInfo.getFullVersion()); if (currentDate.after(ossEndDate)) { - msg.append(" - OSS has ended on: "); - msg.append(gen.getOssSupportEndDate()); + messages.append(" - OSS has ended on: "); + messages.append(gen.getOssSupportEndDate()); } if (currentDate.after(commercialEndDate)) { - msg.append(" - Commercial support has ended on: "); - msg.append(gen.getCommercialSupportEndDate()); + messages.append(" - Commercial support has ended on: "); + messages.append(gen.getCommercialSupportEndDate()); } - - messages.add(msg.toString()); } } 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 f089c4b91..e522aafa7 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 @@ -19,6 +19,7 @@ import java.io.File; import java.sql.Date; import java.util.List; +import org.eclipse.lsp4j.MessageType; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -26,6 +27,7 @@ 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; @@ -198,11 +200,11 @@ public class ProjectGenerationsValidationTest { ImmutableList.of( new SampleProjectsProvider()) ); - List messages = validation.getWarningMessages(jp); - assertTrue(messages != null && messages.size() > 0); - String msg = messages.get(0); + 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 - OSS has ended on: 2020-01-01 - Commercial support has ended on: 2021-01-01", msg); + assertEquals("Using spring-boot version: 1.3.2 - OSS has ended on: 2020-01-01 - Commercial support has ended on: 2021-01-01", versionValidation.getMessage()); } /*