org.fusesource.fabric.agent.download.MavenDownloadTask Maven / Gradle / Ivy
/**
* Copyright (C) FuseSource, Inc.
* http://fusesource.com
*
* Licensed 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.fusesource.fabric.agent.download;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ExecutorService;
import javax.xml.parsers.ParserConfigurationException;
import org.fusesource.fabric.agent.mvn.DownloadableArtifact;
import org.fusesource.fabric.agent.mvn.MavenConfiguration;
import org.fusesource.fabric.agent.mvn.MavenRepositoryURL;
import org.fusesource.fabric.agent.mvn.Parser;
import org.fusesource.fabric.agent.mvn.Version;
import org.fusesource.fabric.agent.mvn.VersionRange;
import org.fusesource.fabric.agent.utils.URLUtils;
import org.fusesource.fabric.agent.utils.XmlUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
public class MavenDownloadTask extends AbstractDownloadTask implements Runnable {
/**
* Logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(AbstractDownloadTask.class);
/**
* 2 spaces indent;
*/
private static final String Ix2 = " ";
/**
* 4 spaces indent;
*/
private static final String Ix4 = " ";
private final MavenRepositoryURL cache;
private final MavenRepositoryURL system;
private final MavenConfiguration configuration;
public MavenDownloadTask(String url, MavenRepositoryURL cache, MavenRepositoryURL system, MavenConfiguration configuration, ExecutorService executor) {
super(url, executor);
this.cache = cache;
this.system = system;
this.configuration = configuration;
}
protected File download() throws Exception {
Parser parser = new Parser(url.substring("mvn:".length()));
Set downloadables;
if (!parser.getVersion().contains("SNAPSHOT")) {
downloadables = doCollectPossibleDownloads(parser, Arrays.asList(cache, system, configuration.getLocalRepository()));
for (DownloadableArtifact artifact : downloadables) {
URL url = artifact.getArtifactURL();
File file = new File(url.getFile());
if (file.exists()) {
return file;
}
}
}
downloadables = collectPossibleDownloads(parser);
if (LOG.isTraceEnabled()) {
LOG.trace("Possible download locations for [" + url + "]");
for (DownloadableArtifact artifact : downloadables) {
LOG.trace(" " + artifact);
}
}
for (DownloadableArtifact artifact : downloadables) {
LOG.trace("Downloading [" + artifact + "]");
try {
configuration.enableProxy(artifact.getArtifactURL());
String repository = cache.getFile().getAbsolutePath();
if (!repository.endsWith(Parser.FILE_SEPARATOR)) {
repository = repository + Parser.FILE_SEPARATOR;
}
InputStream is = artifact.getInputStream();
File file = new File(repository + parser.getArtifactPath());
file.getParentFile().mkdirs();
if (!file.getParentFile().isDirectory()) {
throw new IOException("Unable to create directory " + file.getParentFile().toString());
}
File tmp = File.createTempFile("fabric-agent-", null, file.getParentFile());
OutputStream os = new FileOutputStream(tmp);
copy(is, os);
is.close();
os.close();
if (file.exists() && !file.delete()) {
throw new IOException("Unable to delete file: " + file.toString());
}
if (!tmp.renameTo(file)) {
throw new IOException("Unable to rename file " + tmp.toString() + " to " + file.toString());
}
return file;
} catch (IOException ignore) {
// go on with next repository
LOG.debug(Ix2 + "Could not download [" + artifact + "]");
LOG.trace(Ix2 + "Reason [" + ignore.getClass().getName() + ": " + ignore.getMessage() + "]");
}
}
// no artifact found
throw new IOException("URL [" + url + "] could not be resolved.");
}
/**
* Searches all available repositories for possible artifacts to download. The returned set of downloadable
* artifacts (never null, but maybe empty) will be sorted descending by version of the artifact and by positon of
* repository in the list of repositories to be searched.
*
* @return a non null sorted set of artifacts
* @throws java.net.MalformedURLException re-thrown
*/
private Set collectPossibleDownloads(final Parser parser)
throws MalformedURLException {
final List repositories = new ArrayList();
repositories.addAll(configuration.getRepositories());
repositories.add(configuration.getLocalRepository());
repositories.add(system);
repositories.add(cache);
// if the url contains a preferred repository add that repository as the first repository to be searched
if (parser.getRepositoryURL() != null) {
repositories.add(
repositories.size() == 0 ? 0 : 1,
parser.getRepositoryURL()
);
}
return doCollectPossibleDownloads(parser, repositories);
}
/**
* Search the default repositories for possible artifacts to download.
*/
private Set collectDefaultPossibleDownloads(final Parser parser)
throws MalformedURLException {
return doCollectPossibleDownloads(parser, configuration.getDefaultRepositories());
}
private Set doCollectPossibleDownloads(final Parser parser,
final List repositories)
throws MalformedURLException {
final Set downloadables = new TreeSet(new DownloadComparator());
// find artifact type
final boolean isLatest = parser.getVersion().contains("LATEST");
final boolean isSnapshot = parser.getVersion().endsWith("SNAPSHOT");
VersionRange versionRange = null;
if (!isLatest && !isSnapshot) {
try {
versionRange = new VersionRange(parser.getVersion());
} catch (Exception ignore) {
// well, we do not have a range of versions
}
}
final boolean isVersionRange = versionRange != null;
final boolean isExactVersion = !(isLatest || isSnapshot || isVersionRange);
int priority = 0;
for (MavenRepositoryURL repositoryURL : repositories) {
LOG.debug("Collecting versions from repository [" + repositoryURL + "]");
priority++;
try {
if (isExactVersion) {
downloadables.add(resolveExactVersion(parser, repositoryURL, priority));
} else if (isSnapshot) {
final DownloadableArtifact snapshot =
resolveSnapshotVersion(parser, repositoryURL, priority, parser.getVersion());
downloadables.add(snapshot);
// if we have a local built snapshot we skip the rest of repositories
if (snapshot.isLocalSnapshotBuild()) {
break;
}
} else {
final Document metadata = getMetadata(repositoryURL.getURL(),
new String[]
{
parser.getArtifactLocalMetdataPath(),
parser.getArtifactMetdataPath()
}
);
if (isLatest) {
downloadables.add(resolveLatestVersion(parser, metadata, repositoryURL, priority));
} else {
downloadables.addAll(resolveRangeVersions(parser, metadata, repositoryURL, priority, versionRange));
}
}
} catch (IOException ignore) {
// if metadata cannot be found we go on with the next repository. Maybe we have better luck.
LOG.debug(Ix2 + "Skipping repository [" + repositoryURL + "], reason: " + ignore.getMessage());
}
}
return downloadables;
}
/**
* Returns maven metadata by looking first for a local metatdata xml file and then for a remote one.
* If no metadata file is found or cannot be used an IOException is thrown.
*
* @param repositoryURL url of the repository from where the metadata should be parsed
* @param metadataLocations array of location paths to try as metadata
* @return parsed xml document for the metadata file
* @throws java.io.IOException if:
* metadata file cannot be located
*/
private Document getMetadata(final URL repositoryURL,
final String[] metadataLocations)
throws IOException {
LOG.debug(Ix2 + "Resolving metadata");
InputStream inputStream = null;
String foundLocation = null;
for (String location : metadataLocations) {
try {
// first try to get the artifact local metadata
inputStream = prepareInputStream(repositoryURL, location);
// get out at first found location
foundLocation = location;
LOG.trace(Ix4 + "Metadata found: [" + location + "]");
break;
} catch (IOException ignore) {
LOG.trace(Ix4 + "Metadata not found: [" + location + "]");
}
}
if (inputStream == null) {
throw new IOException("Metadata not found in repository [" + repositoryURL + "]");
}
try {
return XmlUtils.parseDoc(inputStream);
} catch (ParserConfigurationException e) {
throw initIOException("Metadata [" + foundLocation + "] could not be parsed.", e);
} catch (SAXException e) {
throw initIOException("Metadata [" + foundLocation + "] could not be parsed.", e);
}
}
/**
* Returns a downloadable artifact where the version is fully specified.
*
* @param repositoryURL the url of the repository to download from
* @param priority repository priority
* @return a downloadable artifact
* @throws IOException re-thrown
*/
private DownloadableArtifact resolveExactVersion(final Parser parser,
final MavenRepositoryURL repositoryURL,
final int priority)
throws IOException {
if (!repositoryURL.isReleasesEnabled()) {
throw new IOException("Releases not enabled");
}
LOG.debug(Ix2 + "Resolving exact version");
return new DownloadableArtifact(
parser.getVersion(),
priority,
repositoryURL.getURL(),
parser.getArtifactPath(),
false, // no local built snapshot
configuration.getCertificateCheck()
);
}
/**
* Resolves the latest version of the artifact.
*
* @param metadata parsed metadata xml
* @param repositoryURL the url of the repository to download from
* @param priority repository priority
* @return a downloadable artifact or throw an IOException if latest version cannot be determined.
* @throws IOException if the artifact could not be resolved
*/
private DownloadableArtifact resolveLatestVersion(final Parser parser,
final Document metadata,
final MavenRepositoryURL repositoryURL,
final int priority)
throws IOException {
LOG.debug(Ix2 + "Resolving latest version");
final String version = XmlUtils.getTextContentOfElement(metadata, "versioning/versions/version[last]");
if (version != null) {
if (version.endsWith("SNAPSHOT")) {
return resolveSnapshotVersion(parser, repositoryURL, priority, version);
} else {
return new DownloadableArtifact(
version,
priority,
repositoryURL.getURL(),
parser.getArtifactPath(version),
false, // no local built snapshot
configuration.getCertificateCheck()
);
}
}
throw new IOException("LATEST version could not be resolved.");
}
/**
* Resolves snapshot version of the artifact.
* Snapshot versions are resolved by parsing the metadata within the directory that contains the version as:
* 1. if the metadata contains entries like "versioning/snapshot/timestamp (most likely on remote repos) it will
* use the timestamp and buildnumber to point the real version
* 2. if the metatdata does not contain the above (most likely a local repo) it will use as version the
* versioning/lastUpdated
*
* @param repositoryURL the url of the repository to download from
* @param priority repository priority
* @param version snapshot version to resolve
* @return an input stream to the artifact
* @throws IOException if the artifact could not be resolved
*/
private DownloadableArtifact resolveSnapshotVersion(final Parser parser,
final MavenRepositoryURL repositoryURL,
final int priority,
final String version)
throws IOException {
if (!repositoryURL.isSnapshotsEnabled()) {
throw new IOException("Snapshots not enabled");
}
LOG.debug(Ix2 + "Resolving snapshot version [" + version + "]");
try {
final Document snapshotMetadata = getMetadata(repositoryURL.getURL(),
new String[]
{
parser.getVersionLocalMetadataPath(version),
parser.getVersionMetadataPath(version)
}
);
final String timestamp =
XmlUtils.getTextContentOfElement(snapshotMetadata, "versioning/snapshot/timestamp");
final String buildNumber =
XmlUtils.getTextContentOfElement(snapshotMetadata, "versioning/snapshot/buildNumber");
final String localSnapshot =
XmlUtils.getTextContentOfElement(snapshotMetadata, "versioning/snapshot/localCopy");
if (timestamp != null && buildNumber != null) {
return new DownloadableArtifact(
parser.getSnapshotVersion(version, timestamp, buildNumber),
priority,
repositoryURL.getURL(),
parser.getSnapshotPath(version, timestamp, buildNumber),
localSnapshot != null,
configuration.getCertificateCheck()
);
} else {
String lastUpdated = XmlUtils.getTextContentOfElement(snapshotMetadata, "versioning/lastUpdated");
if (lastUpdated != null) {
// last updated should contain in the first 8 chars the date and then the time,
// fact that is not compatible with timeStamp from remote repos which has a "." after date
if (lastUpdated.length() > 8) {
lastUpdated = lastUpdated.substring(0, 8) + "." + lastUpdated.substring(8);
return new DownloadableArtifact(
parser.getSnapshotVersion(version, lastUpdated, "0"),
priority,
repositoryURL.getURL(),
parser.getArtifactPath(version),
localSnapshot != null,
configuration.getCertificateCheck()
);
}
}
}
} catch (IOException ignore) {
// in this case we could not find any metadata so try to get the *-SNAPSHOT file directly
}
return new DownloadableArtifact(
parser.getVersion(),
priority,
repositoryURL.getURL(),
parser.getArtifactPath(),
false, // no local built snapshot
configuration.getCertificateCheck()
);
}
/**
* Resolves all versions that fits the provided range.
*
* @param metadata parsed metadata xml
* @param repositoryURL the url of the repository to download from
* @param priority repository priority
* @param versionRange version range to fulfill
* @return list of downloadable artifacts that match the range
* @throws IOException re-thrown
*/
private List resolveRangeVersions(final Parser parser,
final Document metadata,
final MavenRepositoryURL repositoryURL,
final int priority,
final VersionRange versionRange)
throws IOException {
LOG.debug(Ix2 + "Resolving versions in range [" + versionRange + "]");
final List downladables = new ArrayList();
final List elements = XmlUtils.getElements(metadata, "versioning/versions/version");
if (elements != null && elements.size() > 0) {
for (Element element : elements) {
final String versionString = XmlUtils.getTextContent(element);
if (versionString != null) {
final Version version = new Version(versionString);
if (versionRange.includes(version)) {
if (versionString.endsWith("SNAPSHOT")) {
downladables.add(
resolveSnapshotVersion(parser, repositoryURL, priority, versionString)
);
} else {
downladables.add(
new DownloadableArtifact(
versionString,
priority,
repositoryURL.getURL(),
parser.getArtifactPath(versionString),
false, // no local built snapshot
configuration.getCertificateCheck()
)
);
}
}
}
}
}
return downladables;
}
/**
* @param repositoryURL url to reporsitory
* @param path a path to the artifact jar file
* @return prepared input stream
* @throws IOException re-thrown
* @see org.ops4j.net.URLUtils#prepareInputStream(java.net.URL, boolean)
*/
private InputStream prepareInputStream(URL repositoryURL, final String path)
throws IOException {
String repository = repositoryURL.toExternalForm();
if (!repository.endsWith(org.ops4j.pax.url.mvn.internal.Parser.FILE_SEPARATOR)) {
repository = repository + org.ops4j.pax.url.mvn.internal.Parser.FILE_SEPARATOR;
}
configuration.enableProxy(repositoryURL);
final URL url = new URL(repository + path);
LOG.trace("Reading " + url.toExternalForm());
return URLUtils.prepareInputStream(url, !configuration.getCertificateCheck());
}
/**
* Sorting comparator for downladable artifacts.
* The sorting is done by:
* 1. descending version
* 2. ascending priority.
*/
private static class DownloadComparator
implements Comparator {
public int compare(final DownloadableArtifact first,
final DownloadableArtifact second) {
// first descending by version
int result = -1 * first.getVersion().compareTo(second.getVersion());
if (result == 0) {
// then ascending by priority
if (first.getPriority() < second.getPriority()) {
result = -1;
} else if (first.getPriority() > second.getPriority()) {
result = 1;
}
}
return result;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy