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

@@ -0,0 +1,103 @@
/*******************************************************************************
* 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 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"
+ " \"commercialSupportEndDate\" : \"2021-01-01\",\n" + " \"_links\" : {\n"
+ " \"self\" : {\n"
+ " \"href\" : \"https://spring.io/api/projects/spring-boot/generations/1.3.x\"\n" + " },\n"
+ " \"project\" : {\n" + " \"href\" : \"https://spring.io/api/projects/spring-boot\"\n"
+ " }\n" + " }\n" + " }, {\n" + " \"name\" : \"2.2.x\",\n"
+ " \"initialReleaseDate\" : \"2020-01-01\",\n" + " \"ossSupportEndDate\" : \"2021-01-01\",\n"
+ " \"commercialSupportEndDate\" : \"2022-01-01\",\n" + " \"_links\" : {\n"
+ " \"self\" : {\n"
+ " \"href\" : \"https://spring.io/api/projects/spring-boot/generations/2.2.x\"\n" + " },\n"
+ " \"project\" : {\n" + " \"href\" : \"https://spring.io/api/projects/spring-boot\"\n"
+ " }\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"
+ " \"status\" : \"ACTIVE\",\n" + " \"_links\" : {\n" + " \"self\" : {\n"
+ " \"href\" : \"https://spring.io/api/projects/spring-boot\"\n" + " },\n"
+ " \"releases\" : {\n"
+ " \"href\" : \"https://spring.io/api/projects/spring-boot/releases\"\n" + " },\n"
+ " \"generations\" : {\n"
+ " \"href\" : \"https://spring.io/api/projects/spring-boot/generations\"\n" + " }\n"
+ " }\n" + " }, {\n" + " \"name\" : \"Spring Data\",\n" + " \"slug\" : \"spring-data\",\n"
+ " \"repositoryUrl\" : \"https://github.com/spring-projects/spring-data\",\n"
+ " \"status\" : \"ACTIVE\",\n" + " \"_links\" : {\n" + " \"self\" : {\n"
+ " \"href\" : \"https://spring.io/api/projects/spring-data\"\n" + " },\n"
+ " \"releases\" : {\n"
+ " \"href\" : \"https://spring.io/api/projects/spring-data/releases\"\n" + " },\n"
+ " \"generations\" : {\n"
+ " \"href\" : \"https://spring.io/api/projects/spring-data/generations\"\n" + " }\n"
+ " }\n" + " }, {\n" + " \"name\" : \"Spring Data Elasticsearch\",\n"
+ " \"slug\" : \"spring-data-elasticsearch\",\n"
+ " \"repositoryUrl\" : \"https://github.com/spring-projects/spring-data-elasticsearch\",\n"
+ " \"status\" : \"ACTIVE\",\n" + " \"_links\" : {\n" + " \"self\" : {\n"
+ " \"href\" : \"https://spring.io/api/projects/spring-data-elasticsearch\"\n" + " },\n"
+ " \"releases\" : {\n"
+ " \"href\" : \"https://spring.io/api/projects/spring-data-elasticsearch/releases\"\n"
+ " },\n" + " \"generations\" : {\n"
+ " \"href\" : \"https://spring.io/api/projects/spring-data-elasticsearch/generations\"\n"
+ " },\n" + " \"parent\" : {\n"
+ " \"href\" : \"https://spring.io/api/projects/spring-data\"\n" + " }\n" + " }\n"
+ " } ]\n" + " }\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;
}
}