blob: ba6bfc83db53cda4c11d9883ad079a295dd85bd7 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.ivyde.internal.eclipse.resolve;
import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.ivy.Ivy;
import org.apache.ivy.core.IvyContext;
import org.apache.ivy.core.IvyPatternHelper;
import org.apache.ivy.core.module.descriptor.Artifact;
import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
import org.apache.ivy.core.module.id.ModuleRevisionId;
import org.apache.ivy.core.report.ArtifactDownloadReport;
import org.apache.ivy.core.report.ResolveReport;
import org.apache.ivy.core.resolve.IvyNode;
import org.apache.ivy.core.resolve.ResolveData;
import org.apache.ivy.core.resolve.ResolveOptions;
import org.apache.ivy.core.resolve.ResolvedModuleRevision;
import org.apache.ivy.core.retrieve.RetrieveOptions;
import org.apache.ivy.plugins.report.XmlReportParser;
import org.apache.ivy.plugins.resolver.DependencyResolver;
import org.apache.ivy.util.filter.ArtifactTypeFilter;
import org.apache.ivyde.internal.eclipse.IvyDEMessage;
import org.apache.ivyde.internal.eclipse.IvyPlugin;
import org.apache.ivyde.internal.eclipse.cpcontainer.IvyClasspathUtil;
import org.apache.ivyde.internal.eclipse.workspaceresolver.WorkspaceResolver;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
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.Status;
import org.eclipse.core.variables.IStringVariableManager;
import org.eclipse.core.variables.VariablesPlugin;
/**
* Eclipse classpath container that will contain the ivy resolved entries.
*/
public class IvyResolver {
private boolean usePreviousResolveIfExist = false;
private String[] confs;
private final IProject project;
private final List<String> confInput;
private String retrievePattern = null;
private boolean retrieveSync = true;
private String retrieveTypes = null;
private final String ivyXmlPath;
private boolean useCacheOnly = IvyPlugin.getPreferenceStoreHelper().isOffline();
private boolean useExtendedResolveId = false;
private boolean transitiveResolve = true;
public IvyResolver(String ivyXmlPath, List<String> confInput, IProject project) {
this.ivyXmlPath = ivyXmlPath;
this.confInput = confInput;
this.project = project;
}
public void setUsePreviousResolveIfExist(boolean usePreviousResolveIfExist) {
this.usePreviousResolveIfExist = usePreviousResolveIfExist;
}
public void setRetrievePattern(String retrievePattern) {
this.retrievePattern = retrievePattern;
}
public void setRetrieveSync(boolean retrieveSync) {
this.retrieveSync = retrieveSync;
}
public void setRetrieveTypes(String retrieveTypes) {
this.retrieveTypes = retrieveTypes;
}
public void setUseCacheOnly(boolean useCacheOnly) {
this.useCacheOnly = useCacheOnly;
}
public void setTransitiveResolve(boolean transitiveResolve) {
this.transitiveResolve = transitiveResolve;
}
public String getIvyXmlPath() {
return ivyXmlPath;
}
public IProject getProject() {
return project;
}
public IStatus resolve(Ivy ivy, ModuleDescriptor md, IProgressMonitor monitor, int step) {
computeConfs(confInput, md);
try {
ivy.pushContext();
IvyDEMessage.info("Resolving " + toString());
IvyResolveJobListener ivyResolveJobListener = new IvyResolveJobListener(monitor, step);
ivy.getEventManager().addIvyListener(ivyResolveJobListener);
monitor.setTaskName("Resolve of " + toString());
ResolveResult result = new ResolveResult();
// context Classloader hook for commons logging used by httpclient
// It will also be used by the SaxParserFactory in Ivy
ClassLoader old = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(IvyResolver.class.getClassLoader());
try {
if (usePreviousResolveIfExist) {
result = resolveWithPrevious(ivy, md);
} else {
result = doResolve(ivy, md);
}
if (result.getProblemMessages().isEmpty()) {
// only continue if we resolved correctly
IStatus retrieveStatus = maybeRetrieve(ivy, md, result, monitor);
if (!retrieveStatus.isOK()) {
return retrieveStatus;
}
postResolveOrRefresh(ivy, md, result, monitor);
}
} catch (ParseException e) {
String errorMsg = "Error while parsing the ivy file from " + this.toString() + "\n"
+ e.getMessage();
IvyDEMessage.error(errorMsg);
return new Status(IStatus.ERROR, IvyPlugin.ID, IStatus.ERROR, errorMsg, e);
} catch (Exception e) {
String errorMsg = "Error while resolving dependencies for " + this.toString()
+ "\n" + e.getMessage();
IvyDEMessage.error(errorMsg);
return new Status(IStatus.ERROR, IvyPlugin.ID, IStatus.ERROR, errorMsg, e);
} finally {
Thread.currentThread().setContextClassLoader(old);
ivy.getEventManager().removeIvyListener(ivyResolveJobListener);
}
if (!result.getProblemMessages().isEmpty()) {
MultiStatus multiStatus = new MultiStatus(IvyPlugin.ID, IStatus.ERROR,
"Impossible to resolve dependencies of " + md.getModuleRevisionId(), null);
for (String s : result.getProblemMessages()) {
multiStatus.add(new Status(IStatus.ERROR, IvyPlugin.ID, IStatus.ERROR,
s, null));
}
return multiStatus;
}
return Status.OK_STATUS;
} catch (Throwable e) {
return new Status(IStatus.ERROR, IvyPlugin.ID, IStatus.ERROR, "The resolve job of "
+ toString() + " has unexpectedly stopped", e);
} finally {
ivy.popContext();
}
}
protected void postResolveOrRefresh(Ivy ivy, ModuleDescriptor md, ResolveResult resolveResult,
IProgressMonitor monitor) {
// nothing to do by default
}
private void computeConfs(List<String> confInput, ModuleDescriptor md) {
Set<String> configurations = new HashSet<>(confInput);
if (configurations.contains("*")) {
confs = md.getConfigurationsNames();
} else {
confs = configurations.toArray(new String[configurations.size()]);
}
}
private ResolveResult resolveWithPrevious(Ivy ivy, ModuleDescriptor md) throws ParseException,
IOException {
IvyDEMessage.verbose("Trying to read previous resolve report");
ResolveResult result = new ResolveResult();
// we check if all required configurations have been resolved
for (String conf : confs) {
IvyDEMessage.verbose("Fetching the resolve report for configuration " + conf);
File report = ivy.getResolutionCacheManager().getConfigurationResolveReportInCache(
IvyClasspathUtil.buildResolveId(useExtendedResolveId, md), conf);
IvyDEMessage.debug("Checking resolve report at " + report);
if (!report.exists()) {
IvyDEMessage.info("The resolve report for the configuration " + conf
+ " was not found. Falling back by doing a resolve again.");
return doResolve(ivy, md);
}
IvyDEMessage.verbose("Resolve report found, parsing it");
// found a report, try to parse it.
try {
XmlReportParser parser = new XmlReportParser();
parser.parse(report);
result.addArtifactReports(parser.getArtifactReports());
findAllArtifactOnRefresh(ivy, parser, result);
} catch (ParseException e) {
IvyDEMessage.info("Error while parsing the report " + report
+ ". Falling back by doing a resolve again.");
return doResolve(ivy, md);
}
}
return result;
}
private ResolveResult doResolve(Ivy ivy, ModuleDescriptor md) throws ParseException,
IOException {
IvyDEMessage.debug("Doing a full resolve...");
ResolveOptions resolveOption = new ResolveOptions();
resolveOption.setConfs(confs);
resolveOption.setValidate(ivy.getSettings().doValidate());
resolveOption.setUseCacheOnly(useCacheOnly);
resolveOption.setResolveId(IvyClasspathUtil.buildResolveId(useExtendedResolveId, md));
resolveOption.setTransitive(transitiveResolve);
ResolveReport report = ivy.getResolveEngine().resolve(md, resolveOption);
if (report.hasError()) {
IvyDEMessage.verbose("Resolve ended with errors");
} else {
IvyDEMessage.verbose("Resolve successful");
}
ResolveResult result = new ResolveResult(report);
ArtifactDownloadReport[] artifactReports = report.getArtifactsReports(null, false);
Map<Artifact, ArtifactDownloadReport> workspaceArtifacts = IvyContext
.getContext().get(WorkspaceResolver.IVYDE_WORKSPACE_ARTIFACT_REPORTS);
if (workspaceArtifacts != null) {
// some artifact were 'forced' by the dependency declaration, whereas they should be
// switch by the eclipse project reference
for (int i = 0; i < artifactReports.length; i++) {
ArtifactDownloadReport eclipseArtifactReport = (ArtifactDownloadReport) workspaceArtifacts
.get(artifactReports[i].getArtifact());
if (eclipseArtifactReport != null) {
// let's switch.
artifactReports[i] = eclipseArtifactReport;
}
}
}
result.addArtifactReports(artifactReports);
collectArtifactsByDependency(report, result);
return result;
}
/**
* Populate the map of artifact. The map should be populated by metadata in cache as this is
* called in the refresh process.
*
* @param ivy Ivy
* @param parser XmlReportParser
* @param result ResolveResult
* @throws ParseException if parser fails
*/
private void findAllArtifactOnRefresh(Ivy ivy, XmlReportParser parser, ResolveResult result)
throws ParseException {
ModuleRevisionId[] dependencyMrids = parser.getDependencyRevisionIds();
IvyDEMessage.verbose("Resolve report parsed. Fetching artifacts of "
+ dependencyMrids.length + " dependencie(s)");
for (ModuleRevisionId dependencyMrid : dependencyMrids) {
DependencyResolver depResolver = ivy.getSettings().getResolver(dependencyMrid);
DefaultDependencyDescriptor depDescriptor = new DefaultDependencyDescriptor(
dependencyMrid, false);
ResolveOptions options = new ResolveOptions();
options.setRefresh(true);
options.setUseCacheOnly(true);
IvyDEMessage.debug("Fetching dependency " + dependencyMrid);
ResolvedModuleRevision dependency = depResolver.getDependency(depDescriptor,
new ResolveData(ivy.getResolveEngine(), options));
if (dependency != null) {
Artifact[] artifacts = dependency.getDescriptor().getAllArtifacts();
IvyDEMessage.debug("Dependency " + dependencyMrid + " found: "
+ artifacts.length + " artifact(s) found");
result.putArtifactsForDep(dependencyMrid, artifacts);
} else {
IvyDEMessage.debug("Dependency " + dependencyMrid + " not found");
}
}
}
private void collectArtifactsByDependency(ResolveReport rr, ResolveResult result) {
for (IvyNode node : rr.getDependencies()) {
if (node.getDescriptor() != null) {
result.putArtifactsForDep(node.getResolvedId(), node.getDescriptor()
.getAllArtifacts());
}
}
}
private IStatus maybeRetrieve(Ivy ivy, ModuleDescriptor md, ResolveResult result,
IProgressMonitor monitor) throws IOException {
if (retrievePattern == null || project == null) {
IvyDEMessage.debug("No file retrieving configured");
return Status.OK_STATUS;
}
// Perform variable substitution on the pattern.
IStringVariableManager varManager = VariablesPlugin.getDefault().getStringVariableManager();
String pattern;
try {
pattern = varManager.performStringSubstitution(retrievePattern, false);
} catch (CoreException e) {
return new Status(IStatus.ERROR, IvyPlugin.ID, IStatus.ERROR,
"Incorrect use of variables in retrievePattern '" + retrievePattern + "'."
+ e.getMessage(), e);
}
// For backwards compatibility we prepend the project location to the pattern,
// but we do it only in case the pattern does not start with a variable (i.e. ${xxx )
if (!retrievePattern.startsWith("${")) {
pattern = project.getLocation().toPortableString() + "/" + pattern;
}
IvyDEMessage.info("Retrieving files into " + pattern);
monitor.setTaskName("retrieving dependencies in " + pattern);
RetrieveOptions options = new RetrieveOptions();
options.setSync(retrieveSync);
if (!result.isPreviousUsed()) {
options.setResolveId(result.getReport().getResolveId());
}
options.setConfs(confs).setDestArtifactPattern(pattern);
if (retrieveTypes != null && !retrieveTypes.equals("*")) {
List<String> typeList = IvyClasspathUtil.split(retrieveTypes);
options.setArtifactFilter(new ArtifactTypeFilter(typeList));
}
options.setResolveId(IvyClasspathUtil.buildResolveId(useExtendedResolveId, md));
String refreshPath = IvyPatternHelper.getTokenRoot(retrievePattern);
if (retrieveSync && refreshPath.length() == 0) {
// the root folder of the retrieve pattern is the the project itself
// so let's prevent from deleting the entire project
return new Status(IStatus.ERROR, IvyPlugin.ID, IStatus.ERROR,
"The root of the retrieve pattern is the root folder of the project."
+ " Your project would have then been entirely deleted."
+ " Change your retrieve pattern to have a sub folder.", null);
}
// Actually do the retrieve
// FIXME here we will parse a report we already have
// with a better Java API, we could do probably better
int numberOfItemsRetrieved = ivy.retrieve(md.getModuleRevisionId(), options).getNbrArtifactsCopied();
IvyDEMessage.info(numberOfItemsRetrieved + " retrieved file(s)");
if (numberOfItemsRetrieved > 0) {
// Only refresh if we actually retrieved a file.
IResource retrieveFolder;
if (refreshPath.length() == 0) {
retrieveFolder = project;
IvyDEMessage.verbose("Refreshing Eclipse project " + project.getName());
} else {
retrieveFolder = project.getFolder(refreshPath);
IvyDEMessage.verbose("Refreshing Eclipse folder " + retrieveFolder);
}
RefreshFolderJob refreshFolderJob = new RefreshFolderJob(retrieveFolder);
refreshFolderJob.schedule();
}
// recompute the files which has been copied to build a classpath
String resolvedPattern = IvyPatternHelper.substituteVariables(pattern, ivy.getSettings()
.getVariables());
try {
// FIXME same as above
Map<ArtifactDownloadReport, Set<String>> retrievedArtifacts = ivy.getRetrieveEngine().determineArtifactsToCopy(
md.getModuleRevisionId(), resolvedPattern, options);
result.setRetrievedArtifacts(retrievedArtifacts);
} catch (ParseException e) {
// ooops, failed to parse a report we already have...
return new Status(IStatus.ERROR, IvyPlugin.ID, IStatus.ERROR,
"failed to parse a resolve report in order to do the retrieve", e);
}
return Status.OK_STATUS;
}
/**
* This function will be called by the {@link IvyResolveJob} after all resolve has been
* accomplished. Note that this function will be called even if the resolve failed.
*/
public void postBatchResolve() {
// by default do nothing
}
public String toString() {
return ivyXmlPath + confInput + (project == null ? "" : (" in " + project.getName()));
}
}