From 909b8f4b8f17f330b8b0d7019f52103dae98019e Mon Sep 17 00:00:00 2001 From: Nieraj Singh Date: Fri, 22 Jan 2021 16:20:12 -0800 Subject: [PATCH] PT 173730396 - Various changes to spring project validation Pushed various changes that had remained uncommitted --- .../generations/SampleProjectsProvider.java} | 59 +++++++++++- ...che.java => SpringIoProjectsProvider.java} | 48 +++++----- .../generations/SpringProjectsProvider.java | 28 ++++++ .../SpringProjectsValidations.java | 91 ++++++++++++------- .../generations/SpringVersionInfo.java | 53 +++++++++++ .../ProjectGenerationsValidationTest.java | 70 +++++--------- 6 files changed, 243 insertions(+), 106 deletions(-) rename headless-services/spring-boot-language-server/src/{test/java/org/springframework/ide/vscode/boot/validation/test/SpringProjectsTestSamples.java => main/java/org/springframework/ide/vscode/boot/validation/generations/SampleProjectsProvider.java} (62%) rename headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/{SpringProjectsCache.java => SpringIoProjectsProvider.java} (68%) create mode 100644 headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SpringProjectsProvider.java create mode 100644 headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SpringVersionInfo.java diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/validation/test/SpringProjectsTestSamples.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SampleProjectsProvider.java similarity index 62% rename from headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/validation/test/SpringProjectsTestSamples.java rename to headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SampleProjectsProvider.java index da945c4d1..4dd745416 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/validation/test/SpringProjectsTestSamples.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SampleProjectsProvider.java @@ -1,7 +1,59 @@ -package org.springframework.ide.vscode.boot.validation.test; +/******************************************************************************* + * Copyright (c) 2020 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.util.List; + +import org.springframework.ide.vscode.boot.validation.generations.json.Generations; +import org.springframework.ide.vscode.boot.validation.generations.json.GenerationsEmbedded; +import org.springframework.ide.vscode.boot.validation.generations.json.JsonHalParser; +import org.springframework.ide.vscode.boot.validation.generations.json.SpringProject; +import org.springframework.ide.vscode.boot.validation.generations.json.SpringProjects; +import org.springframework.ide.vscode.boot.validation.generations.json.SpringProjectsEmbedded; + + +/** + * Spring-boot sample json. Used for testing or providing a hardcoded fall-back to spring.io, when + * the latter is not available. + * + */ +public class SampleProjectsProvider implements SpringProjectsProvider { + + private SpringProjects projects; + + @Override + public SpringProject getProject(String projectSlug) throws Exception { + if (this.projects == null) { + JsonHalParser parser = new JsonHalParser(); + this.projects = parser.getEmbedded(SPRING_PROJECTS_JSON_SAMPLE, SpringProjectsEmbedded.class); + } + List projectList = this.projects.getProjects(); + for (SpringProject springProject : projectList) { + if (springProject.getSlug().equals(projectSlug)) { + return springProject; + } + } + return null; + } + + @Override + public Generations getGenerations(String projectSlug) throws Exception { + SpringProject project = getProject(projectSlug); + if (project != null && project.getSlug().equals("spring-boot")) { + JsonHalParser parser = new JsonHalParser(); + return parser.getEmbedded(SPRING_BOOT_PROJECT_GENERATIONS, GenerationsEmbedded.class); + } + return null; + } -public class SpringProjectsTestSamples { - public static final String SPRING_BOOT_PROJECT_GENERATIONS = "{\n" + " \"_embedded\" : {\n" + " \"generations\" : [ {\n" + " \"name\" : \"1.3.x\",\n" + " \"initialReleaseDate\" : \"2019-01-01\",\n" + " \"ossSupportEndDate\" : \"2020-01-01\",\n" @@ -18,7 +70,6 @@ public class SpringProjectsTestSamples { + " }\n" + " }\n" + " } ]\n" + " },\n" + " \"_links\" : {\n" + " \"project\" : {\n" + " \"href\" : \"https://spring.io/api/projects/spring-boot\"\n" + " }\n" + " }\n" + "}"; - public static final String SPRING_PROJECTS_JSON_SAMPLE = "{\n" + " \"_embedded\" : {\n" + " \"projects\" : [ {\n" + " \"name\" : \"Spring Boot\",\n" + " \"slug\" : \"spring-boot\",\n" + " \"repositoryUrl\" : \"https://github.com/spring-projects/spring-boot\",\n" diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SpringProjectsCache.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SpringIoProjectsProvider.java similarity index 68% rename from headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SpringProjectsCache.java rename to headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SpringIoProjectsProvider.java index e319abf21..113435cb9 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SpringProjectsCache.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SpringIoProjectsProvider.java @@ -10,7 +10,6 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.validation.generations; -import java.time.Duration; import java.util.List; import java.util.Map; @@ -19,22 +18,28 @@ import org.springframework.ide.vscode.boot.validation.generations.json.Link; import org.springframework.ide.vscode.boot.validation.generations.json.Links; import org.springframework.ide.vscode.boot.validation.generations.json.SpringProject; import org.springframework.ide.vscode.boot.validation.generations.json.SpringProjects; -import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; -import org.springframework.ide.vscode.commons.util.AsyncRunner; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap.Builder; -public class SpringProjectsCache { +/** + * 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 + * will be used instead that will point to "https://spring.io/api/projects" + * + */ +public class SpringIoProjectsProvider implements SpringProjectsProvider { - private static final long TIMEOUT_SECS = 30; private final SpringProjectsClient client; - private final SimpleLanguageServer server; private Map cache; - public SpringProjectsCache(SpringProjectsClient client, SimpleLanguageServer server) { + public SpringIoProjectsProvider(SpringProjectsClient client) { this.client = client; - this.server = server; + } + + public SpringIoProjectsProvider() { + this(getDefaultClient()); } /** @@ -43,11 +48,14 @@ public class SpringProjectsCache { * @return * @throws Exception */ - public SpringProject getProject(String slug) throws Exception { - return cache().get(slug); + @Override + public SpringProject getProject(String projectSlug) throws Exception { + return cache().get(projectSlug); } - public Generations getGenerations(SpringProject project) throws Exception { + @Override + public Generations getGenerations(String projectSlug) throws Exception { + SpringProject project = getProject(projectSlug); if (project != null) { Links _links = project.get_links(); if (_links != null) { @@ -62,21 +70,12 @@ public class SpringProjectsCache { private Map cache() throws Exception { if (cache == null) { - loadCache(); + SpringProjects springProjects = client.getSpringProjects(); + cache = asMap(springProjects); } return cache != null ? cache : ImmutableMap.of(); } - private void loadCache() throws Exception { - AsyncRunner async = this.server.getAsync(); - if (async != null) { - async.invoke(Duration.ofSeconds(TIMEOUT_SECS), () -> { - SpringProjects springProjects = client.getSpringProjects(); - return asMap(springProjects); - }).thenAccept((map) -> cache = map).get(); - } - } - private Map asMap(SpringProjects springProjects) { Builder builder = ImmutableMap.builder(); @@ -90,4 +89,9 @@ public class SpringProjectsCache { } return builder.build(); } + + private static SpringProjectsClient getDefaultClient() { + String url = "https://spring.io/api/projects"; + return new SpringProjectsClient(url); + } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SpringProjectsProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SpringProjectsProvider.java new file mode 100644 index 000000000..202a8f9a3 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SpringProjectsProvider.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * 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.springframework.ide.vscode.boot.validation.generations.json.Generations; +import org.springframework.ide.vscode.boot.validation.generations.json.SpringProject; + +public interface SpringProjectsProvider { + + /** + * + * @param Project slug. E.g. "spring-boot" + * @return + * @throws Exception + */ + SpringProject getProject(String projectSlug) throws Exception; + + Generations getGenerations(String projectSlug) throws Exception; + +} \ 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/SpringProjectsValidations.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SpringProjectsValidations.java index 1d0104144..1a6ff7c1a 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2020 Pivotal, Inc. + * 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 @@ -11,50 +11,62 @@ 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.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.SpringProject; 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 final SpringProjectsCache cache; + private static final long TIMEOUT_SECS = 30; - public SpringProjectsValidations(SpringProjectsCache cache) { - this.cache = cache; + private final List projectsProviders; + private final SimpleLanguageServer server; + + public SpringProjectsValidations(SimpleLanguageServer server, List projectsProviders) { + this.projectsProviders = projectsProviders; + this.server = server; } - public List getVersionWarnings(IJavaProject jp) throws Exception { + 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(); if (jp != null) { List librariesOnClasspath = SpringProjectUtil.getLibrariesOnClasspath(jp, "spring"); if (librariesOnClasspath != null) { for (File file : librariesOnClasspath) { - String fileName = file.getName(); - String slug = SpringProjectUtil.getProjectSlug(fileName); - String majMin = SpringProjectUtil.getMajMinVersion(fileName); - String fullVersion = SpringProjectUtil.getVersion(fileName); - SpringProject springProject = cache.getProject(slug); - if (springProject != null && majMin != null) { - Generations generations = cache.getGenerations(springProject); + 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) { - Generation gen = null; - for (Generation g : gens) { - if (isInGeneration(majMin, g)) { - gen = g; - break; - } - } - if (gen != null) { - messages.add(getWarning(slug, fullVersion, gen)); + for (Generation gen : gens) { + resolveWarnings(gen, messages, versionInfo); } } } @@ -65,17 +77,32 @@ public class SpringProjectsValidations { return messages.build(); } - private String getWarning(String slug, String version, Generation gen) { - StringBuilder msg = new StringBuilder(); - msg.append("Using "); - msg.append(slug); - msg.append(" version: "); - msg.append(version); - msg.append(" - OSS support end date: "); - msg.append(gen.getOssSupportEndDate()); - return msg.toString(); + private void resolveWarnings(Generation gen, Builder 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()); + + if (currentDate.after(ossEndDate)) { + msg.append(" - OSS has ended on: "); + msg.append(gen.getOssSupportEndDate()); + } + if (currentDate.after(commercialEndDate)) { + msg.append(" - Commercial support has ended on: "); + msg.append(gen.getCommercialSupportEndDate()); + } + + messages.add(msg.toString()); + } } - + 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/SpringVersionInfo.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SpringVersionInfo.java new file mode 100644 index 000000000..4477726f5 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/SpringVersionInfo.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * 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 java.io.File; + +import org.springframework.ide.vscode.commons.java.SpringProjectUtil; + +/** + * Version info for a spring dependency. + * + * For example, given a spring dependency name: "spring-boot-2.4.0-M4" , the slug is + * "spring-boot", the fullVersion "2.4.0-M4", and the majMin "2.4" + * + */ +public class SpringVersionInfo { + + private final String slug; + private final String majMin; + private final String fullVersion; + + /** + * + * @param file spring for dependency, e.g. spring-boot-2.4.0-M4.jar + */ + public SpringVersionInfo(File file) { + String fileName = file.getName(); + this.slug = SpringProjectUtil.getProjectSlug(fileName); + this.majMin = SpringProjectUtil.getMajMinVersion(fileName); + this.fullVersion = SpringProjectUtil.getVersion(fileName); + } + + public String getSlug() { + return slug; + } + + public String getMajMin() { + return majMin; + } + + public String getFullVersion() { + return fullVersion; + } + +} \ No newline at end of file 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 37d3937b5..f089c4b91 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 @@ -16,6 +16,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; +import java.sql.Date; import java.util.List; import org.junit.Before; @@ -25,23 +26,23 @@ 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.SpringProjectsCache; -import org.springframework.ide.vscode.boot.validation.generations.SpringProjectsValidations; +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.GenerationsEmbedded; -import org.springframework.ide.vscode.boot.validation.generations.json.JsonHalParser; 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.boot.validation.generations.json.SpringProjects; -import org.springframework.ide.vscode.boot.validation.generations.json.SpringProjectsEmbedded; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.java.SpringProjectUtil; 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) @@ -142,7 +143,7 @@ public class ProjectGenerationsValidationTest { public void testProjectsInfoFromSpringIo() throws Exception { String url = "https://spring.io/api/projects"; SpringProjectsClient client = new SpringProjectsClient(url); - SpringProjectsCache cache = new SpringProjectsCache(client, harness.getServer()); + SpringProjectsProvider cache = new SpringIoProjectsProvider(client); SpringProject project = cache.getProject("spring-boot"); assertNotNull(project); @@ -159,18 +160,21 @@ public class ProjectGenerationsValidationTest { generationsUrl = project.get_links().getGenerations(); assertNotNull(generationsUrl); assertEquals("https://spring.io/api/projects/spring-integration/generations", generationsUrl.getHref()); + + // Enable when generations is actually available from spring.io +// Generations generations = cache.getGenerations("spring-boot"); +// assertNotNull(generations); } @Test public void testGenerationsFromSample() throws Exception { - SpringProjectsClient client = getMockClient(); - SpringProjectsCache cache = new SpringProjectsCache(client, harness.getServer()); + SampleProjectsProvider provider = new SampleProjectsProvider(); - SpringProject project = cache.getProject("spring-boot"); + SpringProject project = provider.getProject("spring-boot"); assertNotNull(project); - Generations generations = cache.getGenerations(project); + Generations generations = provider.getGenerations("spring-boot"); assertNotNull(generations); List genList = generations.getGenerations(); @@ -188,16 +192,17 @@ public class ProjectGenerationsValidationTest { @Test public void testWarningsFromSample() throws Exception { - SpringProjectsClient client = getMockClient(); IJavaProject jp = projects.mavenProject("empty-boot-1.3.0-app"); - SpringProjectsCache cache = new SpringProjectsCache(client, harness.getServer()); - SpringProjectsValidations validation = new SpringProjectsValidations(cache); - List messages = validation.getVersionWarnings(jp); + SpringProjectsValidations validation = new SpringProjectsValidations(harness.getServer(), + ImmutableList.of( new SampleProjectsProvider()) + ); + + List messages = validation.getWarningMessages(jp); assertTrue(messages != null && messages.size() > 0); String msg = messages.get(0); // Check that the message mentions the boot version of the project and the OSS support end date - assertTrue(msg.contains("1.3.2") && msg.contains("OSS") && msg.contains("2020-01-01")); + assertEquals("Using spring-boot version: 1.3.2 - OSS has ended on: 2020-01-01 - Commercial support has ended on: 2021-01-01", msg); } /* @@ -209,38 +214,7 @@ public class ProjectGenerationsValidationTest { * */ - protected SpringProjectsClient getMockClient() throws Exception { - return new SpringProjectsClient(null) { - - @Override - public SpringProjects getSpringProjects() throws Exception { - return getProjectsFromSampleJson(); - } - - @Override - public Generations getGenerations(String generationsUrl) throws Exception { - return getGenerationsFromSampleJson(generationsUrl); - } - }; - } - - protected SpringProjects getProjectsFromSampleJson() throws Exception { - JsonHalParser parser = new JsonHalParser(); - return parser.getEmbedded(SpringProjectsTestSamples.SPRING_PROJECTS_JSON_SAMPLE, SpringProjectsEmbedded.class); - } - - protected Generations getGenerationsFromSampleJson(String genUrl) throws Exception { - String json = null; - if ("https://spring.io/api/projects/spring-boot/generations".equals(genUrl)) { - json = SpringProjectsTestSamples.SPRING_BOOT_PROJECT_GENERATIONS; - } - - if (json != null) { - JsonHalParser parser = new JsonHalParser(); - return parser.getEmbedded(json, GenerationsEmbedded.class); - } - return null; - } + private File getLib(List springLibs, String slug) { for (File file : springLibs) {