/*******************************************************************************
* Copyright (c) 2008-2010 Sonatype, 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
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Sonatype, Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.m2e.jdt.internal;
import java.io.File;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.project.MavenProject;
import org.eclipse.m2e.core.MavenPlugin;
import org.eclipse.m2e.core.embedder.ArtifactKey;
import org.eclipse.m2e.core.embedder.ICallable;
import org.eclipse.m2e.core.embedder.IMaven;
import org.eclipse.m2e.core.embedder.IMavenExecutionContext;
import org.eclipse.m2e.core.internal.jobs.IBackgroundProcessingQueue;
import org.eclipse.m2e.core.project.IMavenProjectFacade;
import org.eclipse.m2e.core.project.IMavenProjectRegistry;
import org.eclipse.m2e.jdt.MavenJdtPlugin;
/**
* DownloadSourcesJob
*
* @author igor
*/
class DownloadSourcesJob extends Job implements IBackgroundProcessingQueue {
private static Logger log = LoggerFactory.getLogger(DownloadSourcesJob.class);
private static final long SCHEDULE_INTERVAL = 1000L;
private static class DownloadRequest {
final IProject project;
final IPackageFragmentRoot fragment;
final ArtifactKey artifact;
final boolean downloadSources;
final boolean downloadJavaDoc;
public DownloadRequest(IProject project, IPackageFragmentRoot fragment, ArtifactKey artifact,
boolean downloadSources, boolean downloadJavaDoc) {
this.project = project;
this.fragment = fragment;
this.artifact = artifact;
this.downloadSources = downloadSources;
this.downloadJavaDoc = downloadJavaDoc;
}
public int hashCode() {
int hash = 17;
hash = hash * 31 + project.hashCode();
hash = hash * 31 + (fragment != null ? fragment.hashCode() : 0);
hash = hash * 31 + (artifact != null ? artifact.hashCode() : 0);
hash = hash * 31 + (downloadSources ? 1 : 0);
hash = hash * 31 + (downloadJavaDoc ? 1 : 0);
return hash;
}
public boolean equals(Object o) {
if(this == o) {
return true;
}
if(!(o instanceof DownloadRequest)) {
return false;
}
DownloadRequest other = (DownloadRequest) o;
return project.equals(other.project)
&& (fragment != null ? fragment.equals(other.fragment) : other.fragment == null)
&& (artifact != null ? artifact.equals(other.artifact) : other.artifact == null)
&& downloadSources == other.downloadSources && downloadJavaDoc == other.downloadJavaDoc;
}
}
private final IMaven maven;
private final BuildPathManager manager;
private final IMavenProjectRegistry projectManager;
private final ArrayList<DownloadRequest> queue = new ArrayList<DownloadRequest>();
public DownloadSourcesJob(BuildPathManager manager) {
super(Messages.DownloadSourcesJob_job_download);
this.manager = manager;
this.maven = MavenPlugin.getMaven();
this.projectManager = MavenPlugin.getMavenProjectRegistry();
}
public IStatus run(IProgressMonitor monitor) {
final ArrayList<DownloadRequest> downloadRequests;
synchronized(this.queue) {
downloadRequests = new ArrayList<DownloadRequest>(this.queue);
this.queue.clear();
}
try {
return maven.execute(new ICallable<IStatus>() {
public IStatus call(IMavenExecutionContext context, IProgressMonitor monitor) {
return run(downloadRequests, monitor);
}
}, monitor);
} catch(CoreException ex) {
return ex.getStatus();
}
}
IStatus run(ArrayList<DownloadRequest> downloadRequests, IProgressMonitor monitor) {
SubMonitor subMonitor = SubMonitor.convert(monitor, 3 * downloadRequests.size() + 5);
final ArrayList<IStatus> exceptions = new ArrayList<IStatus>();
final Set<IProject> mavenProjects = new LinkedHashSet<IProject>();
final Map<IPackageFragmentRoot, File[]> nonMavenProjects = new LinkedHashMap<IPackageFragmentRoot, File[]>();
for(DownloadRequest request : downloadRequests) {
SubMonitor requestMonitor = subMonitor.split(3);
try {
if(request.artifact != null) {
requestMonitor.setTaskName(getName() + ": " + request.artifact.getArtifactId());
} else if(request.project != null) {
requestMonitor.setTaskName(getName() + ": " + request.project.getName());
}
IMavenProjectFacade projectFacade = projectManager.create(request.project, requestMonitor.split(1));
if(projectFacade != null) {
downloadMaven(projectFacade, request.artifact, request.downloadSources, request.downloadJavaDoc,
requestMonitor.split(2));
mavenProjects.add(request.project);
} else if(request.artifact != null) {
List<ArtifactRepository> repositories = maven.getArtifactRepositories();
File[] files = downloadAttachments(request.artifact, repositories, request.downloadSources,
request.downloadJavaDoc, requestMonitor.split(2));
if(request.fragment == null) {
log.warn(
"IPackageFragmentRoot is missing, skipping javadoc/source attachment for project " + request.project);
} else {
nonMavenProjects.put(request.fragment, files);
}
}
} catch(CoreException ex) {
exceptions.add(ex.getStatus());
}
requestMonitor.done();
}
// consider update classpath after each individual download?
// pro: user gets sources progressively (then faster)
// con: more save operations
SubMonitor updateMonitor = SubMonitor.convert(subMonitor.split(5),
1 + mavenProjects.size() + nonMavenProjects.size());
if(!mavenProjects.isEmpty() || !nonMavenProjects.isEmpty()) {
ISchedulingRule schedulingRule = ResourcesPlugin.getWorkspace().getRuleFactory().buildRule();
getJobManager().beginRule(schedulingRule, updateMonitor.split(1));
try {
for(IProject mavenProject : mavenProjects) {
manager.updateClasspath(mavenProject, updateMonitor.split(1));
}
for(Map.Entry<IPackageFragmentRoot, File[]> entry : nonMavenProjects.entrySet()) {
File[] files = entry.getValue();
manager.attachSourcesAndJavadoc(entry.getKey(), files[0], files[1], updateMonitor.split(1));
}
} finally {
getJobManager().endRule(schedulingRule);
}
}
if(!exceptions.isEmpty()) {
IStatus[] problems = exceptions.toArray(new IStatus[exceptions.size()]);
return new MultiStatus(MavenJdtPlugin.PLUGIN_ID, -1, problems, "Could not download sources or javadoc", null);
}
return Status.OK_STATUS;
}
private void downloadMaven(IMavenProjectFacade projectFacade, ArtifactKey artifact, boolean downloadSources,
boolean downloadJavadoc, IProgressMonitor monitor) throws CoreException {
MavenProject mavenProject = projectFacade.getMavenProject(monitor);
List<ArtifactRepository> repositories = mavenProject.getRemoteArtifactRepositories();
if(artifact != null) {
downloadAttachments(artifact, repositories, downloadSources, downloadJavadoc, monitor);
} else {
for(Artifact a : mavenProject.getArtifacts()) {
ArtifactKey aKey = new ArtifactKey(a.getGroupId(), a.getArtifactId(), a.getBaseVersion(), a.getClassifier());
downloadAttachments(aKey, repositories, downloadSources, downloadJavadoc, monitor);
}
}
}
private File[] downloadAttachments(ArtifactKey artifact, List<ArtifactRepository> repositories,
boolean downloadSources, boolean downloadJavadoc, IProgressMonitor monitor) throws CoreException {
if(monitor != null && monitor.isCanceled()) {
String message = "Downloading of sources/javadocs was canceled"; //$NON-NLS-1$
log.debug(message);
synchronized(queue) {
queue.clear();
}
throw new OperationCanceledException(message);
}
ArtifactKey[] attached = manager.getAttachedSourcesAndJavadoc(artifact, repositories, downloadSources,
downloadJavadoc);
File[] files = new File[2];
if(attached[0] != null) {
try {
files[0] = download(attached[0], repositories, monitor);
log.info("Downloaded sources for " + artifact.toString());
} catch(CoreException e) {
log.error("Could not download sources for " + artifact.toString(), e); //$NON-NLS-1$
}
}
if(attached[1] != null) {
try {
files[1] = download(attached[1], repositories, monitor);
log.info("Downloaded javadoc for " + artifact.toString());
} catch(CoreException e) {
log.error("Could not download sources for " + artifact.toString(), e); //$NON-NLS-1$
}
}
return files;
}
private File download(ArtifactKey artifact, List<ArtifactRepository> repositories, IProgressMonitor monitor)
throws CoreException {
Artifact resolved = maven.resolve(artifact.getGroupId(), //
artifact.getArtifactId(), //
artifact.getVersion(), //
"jar" /*type*/, // //$NON-NLS-1$
artifact.getClassifier(), //
repositories, //
monitor);
return resolved.getFile();
}
private void scheduleDownload(IProject project, IPackageFragmentRoot fragment, ArtifactKey artifact,
boolean downloadSources, boolean downloadJavadoc) {
addDownloadRequest(project, fragment, artifact, downloadSources, downloadJavadoc);
schedule(SCHEDULE_INTERVAL);
}
public void addDownloadRequest(IProject project, IPackageFragmentRoot fragment, ArtifactKey artifact,
boolean downloadSources, boolean downloadJavadoc) {
if(project == null || !project.isAccessible()) {
return;
}
synchronized(this.queue) {
queue.add(new DownloadRequest(project, fragment, artifact, downloadSources, downloadJavadoc));
}
}
/**
* If artifact is not null, download sources and/or javadoc of this artifact. If artifact is null, download sources
* and/or javadoc of all project dependencies. Entire project classpath is updated after download. Does nothing if
* both downloadSources and downloadJavadoc are false.
*/
public void scheduleDownload(IProject project, ArtifactKey artifact, boolean downloadSources,
boolean downloadJavadoc) {
scheduleDownload(project, null, artifact, downloadSources, downloadJavadoc);
}
public void scheduleDownload(IPackageFragmentRoot fragment, ArtifactKey artifact, boolean downloadSources,
boolean downloadJavadoc) {
IProject project = fragment.getJavaProject().getProject();
scheduleDownload(project, fragment, artifact, downloadSources, downloadJavadoc);
}
public boolean isEmpty() {
synchronized(queue) {
return queue.isEmpty();
}
}
}