| /* |
| * 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); |
| } |
| } |