blob: b325faa0de4a72347e39f098fd894381e064cdcc [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
*
* https://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.ivy.plugins.resolver;
import org.apache.ivy.core.IvyPatternHelper;
import org.apache.ivy.core.cache.ArtifactOrigin;
import org.apache.ivy.core.module.descriptor.Artifact;
import org.apache.ivy.core.module.descriptor.DefaultArtifact;
import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
import org.apache.ivy.core.module.id.ModuleRevisionId;
import org.apache.ivy.core.report.DownloadReport;
import org.apache.ivy.core.resolve.DownloadOptions;
import org.apache.ivy.core.resolve.ResolveData;
import org.apache.ivy.core.resolve.ResolvedModuleRevision;
import org.apache.ivy.core.search.ModuleEntry;
import org.apache.ivy.core.search.OrganisationEntry;
import org.apache.ivy.core.search.RevisionEntry;
import org.apache.ivy.plugins.matcher.PatternMatcher;
import org.apache.ivy.plugins.repository.Repository;
import org.apache.ivy.plugins.repository.Resource;
import org.apache.ivy.plugins.resolver.util.ResolvedResource;
import org.apache.ivy.plugins.version.MavenTimedSnapshotVersionMatcher;
import org.apache.ivy.util.ContextualSAXHandler;
import org.apache.ivy.util.Message;
import org.apache.ivy.util.XMLHelper;
import org.xml.sax.SAXException;
import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* IBiblioResolver is a resolver which can be used to resolve dependencies found in the ibiblio
* maven repository, or similar repositories.
* <p>
* For more flexibility with url and patterns, see
* {@link org.apache.ivy.plugins.resolver.URLResolver}.
*/
public class IBiblioResolver extends URLResolver {
private static final String M2_PER_MODULE_PATTERN = "[revision]/[artifact]-[revision](-[classifier]).[ext]";
private static final String M2_PATTERN = "[organisation]/[module]/" + M2_PER_MODULE_PATTERN;
@Deprecated
public static final String DEFAULT_PATTERN = "[module]/[type]s/[artifact]-[revision].[ext]";
@Deprecated
public static final String DEFAULT_ROOT = "http://www.ibiblio.org/maven/";
public static final String DEFAULT_M2_ROOT = "https://repo1.maven.org/maven2/";
private String root = null;
private String pattern = null;
// use poms if m2 compatible is true
private boolean usepoms = true;
// use maven-metadata.xml is exists to list revisions
private boolean useMavenMetadata = true;
public IBiblioResolver() {
// SNAPSHOT revisions are changing revisions
setChangingMatcher(PatternMatcher.REGEXP);
setChangingPattern(".*-SNAPSHOT");
}
@Override
public ResolvedResource findIvyFileRef(DependencyDescriptor dd, ResolveData data) {
if (!isM2compatible() || !isUsepoms()) {
return null;
}
ModuleRevisionId mrid = dd.getDependencyRevisionId();
mrid = convertM2IdForResourceSearch(mrid);
final String revision = dd.getDependencyRevisionId().getRevision();
final MavenTimedSnapshotVersionMatcher.MavenSnapshotRevision snapshotRevision = MavenTimedSnapshotVersionMatcher.computeIfSnapshot(revision);
if (snapshotRevision != null) {
final ResolvedResource rres = findSnapshotDescriptor(dd, data, mrid, snapshotRevision);
if (rres != null) {
return rres;
}
}
return findResourceUsingPatterns(mrid, getIvyPatterns(),
DefaultArtifact.newPomArtifact(mrid, data.getDate()), getRMDParser(dd, data),
data.getDate());
}
@Override
public ResolvedResource findArtifactRef(Artifact artifact, Date date) {
ensureConfigured(getSettings());
ModuleRevisionId mrid = artifact.getModuleRevisionId();
if (isM2compatible()) {
mrid = convertM2IdForResourceSearch(mrid);
}
final String revision = artifact.getId().getRevision();
final MavenTimedSnapshotVersionMatcher.MavenSnapshotRevision snapshotRevision = MavenTimedSnapshotVersionMatcher.computeIfSnapshot(revision);
if (snapshotRevision != null) {
final ResolvedResource rres = findSnapshotArtifact(artifact, date, mrid, snapshotRevision);
if (rres != null) {
return rres;
}
}
return findResourceUsingPatterns(mrid, getArtifactPatterns(), artifact,
getDefaultRMDParser(artifact.getModuleRevisionId().getModuleId()), date);
}
private ResolvedResource findSnapshotArtifact(final Artifact artifact, final Date date,
final ModuleRevisionId mrid, final MavenTimedSnapshotVersionMatcher.MavenSnapshotRevision snapshotRevision) {
if (!isM2compatible()) {
return null;
}
final String snapshotArtifactPattern;
if (snapshotRevision.isTimestampedSnapshot()) {
Message.debug(mrid + " has been identified as a (Maven) timestamped snapshot revision");
// this is a Maven timestamped snapshot revision. Something like 1.0.0-<timestampedRev>
// We now get the base revision from it, which is "1.0.0" and append the "-SNAPSHOT" to it.
final String inferredSnapshotRevision = snapshotRevision.getBaseRevision() + "-SNAPSHOT";
// we replace the "/[revision]" in the descriptor pattern with the "inferred" snapshot
// revision which is like "1.0.0-SNAPSHOT". Ultimately, this will translate to
// something like
// org/module/1.0.0-SNAPSHOT/artifact-1.0.0-<timestampedRev>(-[classifier]).ext
snapshotArtifactPattern = getWholePattern().replaceFirst("/\\[revision\\]", "/" + inferredSnapshotRevision);
} else {
// it's not a timestamped revision, but a regular snapshot. Try and find any potential
// timestamped revisions of this regular snapshot, by looking into the Maven metadata
final String timestampedRev = findTimestampedSnapshotVersion(mrid);
if (timestampedRev == null) {
// no timestamped snapshots found and instead this is just a regular snapshot
// version. So let's just fallback to our logic of finding resources using
// configured artifact pattern(s)
return null;
}
Message.verbose(mrid + " has been identified as a snapshot revision which has a timestamped snapshot revision " + timestampedRev);
// we have found a timestamped revision for a snapshot. So we replace the "-[revision]"
// in the artifact file name to use the timestamped revision.
// Ultimately, this will translate to something like
// org/module/1.0.0-SNAPSHOT/artifact-1.0.0-<timestampedRev>(-[classifier]).ext
snapshotArtifactPattern = getWholePattern().replaceFirst("\\-\\[revision\\]", "-" + timestampedRev);
}
return findResourceUsingPattern(mrid, snapshotArtifactPattern, artifact, getDefaultRMDParser(artifact
.getModuleRevisionId().getModuleId()), date);
}
private ResolvedResource findSnapshotDescriptor(final DependencyDescriptor dd, final ResolveData data,
final ModuleRevisionId mrid,
final MavenTimedSnapshotVersionMatcher.MavenSnapshotRevision snapshotRevision) {
if (!isM2compatible()) {
return null;
}
final String snapshotDescriptorPattern;
if (snapshotRevision.isTimestampedSnapshot()) {
Message.debug(mrid + " has been identified as a (Maven) timestamped snapshot revision");
// this is a Maven timestamped snapshot revision. Something like 1.0.0-<timestampedRev>
// We now get the base revision from it, which is "1.0.0" and append the "-SNAPSHOT" to it.
final String inferredSnapshotRevision = snapshotRevision.getBaseRevision() + "-SNAPSHOT";
// we replace the "/[revision]" in the descriptor pattern with the "inferred" snapshot
// revision which is like "1.0.0-SNAPSHOT".
// Ultimately, this will translate to something like
// org/module/1.0.0-SNAPSHOT/artifact-1.0.0-<timestampedRev>(-[classifier]).ext
snapshotDescriptorPattern = getWholePattern().replaceFirst("/\\[revision\\]", "/" + inferredSnapshotRevision);
} else {
// it's not a timestamped revision, but a regular snapshot. Try and find any potential
// timestamped revisions of this regular snapshot, by looking into the Maven metadata
final String timestampedRev = findTimestampedSnapshotVersion(mrid);
if (timestampedRev == null) {
// no timestamped snapshots found and instead this is just a regular snapshot
// version. So let's just fallback to our logic of finding resources using
// configured Ivy pattern(s)
return null;
}
Message.verbose(mrid + " has been identified as a snapshot revision which has a timestamped snapshot revision " + timestampedRev);
// we have found a timestamped revision for a snapshot. So we replace the "-[revision]"
// in the artifact file name to use the timestamped revision.
// Ultimately, this will translate to something like
// org/module/1.0.0-SNAPSHOT/artifact-1.0.0-<timestampedRev>(-[classifier]).ext
snapshotDescriptorPattern = getWholePattern().replaceFirst("\\-\\[revision\\]", "-" + timestampedRev);
}
// find the descriptor using the snapshot descriptor pattern
return findResourceUsingPattern(mrid, snapshotDescriptorPattern,
DefaultArtifact.newPomArtifact(mrid, data.getDate()), getRMDParser(dd, data),
data.getDate());
}
private String findTimestampedSnapshotVersion(final ModuleRevisionId mrid) {
if (!isM2compatible()) {
return null;
}
if (!shouldUseMavenMetadata(getWholePattern())) {
return null;
}
try {
final String metadataLocation = IvyPatternHelper.substitute(root
+ "[organisation]/[module]/[revision]/maven-metadata.xml", mrid);
final Resource metadata = getRepository().getResource(metadataLocation);
if (!metadata.exists()) {
Message.verbose("\tmaven-metadata not available for: " + mrid);
return null;
}
try (final InputStream metadataStream = metadata.openStream()) {
final StringBuilder timestamp = new StringBuilder();
final StringBuilder buildNumber = new StringBuilder();
XMLHelper.parse(metadataStream, null, new ContextualSAXHandler() {
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
if ("metadata/versioning/snapshot/timestamp".equals(getContext())) {
timestamp.append(getText());
}
if ("metadata/versioning/snapshot/buildNumber".equals(getContext())) {
buildNumber.append(getText());
}
super.endElement(uri, localName, qName);
}
}, null);
if (timestamp.length() > 0) {
// we have found a timestamp, so this is a snapshot unique version
String rev = mrid.getRevision();
rev = rev.substring(0, rev.length() - "SNAPSHOT".length());
rev += timestamp.toString() + "-" + buildNumber.toString();
return rev;
}
}
} catch (IOException | SAXException | ParserConfigurationException e) {
Message.debug("impossible to access maven metadata file, ignored", e);
}
return null;
}
@Override
public void setM2compatible(boolean m2compatible) {
super.setM2compatible(m2compatible);
if (m2compatible) {
if (root == null) {
root = DEFAULT_M2_ROOT;
}
if (pattern == null) {
pattern = M2_PATTERN;
}
updateWholePattern();
}
}
public void ensureConfigured(ResolverSettings settings) {
if (settings != null && (root == null || pattern == null)) {
if (root == null) {
String root = settings.getVariable("ivy.ibiblio.default.artifact.root");
if (root != null) {
this.root = root;
} else {
settings.configureRepositories(true);
this.root = settings.getVariable("ivy.ibiblio.default.artifact.root");
}
}
if (pattern == null) {
String pattern = settings.getVariable("ivy.ibiblio.default.artifact.pattern");
if (pattern != null) {
this.pattern = pattern;
} else {
settings.configureRepositories(false);
this.pattern = settings.getVariable("ivy.ibiblio.default.artifact.pattern");
}
}
updateWholePattern();
}
}
@Override
protected String getModuleDescriptorExtension() {
return "pom";
}
private String getWholePattern() {
return root + pattern;
}
public String getPattern() {
return pattern;
}
public void setPattern(String pattern) {
if (pattern == null) {
throw new NullPointerException("pattern must not be null");
}
this.pattern = pattern;
ensureConfigured(getSettings());
updateWholePattern();
}
public String getRoot() {
return root;
}
/**
* Sets the root of the maven like repository. The maven like repository is necessarily an http
* repository.
*
* @param root the root of the maven like repository
* @throws IllegalArgumentException if root does not start with "http://"
*/
public void setRoot(String root) {
if (root == null) {
throw new NullPointerException("root must not be null");
}
if (!root.endsWith("/")) {
this.root = root + "/";
} else {
this.root = root;
}
ensureConfigured(getSettings());
updateWholePattern();
}
private void updateWholePattern() {
if (isM2compatible() && isUsepoms()) {
setIvyPatterns(Collections.singletonList(getWholePattern()));
} else {
setIvyPatterns(Collections.<String>emptyList());
}
setArtifactPatterns(Collections.singletonList(getWholePattern()));
}
public void publish(Artifact artifact, File src) {
throw new UnsupportedOperationException("publish not supported by IBiblioResolver");
}
// we do not allow to list organisations on ibiblio, nor modules in ibiblio 1
@Override
public String[] listTokenValues(String token, Map<String, String> otherTokenValues) {
if (IvyPatternHelper.ORGANISATION_KEY.equals(token)) {
return new String[0];
}
if (IvyPatternHelper.MODULE_KEY.equals(token) && !isM2compatible()) {
return new String[0];
}
ensureConfigured(getSettings());
return super.listTokenValues(token, otherTokenValues);
}
@Override
protected String[] listTokenValues(String pattern, String token) {
if (IvyPatternHelper.ORGANISATION_KEY.equals(token)) {
return new String[0];
}
if (IvyPatternHelper.MODULE_KEY.equals(token) && !isM2compatible()) {
return new String[0];
}
ensureConfigured(getSettings());
// let's see if we should use maven metadata for this listing...
if (IvyPatternHelper.REVISION_KEY.equals(token)
&& shouldUseMavenMetadata(getWholePattern())) {
// now we must use metadata if available
/*
* we substitute tokens with ext token only in the m2 per module pattern, to match has
* has been done in the given pattern
*/
String partiallyResolvedM2PerModulePattern = IvyPatternHelper.substituteTokens(
M2_PER_MODULE_PATTERN, Collections.singletonMap(IvyPatternHelper.EXT_KEY, "pom"));
if (pattern.endsWith(partiallyResolvedM2PerModulePattern)) {
/*
* the given pattern already contain resolved org and module, we just have to
* replace the per module pattern at the end by 'maven-metadata.xml' to have the
* maven metadata file location
*/
String metadataLocation = pattern.substring(0,
pattern.lastIndexOf(partiallyResolvedM2PerModulePattern))
+ "maven-metadata.xml";
List<String> revs = listRevisionsWithMavenMetadata(getRepository(),
metadataLocation);
if (revs != null) {
return revs.toArray(new String[revs.size()]);
}
} else {
/*
* this is probably because the given pattern has been substituted with jar ext, if
* this resolver has optional module descriptors. But since we have to use maven
* metadata, we don't care about this case, maven metadata has already been used
* when looking for revisions with the pattern substituted with ext=xml for the
* "ivy" pattern.
*/
return new String[0];
}
}
return super.listTokenValues(pattern, token);
}
@Override
public OrganisationEntry[] listOrganisations() {
return new OrganisationEntry[0];
}
@Override
public ModuleEntry[] listModules(OrganisationEntry org) {
if (isM2compatible()) {
ensureConfigured(getSettings());
return super.listModules(org);
}
return new ModuleEntry[0];
}
@Override
public RevisionEntry[] listRevisions(ModuleEntry mod) {
ensureConfigured(getSettings());
return super.listRevisions(mod);
}
@Override
protected ResolvedResource[] listResources(Repository repository, ModuleRevisionId mrid,
String pattern, Artifact artifact) {
if (shouldUseMavenMetadata(pattern)) {
List<String> revs = listRevisionsWithMavenMetadata(repository, mrid.getModuleId()
.getAttributes());
if (revs != null) {
Message.debug("\tfound revs: " + revs);
List<ResolvedResource> rres = new ArrayList<>();
for (String rev : revs) {
ModuleRevisionId historicalMrid = ModuleRevisionId.newInstance(mrid, rev);
String patternForRev = pattern;
if (rev.endsWith("SNAPSHOT")) {
String snapshotVersion = findTimestampedSnapshotVersion(historicalMrid);
if (snapshotVersion != null) {
patternForRev = pattern.replaceFirst("\\-\\[revision\\]", "-"
+ snapshotVersion);
}
}
String resolvedPattern = IvyPatternHelper.substitute(patternForRev,
historicalMrid, artifact);
try {
Resource res = repository.getResource(resolvedPattern);
if (res != null) {
// we do not test if the resource actually exist here, it would cause
// a lot of checks which are not always necessary depending on the usage
// which is done of the returned ResolvedResource array
rres.add(new ResolvedResource(res, rev));
}
} catch (IOException e) {
Message.warn(
"impossible to get resource from name listed by maven-metadata.xml:"
+ rres, e);
}
}
return rres.toArray(new ResolvedResource[rres.size()]);
} else {
// maven metadata not available or something went wrong,
// use default listing capability
return super.listResources(repository, mrid, pattern, artifact);
}
} else {
return super.listResources(repository, mrid, pattern, artifact);
}
}
private List<String> listRevisionsWithMavenMetadata(Repository repository,
Map<String, String> tokenValues) {
String metadataLocation = IvyPatternHelper.substituteTokens(root
+ "[organisation]/[module]/maven-metadata.xml", tokenValues);
return listRevisionsWithMavenMetadata(repository, metadataLocation);
}
private List<String> listRevisionsWithMavenMetadata(Repository repository,
String metadataLocation) {
List<String> revs = null;
InputStream metadataStream = null;
try {
Resource metadata = repository.getResource(metadataLocation);
if (metadata.exists()) {
Message.verbose("\tlisting revisions from maven-metadata: " + metadata);
final List<String> metadataRevs = new ArrayList<>();
metadataStream = metadata.openStream();
XMLHelper.parse(metadataStream, null, new ContextualSAXHandler() {
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
if ("metadata/versioning/versions/version".equals(getContext())) {
metadataRevs.add(getText().trim());
}
super.endElement(uri, localName, qName);
}
}, null);
revs = metadataRevs;
} else {
Message.verbose("\tmaven-metadata not available: " + metadata);
}
} catch (IOException e) {
Message.verbose("impossible to access maven metadata file, ignored", e);
} catch (SAXException | ParserConfigurationException e) {
Message.verbose("impossible to parse maven metadata file, ignored", e);
} finally {
if (metadataStream != null) {
try {
metadataStream.close();
} catch (IOException e) {
// ignored
}
}
}
return revs;
}
@Override
protected void findTokenValues(Collection<String> names, List<String> patterns,
Map<String, String> tokenValues, String token) {
if (IvyPatternHelper.REVISION_KEY.equals(token)) {
if (shouldUseMavenMetadata(getWholePattern())) {
List<String> revs = listRevisionsWithMavenMetadata(getRepository(), tokenValues);
if (revs != null) {
names.addAll(filterNames(revs));
return;
}
}
}
super.findTokenValues(names, patterns, tokenValues, token);
}
private boolean shouldUseMavenMetadata(String pattern) {
return isUseMavenMetadata() && isM2compatible() && pattern.endsWith(M2_PATTERN);
}
@Override
public String getTypeName() {
return "ibiblio";
}
// override some methods to ensure configuration
@Override
public ResolvedModuleRevision getDependency(DependencyDescriptor dd, ResolveData data)
throws ParseException {
ensureConfigured(data.getSettings());
return super.getDependency(dd, data);
}
@Override
public DownloadReport download(Artifact[] artifacts, DownloadOptions options) {
ensureConfigured(getSettings());
return super.download(artifacts, options);
}
@Override
public boolean exists(Artifact artifact) {
ensureConfigured(getSettings());
return super.exists(artifact);
}
@Override
public ArtifactOrigin locate(Artifact artifact) {
ensureConfigured(getSettings());
return super.locate(artifact);
}
@Override
public List<String> getArtifactPatterns() {
ensureConfigured(getSettings());
return super.getArtifactPatterns();
}
public boolean isUsepoms() {
return usepoms;
}
public void setUsepoms(boolean usepoms) {
this.usepoms = usepoms;
updateWholePattern();
}
public boolean isUseMavenMetadata() {
return useMavenMetadata;
}
public void setUseMavenMetadata(boolean useMavenMetadata) {
this.useMavenMetadata = useMavenMetadata;
}
@Override
public void dumpSettings() {
ensureConfigured(getSettings());
super.dumpSettings();
Message.debug("\t\troot: " + getRoot());
Message.debug("\t\tpattern: " + getPattern());
Message.debug("\t\tusepoms: " + usepoms);
Message.debug("\t\tuseMavenMetadata: " + useMavenMetadata);
}
}