Skip to content

Commit

Permalink
Download sources for the masses
Browse files Browse the repository at this point in the history
- Fixes redhat-developer/vscode-java#1664
- Download maven-central artifact's sources on-demand

Signed-off-by: Fred Bricon <fbricon@gmail.com>
[rgrunber@redhat.com]: Fix testDynamicSourceLookups()
- Move testcase from HoverHandlerTest to InvisibleProjectBuildSupportTest
- Set up the external library after project import to mimic approach
  in other invisible projects
- Create IMavenArtifactIdentifier to be implemented by
  MavenPropertiesIdentifier and MavenCentralIdentifier
Signed-off-by: Roland Grunberg <rgrunber@redhat.com>
  • Loading branch information
fbricon authored and rgrunber committed Jan 4, 2021
1 parent a29f8b0 commit 112110b
Show file tree
Hide file tree
Showing 17 changed files with 512 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@
import org.eclipse.jdt.ls.core.internal.handlers.JDTLanguageServer;
import org.eclipse.jdt.ls.core.internal.managers.ContentProviderManager;
import org.eclipse.jdt.ls.core.internal.managers.DigestStore;
import org.eclipse.jdt.ls.core.internal.managers.ISourceDownloader;
import org.eclipse.jdt.ls.core.internal.managers.MavenSourceDownloader;
import org.eclipse.jdt.ls.core.internal.managers.ProjectsManager;
import org.eclipse.jdt.ls.core.internal.managers.StandardProjectsManager;
import org.eclipse.jdt.ls.core.internal.preferences.PreferenceManager;
Expand Down Expand Up @@ -120,6 +122,8 @@ public class JavaLanguageServerPlugin extends Plugin {
private static PrintStream out;
private static PrintStream err;

private ISourceDownloader sourceDownloader;

private LanguageServer languageServer;
private ProjectsManager projectsManager;
private DigestStore digestStore;
Expand Down Expand Up @@ -592,7 +596,6 @@ public synchronized ContextTypeRegistry getTemplateContextRegistry() {

fContextTypeRegistry = registry;
}

return fContextTypeRegistry;
}

Expand Down Expand Up @@ -627,4 +630,11 @@ public synchronized TypeFilter getTypeFilter() {
}
return typeFilter;
}

public static synchronized ISourceDownloader getDefaultSourceDownloader() {
if (pluginInstance.sourceDownloader == null) {
pluginInstance.sourceDownloader = new MavenSourceDownloader();
}
return pluginInstance.sourceDownloader;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*******************************************************************************/
package org.eclipse.jdt.ls.core.internal.managers;

import java.util.Objects;
import java.util.Set;

import org.eclipse.core.resources.IFile;
Expand All @@ -21,11 +22,14 @@
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
import org.eclipse.jdt.ls.core.internal.ProjectUtils;
import org.eclipse.jdt.ls.core.internal.managers.ProjectsManager.CHANGE_TYPE;
import org.eclipse.jdt.ls.core.internal.preferences.PreferenceManager;

import com.google.common.collect.Sets;

Expand Down Expand Up @@ -86,5 +90,18 @@ public boolean fileChanged(IResource resource, CHANGE_TYPE changeType, IProgress
return false;
}

@Override
public void discoverSource(IClassFile classFile, IProgressMonitor monitor) throws CoreException {
boolean shouldDiscoverSources = (classFile.getJavaProject() != null && Objects.equals(ProjectsManager.getDefaultProject(), classFile.getJavaProject().getProject()));

if (!shouldDiscoverSources) {
PreferenceManager preferencesManager = JavaLanguageServerPlugin.getPreferencesManager();
shouldDiscoverSources = preferencesManager != null && preferencesManager.getPreferences().isEclipseDownloadSources();
}
if (shouldDiscoverSources) {
JavaLanguageServerPlugin.getDefaultSourceDownloader().discoverSource(classFile, monitor);
}
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*******************************************************************************
* Copyright (c) 2021 Red Hat Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.ls.core.internal.managers;

import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.m2e.core.embedder.ArtifactKey;

public interface IMavenArtifactIdentifier {

public ArtifactKey identify(IPath path, IProgressMonitor monitor);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*******************************************************************************
* Copyright (c) 2020 Red Hat Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.ls.core.internal.managers;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.IClasspathEntry;

/**
* Service to automatically discover and attach sources to unknown class files.
*
* @author Fred Bricon
*
*/
public interface ISourceDownloader {

/**
* Discovers and attaches sources to the given {@link IClassFile}'s parent
* {@link IClasspathEntry}, if it's a jar file.
*
* @param classFile
* the file to identify and search sources for
* @param monitor
* a progress monitor
* @throws CoreException
*/
public void discoverSource(IClassFile classFile, IProgressMonitor monitor) throws CoreException;


}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
import org.eclipse.jdt.ls.core.internal.ProjectUtils;
Expand Down Expand Up @@ -73,4 +74,9 @@ public boolean matchPattern(IPath base, String pattern, String path) {
}
}

@Override
public void discoverSource(IClassFile classFile, IProgressMonitor monitor) throws CoreException {
JavaLanguageServerPlugin.getDefaultSourceDownloader().discoverSource(classFile, monitor);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
Expand All @@ -29,11 +28,8 @@
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
import org.eclipse.jdt.ls.core.internal.JobHelpers;
import org.eclipse.jdt.ls.core.internal.ProjectUtils;
import org.eclipse.jdt.ls.core.internal.managers.ProjectsManager.CHANGE_TYPE;
import org.eclipse.m2e.core.MavenPlugin;
Expand All @@ -44,22 +40,15 @@
import org.eclipse.m2e.core.project.IMavenProjectRegistry;
import org.eclipse.m2e.core.project.IProjectConfigurationManager;
import org.eclipse.m2e.core.project.MavenUpdateRequest;
import org.eclipse.m2e.jdt.IClasspathManager;
import org.eclipse.m2e.jdt.MavenJdtPlugin;
import org.eclipse.m2e.jdt.internal.launch.MavenRuntimeClasspathProvider;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;

/**
* @author Fred Bricon
*
*/
public class MavenBuildSupport implements IBuildSupport {

private static final int MAX_TIME_MILLIS = 3000;
private static final List<String> WATCH_FILE_PATTERNS = Collections.singletonList("**/pom.xml");
private static Cache<String, Boolean> downloadRequestsCache = CacheBuilder.newBuilder().maximumSize(100).expireAfterWrite(1, TimeUnit.HOURS).build();

private IProjectConfigurationManager configurationManager;
private ProjectRegistryManager projectManager;
Expand Down Expand Up @@ -151,36 +140,9 @@ public boolean fileChanged(IResource resource, CHANGE_TYPE changeType, IProgress
return IBuildSupport.super.fileChanged(resource, changeType, monitor) || isBuildFile(resource);
}

/* (non-Javadoc)
* @see org.eclipse.jdt.ls.core.internal.managers.IBuildSupport#getSource(org.eclipse.jdt.core.IClassFile, org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
public void discoverSource(IClassFile classFile, IProgressMonitor monitor) throws CoreException {
if (classFile == null) {
return;
}
IJavaElement element = classFile;
while (element.getParent() != null) {
element = element.getParent();
if (element instanceof IPackageFragmentRoot) {
final IPackageFragmentRoot fragment = (IPackageFragmentRoot) element;
IPath attachmentPath = fragment.getSourceAttachmentPath();
if (attachmentPath != null && !attachmentPath.isEmpty() && attachmentPath.toFile().exists()) {
break;
}
if (fragment.isArchive()) {
IPath path = fragment.getPath();
Boolean downloaded = downloadRequestsCache.getIfPresent(path.toString());
if (downloaded == null) {
downloadRequestsCache.put(path.toString(), true);
IClasspathManager buildpathManager = MavenJdtPlugin.getDefault().getBuildpathManager();
buildpathManager.scheduleDownload(fragment, true, true);
JobHelpers.waitForDownloadSourcesJobs(MAX_TIME_MILLIS);
}
break;
}
}
}
JavaLanguageServerPlugin.getDefaultSourceDownloader().discoverSource(classFile, monitor);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*******************************************************************************
* Copyright (c) 2008-2020 Red Hat Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*
*******************************************************************************/
package org.eclipse.jdt.ls.core.internal.managers;

import java.io.File;
import java.io.IOException;
import java.net.ProxySelector;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpClient.Version;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
import org.eclipse.jdt.ls.internal.gradle.checksums.HashProvider;
import org.eclipse.m2e.core.embedder.ArtifactKey;
import org.eclipse.osgi.util.NLS;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

/**
* @author Fred Bricon
*
* Partially copied from
* https://github.com/jbosstools/jbosstools-central/blob/24e907a07cbf81b9b7a9cafa69bd38b2271878eb/maven/plugins/org.jboss.tools.maven.core/src/org/jboss/tools/maven/core/internal/identification/MavenCentralIdentifier.java
*
*/
public class MavenCentralIdentifier implements IMavenArtifactIdentifier {

private static final String SHA1_SEARCH_QUERY = "https://search.maven.org/solrsearch/select?q=1:%22{0}%22&rows=1&wt=json";

private HashProvider hashProvider = new HashProvider(HashProvider.SHA1);

@Override
public ArtifactKey identify(IPath path, IProgressMonitor monitor) {
if (path == null) {
return null;
}
return identify(path.toFile(), monitor);
}

private ArtifactKey identify(File file, IProgressMonitor monitor) {
if (file == null || !file.isFile() || !file.canRead()) {
return null;
}
String sha1 = null;
try {
sha1 = hashProvider.getChecksum(file);
} catch (IOException e) {
JavaLanguageServerPlugin.logError("Failed to compute SHA1 checksum for " + file + " : " + e.getMessage());
return null;
}
return identifySha1(sha1, monitor);
}

private ArtifactKey identifySha1(String sha1, IProgressMonitor monitor) {
if (sha1 == null || sha1.isBlank()) {
return null;
}
String searchUrl = NLS.bind(SHA1_SEARCH_QUERY, sha1);
try {
return find(searchUrl, monitor);
} catch (Exception e) {
JavaLanguageServerPlugin.logError("Failed to identify " + sha1 + " with Maven Central : " + e.getMessage());
}
return null;
}

private ArtifactKey find(String searchUrl, IProgressMonitor monitor) throws IOException, InterruptedException {
Duration timeout = Duration.ofSeconds(10);
HttpClient client = HttpClient.newBuilder()
.connectTimeout(timeout)
.proxy(ProxySelector.getDefault())
.version(Version.HTTP_2)
.build();
HttpRequest httpRequest = HttpRequest.newBuilder()
.timeout(timeout)
.uri(URI.create(searchUrl))
.GET()
.build();

if (monitor == null) {
monitor = new NullProgressMonitor();
}
if (monitor.isCanceled()) {
return null;
}

//TODO implement request cancellation, according to monitor status
HttpResponse<String> response = client.send(httpRequest, HttpResponse.BodyHandlers.ofString());
JsonElement jsonElement = new JsonParser().parse(response.body());
if (jsonElement != null && jsonElement.isJsonObject()) {
return extractKey(jsonElement.getAsJsonObject());
}
return null;
}

private ArtifactKey extractKey(JsonObject modelNode) {
JsonObject response = modelNode.getAsJsonObject("response");
if (response != null) {
int num = response.get("numFound").getAsInt();
if (num > 0) {
JsonArray docs = response.getAsJsonArray("docs");
String a = null, g = null, v = null;
for (JsonElement d : docs) {
JsonObject o = d.getAsJsonObject();
a = o.get("a").getAsString();
g = o.get("g").getAsString();
v = o.get("v").getAsString();
if (a != null && g != null && v != null) {
return new ArtifactKey(g, a, v, null);
}
}
}
}
return null;
}

}
Loading

0 comments on commit 112110b

Please sign in to comment.