/* * 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.ivy.plugins.resolver; 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.Iterator; import java.util.List; import java.util.Map; import javax.xml.parsers.ParserConfigurationException; 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.util.ContextualSAXHandler; import org.apache.ivy.util.Message; import org.apache.ivy.util.XMLHelper; import org.xml.sax.SAXException; /** * IBiblioResolver is a resolver which can be used to resolve dependencies found in the ibiblio * maven repository, or similar repositories. *

* 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; public static final String DEFAULT_PATTERN = "[module]/[type]s/[artifact]-[revision].[ext]"; public static final String DEFAULT_ROOT = "http://www.ibiblio.org/maven/"; public static final String DEFAULT_M2_ROOT = "http://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"); } public ResolvedResource findIvyFileRef(DependencyDescriptor dd, ResolveData data) { if (isM2compatible() && isUsepoms()) { ModuleRevisionId mrid = dd.getDependencyRevisionId(); mrid = convertM2IdForResourceSearch(mrid); ResolvedResource rres = null; if (dd.getDependencyRevisionId().getRevision().endsWith("SNAPSHOT")) { rres = findSnapshotDescriptor(dd, data, mrid); if (rres != null) { return rres; } } rres = findResourceUsingPatterns(mrid, getIvyPatterns(), DefaultArtifact.newPomArtifact(mrid, data.getDate()), getRMDParser(dd, data), data.getDate()); return rres; } else { return null; } } public ResolvedResource findArtifactRef(Artifact artifact, Date date) { ensureConfigured(getSettings()); ModuleRevisionId mrid = artifact.getModuleRevisionId(); if (isM2compatible()) { mrid = convertM2IdForResourceSearch(mrid); } ResolvedResource rres = null; if (artifact.getId().getRevision().endsWith("SNAPSHOT") && isM2compatible()) { rres = findSnapshotArtifact(artifact, date, mrid); if (rres != null) { return rres; } } return findResourceUsingPatterns(mrid, getArtifactPatterns(), artifact, getDefaultRMDParser(artifact.getModuleRevisionId().getModuleId()), date); } private ResolvedResource findSnapshotArtifact(Artifact artifact, Date date, ModuleRevisionId mrid) { String rev = findSnapshotVersion(mrid); if (rev != null) { // replace the revision token in file name with the resolved revision String pattern = getWholePattern().replaceFirst("\\-\\[revision\\]", "-" + rev); return findResourceUsingPattern(mrid, pattern, artifact, getDefaultRMDParser(artifact .getModuleRevisionId().getModuleId()), date); } return null; } private ResolvedResource findSnapshotDescriptor(DependencyDescriptor dd, ResolveData data, ModuleRevisionId mrid) { String rev = findSnapshotVersion(mrid); if (rev != null) { // here it would be nice to be able to store the resolved snapshot version, to avoid // having to follow the same process to download artifacts Message.verbose("[" + rev + "] " + mrid); // replace the revision token in file name with the resolved revision String pattern = getWholePattern().replaceFirst("\\-\\[revision\\]", "-" + rev); return findResourceUsingPattern(mrid, pattern, DefaultArtifact.newPomArtifact(mrid, data.getDate()), getRMDParser(dd, data), data.getDate()); } return null; } private String findSnapshotVersion(ModuleRevisionId mrid) { if (!isM2compatible()) { return null; } if (shouldUseMavenMetadata(getWholePattern())) { InputStream metadataStream = null; try { String metadataLocation = IvyPatternHelper.substitute(root + "[organisation]/[module]/[revision]/maven-metadata.xml", mrid); Resource metadata = getRepository().getResource(metadataLocation); if (metadata.exists()) { metadataStream = metadata.openStream(); final StringBuffer timestamp = new StringBuffer(); final StringBuffer buildNumer = new StringBuffer(); XMLHelper.parse(metadataStream, null, new ContextualSAXHandler() { 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())) { buildNumer.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 = rev + timestamp.toString() + "-" + buildNumer.toString(); return rev; } } else { Message.verbose("\tmaven-metadata not available: " + metadata); } } catch (IOException e) { Message.verbose("impossible to access maven metadata file, ignored", e); } catch (SAXException e) { Message.verbose("impossible to parse maven metadata file, ignored", e); } catch (ParserConfigurationException e) { Message.verbose("impossible to parse maven metadata file, ignored", e); } finally { if (metadataStream != null) { try { metadataStream.close(); } catch (IOException e) { // ignored } } } } return null; } 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(); } } 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.EMPTY_LIST); } 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 public String[] listTokenValues(String token, Map 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); } 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 revs = listRevisionsWithMavenMetadata(getRepository(), metadataLocation); if (revs != null) { return (String[]) 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); } public OrganisationEntry[] listOrganisations() { return new OrganisationEntry[0]; } public ModuleEntry[] listModules(OrganisationEntry org) { if (isM2compatible()) { ensureConfigured(getSettings()); return super.listModules(org); } return new ModuleEntry[0]; } public RevisionEntry[] listRevisions(ModuleEntry mod) { ensureConfigured(getSettings()); return super.listRevisions(mod); } protected ResolvedResource[] listResources(Repository repository, ModuleRevisionId mrid, String pattern, Artifact artifact) { if (shouldUseMavenMetadata(pattern)) { List revs = listRevisionsWithMavenMetadata(repository, mrid.getModuleId() .getAttributes()); if (revs != null) { Message.debug("\tfound revs: " + revs); List rres = new ArrayList(); for (Iterator iter = revs.iterator(); iter.hasNext();) { String rev = (String) iter.next(); ModuleRevisionId historicalMrid = ModuleRevisionId.newInstance(mrid, rev); String patternForRev = pattern; if (rev.endsWith("SNAPSHOT")) { String snapshotVersion = findSnapshotVersion(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 (ResolvedResource[]) 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 listRevisionsWithMavenMetadata(Repository repository, Map tokenValues) { String metadataLocation = IvyPatternHelper.substituteTokens(root + "[organisation]/[module]/maven-metadata.xml", tokenValues); return listRevisionsWithMavenMetadata(repository, metadataLocation); } private List listRevisionsWithMavenMetadata(Repository repository, String metadataLocation) { List revs = null; InputStream metadataStream = null; try { Resource metadata = repository.getResource(metadataLocation); if (metadata.exists()) { Message.verbose("\tlisting revisions from maven-metadata: " + metadata); final List metadataRevs = new ArrayList(); metadataStream = metadata.openStream(); XMLHelper.parse(metadataStream, null, new ContextualSAXHandler() { 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 e) { Message.verbose("impossible to parse maven metadata file, ignored", e); } catch (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; } protected void findTokenValues(Collection names, List patterns, Map tokenValues, String token) { if (IvyPatternHelper.REVISION_KEY.equals(token)) { if (shouldUseMavenMetadata(getWholePattern())) { List 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); } public String getTypeName() { return "ibiblio"; } // override some methods to ensure configuration public ResolvedModuleRevision getDependency(DependencyDescriptor dd, ResolveData data) throws ParseException { ensureConfigured(data.getSettings()); return super.getDependency(dd, data); } public DownloadReport download(Artifact[] artifacts, DownloadOptions options) { ensureConfigured(getSettings()); return super.download(artifacts, options); } public boolean exists(Artifact artifact) { ensureConfigured(getSettings()); return super.exists(artifact); } public ArtifactOrigin locate(Artifact artifact) { ensureConfigured(getSettings()); return super.locate(artifact); } public List 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; } 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); } }