PT 173730396 - Various changes to spring project validation

Pushed various changes that had remained uncommitted
This commit is contained in:
Nieraj Singh
2021-01-22 16:20:12 -08:00
parent de9b2c3241
commit 909b8f4b8f
6 changed files with 243 additions and 106 deletions

View File

@@ -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<SpringProject> 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"

View File

@@ -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"
* <p/>
* 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<String, SpringProject> 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<String, SpringProject> 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<String, SpringProject> asMap(SpringProjects springProjects) {
Builder<String, SpringProject> 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);
}
}

View File

@@ -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;
}

View File

@@ -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<SpringProjectsProvider> projectsProviders;
private final SimpleLanguageServer server;
public SpringProjectsValidations(SimpleLanguageServer server, List<SpringProjectsProvider> projectsProviders) {
this.projectsProviders = projectsProviders;
this.server = server;
}
public List<String> getVersionWarnings(IJavaProject jp) throws Exception {
public CompletableFuture<List<String>> 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<String> getWarningMessages(IJavaProject jp) throws Exception {
ImmutableList.Builder<String> messages = ImmutableList.builder();
if (jp != null) {
List<File> 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<Generation> 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<String> 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);
}

View File

@@ -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;
}
}

View File

@@ -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<Generation> 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<String> messages = validation.getVersionWarnings(jp);
SpringProjectsValidations validation = new SpringProjectsValidations(harness.getServer(),
ImmutableList.of( new SampleProjectsProvider())
);
List<String> 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<File> springLibs, String slug) {
for (File file : springLibs) {