Index: test/java/org/apache/ivy/core/resolve/ResolveTest.java =================================================================== --- test/java/org/apache/ivy/core/resolve/ResolveTest.java (revision 597158) +++ test/java/org/apache/ivy/core/resolve/ResolveTest.java (working copy) @@ -43,6 +43,7 @@ import org.apache.ivy.core.report.ConfigurationResolveReport; import org.apache.ivy.core.report.DownloadStatus; import org.apache.ivy.core.report.ResolveReport; +import org.apache.ivy.core.resolve.IvyNode; import org.apache.ivy.core.settings.IvySettings; import org.apache.ivy.plugins.circular.CircularDependencyException; import org.apache.ivy.plugins.circular.ErrorCircularDependencyStrategy; @@ -3039,6 +3040,103 @@ "1.0", "test-classifier", "jar", "jar").exists()); } + public void testResolveMaven2ParentPomChainResolver() throws Exception { + // test has a dependency on test2 but there is no version listed. test has a parent of parent(2.0) + // then parent2. Both parents have a dependencyManagement element for test2, and each list the version as + // ${pom.version}. The parent version should take precidence over parent2, + // so the version should be test2 version 2.0. Test3 is also a dependency, and it's version is listed + // as 1.0 in parent2. + Ivy ivy = new Ivy(); + ivy.configure(new File("test/repositories/parentPom/ivysettings.xml")); + ivy.getSettings().setDefaultResolver("parentChain"); + + ResolveReport report = ivy.resolve(new File( + "test/repositories/parentPom/org/apache/dm/test/1.0/test-1.0.pom").toURL(), + getResolveOptions(new String[] {"*"})); + assertNotNull(report); + ModuleDescriptor md = report.getModuleDescriptor(); + assertNotNull(md); + ModuleRevisionId mrid = ModuleRevisionId.newInstance("org.apache.dm", "test", "1.0"); + assertEquals(mrid, md.getModuleRevisionId()); + + assertTrue(ivy.getCacheManager(cache).getResolvedIvyFileInCache(mrid).exists()); + + //test the report to make sure the right dependencies are listed + List dependencies = report.getDependencies(); + assertEquals(2, dependencies.size()); + + IvyNode ivyNode; + ivyNode = (IvyNode) dependencies.get(0); + assertNotNull(ivyNode); + mrid = ModuleRevisionId.newInstance("org.apache.dm", "test2", "2.0"); + assertEquals(mrid, ivyNode.getId()); + // dependencies + assertTrue(ivy.getCacheManager(cache).getIvyFileInCache( + ModuleRevisionId.newInstance("org.apache.dm", "test2", "2.0")).exists()); + assertTrue(TestHelper.getArchiveFileInCache(ivy, cache, "org.apache.dm", "test2", "2.0", + "test2", "jar", "jar").exists()); + + ivyNode = (IvyNode) dependencies.get(1); + assertNotNull(ivyNode); + mrid = ModuleRevisionId.newInstance("org.apache.dm", "test3", "1.0"); + assertEquals(mrid, ivyNode.getId()); + // dependencies + assertTrue(ivy.getCacheManager(cache).getIvyFileInCache( + ModuleRevisionId.newInstance("org.apache.dm", "test3", "1.0")).exists()); + assertTrue(TestHelper.getArchiveFileInCache(ivy, cache, "org.apache.dm", "test3", "1.0", + "test3", "jar", "jar").exists()); + } + + public void testResolveMaven2ParentPomDualResolver() throws Exception { + // test has a dependency on test2 but there is no version listed. test has a parent of parent(2.0) + // then parent2. Both parents have a dependencyManagement element for test2, and each list the version as + // ${pom.version}. The parent version should take precidence over parent2, + // so the version should be test2 version 2.0. Test3 is also a dependency, and it's version is listed + // as 1.0 in parent2. + + // now run tests with dual resolver + Ivy ivy = new Ivy(); + ivy.configure(new File("test/repositories/parentPom/ivysettings.xml")); + ivy.getSettings().setDefaultResolver("parentDual"); + + ResolveReport report = ivy.resolve(new File( + "test/repositories/parentPom/org/apache/dm/test/1.0/test-1.0.pom").toURL(), + getResolveOptions(new String[] {"*"})); + assertNotNull(report); + ModuleDescriptor md = report.getModuleDescriptor(); + assertNotNull(md); + ModuleRevisionId mrid = ModuleRevisionId.newInstance("org.apache.dm", "test", "1.0"); + assertEquals(mrid, md.getModuleRevisionId()); + + assertTrue(ivy.getCacheManager(cache).getResolvedIvyFileInCache(mrid).exists()); + + //test the report to make sure the right dependencies are listed + List dependencies = report.getDependencies(); + assertEquals(2, dependencies.size()); + + IvyNode ivyNode; + ivyNode = (IvyNode) dependencies.get(0); + assertNotNull(ivyNode); + mrid = ModuleRevisionId.newInstance("org.apache.dm", "test2", "2.0"); + assertEquals(mrid, ivyNode.getId()); + // dependencies + assertTrue(ivy.getCacheManager(cache).getIvyFileInCache( + ModuleRevisionId.newInstance("org.apache.dm", "test2", "2.0")).exists()); + assertTrue(TestHelper.getArchiveFileInCache(ivy, cache, "org.apache.dm", "test2", "2.0", + "test2", "jar", "jar").exists()); + + ivyNode = (IvyNode) dependencies.get(1); + assertNotNull(ivyNode); + mrid = ModuleRevisionId.newInstance("org.apache.dm", "test3", "1.0"); + assertEquals(mrid, ivyNode.getId()); + // dependencies + assertTrue(ivy.getCacheManager(cache).getIvyFileInCache( + ModuleRevisionId.newInstance("org.apache.dm", "test3", "1.0")).exists()); + assertTrue(TestHelper.getArchiveFileInCache(ivy, cache, "org.apache.dm", "test3", "1.0", + "test3", "jar", "jar").exists()); + + } + public void testNamespaceMapping() throws Exception { // the dependency is in another namespace Ivy ivy = new Ivy(); Index: test/repositories/parentPom/org/apache/dm/test/1.0/test-1.0.pom =================================================================== --- test/repositories/parentPom/org/apache/dm/test/1.0/test-1.0.pom (revision 0) +++ test/repositories/parentPom/org/apache/dm/test/1.0/test-1.0.pom (revision 0) @@ -0,0 +1,46 @@ + + + + + parent + org.apache.dm + 2.0 + + 4.0.0 + org.apache.dm + test + Test parsing parent POM + 1.0 + http://ivy.jayasoft.org/ + + Jayasoft + http://www.jayasoft.org/ + + + + org.apache.dm + test2 + + + org.apache.dm + test3 + + + Index: test/repositories/parentPom/org/apache/dm/test/1.0/test-1.0.jar =================================================================== --- test/repositories/parentPom/org/apache/dm/test/1.0/test-1.0.jar (revision 0) +++ test/repositories/parentPom/org/apache/dm/test/1.0/test-1.0.jar (revision 0) @@ -0,0 +1 @@ + Index: test/repositories/parentPom/org/apache/dm/test2/2.0/test2-2.0.pom =================================================================== --- test/repositories/parentPom/org/apache/dm/test2/2.0/test2-2.0.pom (revision 0) +++ test/repositories/parentPom/org/apache/dm/test2/2.0/test2-2.0.pom (revision 0) @@ -0,0 +1,31 @@ + + + + 4.0.0 + org.apache.dm + test2 + Test parsing parent POM + 2.0 + http://ivy.jayasoft.org/ + + Jayasoft + http://www.jayasoft.org/ + + Index: test/repositories/parentPom/org/apache/dm/test2/2.0/test2-2.0.jar =================================================================== --- test/repositories/parentPom/org/apache/dm/test2/2.0/test2-2.0.jar (revision 0) +++ test/repositories/parentPom/org/apache/dm/test2/2.0/test2-2.0.jar (revision 0) @@ -0,0 +1 @@ + Index: test/repositories/parentPom/org/apache/dm/test2/1.0/test2-1.0.pom =================================================================== --- test/repositories/parentPom/org/apache/dm/test2/1.0/test2-1.0.pom (revision 0) +++ test/repositories/parentPom/org/apache/dm/test2/1.0/test2-1.0.pom (revision 0) @@ -0,0 +1,31 @@ + + + + 4.0.0 + org.apache.dm + test2 + Test parsing parent POM + 1.0 + http://ivy.jayasoft.org/ + + Jayasoft + http://www.jayasoft.org/ + + Index: test/repositories/parentPom/org/apache/dm/test2/1.0/test2-1.0.jar =================================================================== --- test/repositories/parentPom/org/apache/dm/test2/1.0/test2-1.0.jar (revision 0) +++ test/repositories/parentPom/org/apache/dm/test2/1.0/test2-1.0.jar (revision 0) @@ -0,0 +1 @@ + Index: test/repositories/parentPom/org/apache/dm/test3/2.0/test3-2.0.pom =================================================================== --- test/repositories/parentPom/org/apache/dm/test3/2.0/test3-2.0.pom (revision 0) +++ test/repositories/parentPom/org/apache/dm/test3/2.0/test3-2.0.pom (revision 0) @@ -0,0 +1,31 @@ + + + + 4.0.0 + org.apache.dm + test3 + Test parsing parent POM + 2.0 + http://ivy.jayasoft.org/ + + Jayasoft + http://www.jayasoft.org/ + + Index: test/repositories/parentPom/org/apache/dm/test3/2.0/test3-2.0.jar =================================================================== --- test/repositories/parentPom/org/apache/dm/test3/2.0/test3-2.0.jar (revision 0) +++ test/repositories/parentPom/org/apache/dm/test3/2.0/test3-2.0.jar (revision 0) @@ -0,0 +1 @@ + Index: test/repositories/parentPom/org/apache/dm/test3/1.0/test3-1.0.pom =================================================================== --- test/repositories/parentPom/org/apache/dm/test3/1.0/test3-1.0.pom (revision 0) +++ test/repositories/parentPom/org/apache/dm/test3/1.0/test3-1.0.pom (revision 0) @@ -0,0 +1,31 @@ + + + + 4.0.0 + org.apache.dm + test3 + Test parsing parent POM + 1.0 + http://ivy.jayasoft.org/ + + Jayasoft + http://www.jayasoft.org/ + + Index: test/repositories/parentPom/org/apache/dm/test3/1.0/test3-1.0.jar =================================================================== --- test/repositories/parentPom/org/apache/dm/test3/1.0/test3-1.0.jar (revision 0) +++ test/repositories/parentPom/org/apache/dm/test3/1.0/test3-1.0.jar (revision 0) @@ -0,0 +1 @@ + Index: test/repositories/parentPom/org/apache/dm/parent/2.0/parent-2.0.pom =================================================================== --- test/repositories/parentPom/org/apache/dm/parent/2.0/parent-2.0.pom (revision 0) +++ test/repositories/parentPom/org/apache/dm/parent/2.0/parent-2.0.pom (revision 0) @@ -0,0 +1,40 @@ + + + + 4.0.0 + + parent2 + org.apache.dm + 1.0 + + org.apache.dm + parent + Test parsing parent POM + 2.0 + + + + org.apache.dm + test2 + ${pom.version} + + + + Index: test/repositories/parentPom/org/apache/dm/parent2/1.0/parent2-1.0.pom =================================================================== --- test/repositories/parentPom/org/apache/dm/parent2/1.0/parent2-1.0.pom (revision 0) +++ test/repositories/parentPom/org/apache/dm/parent2/1.0/parent2-1.0.pom (revision 0) @@ -0,0 +1,40 @@ + + + + 4.0.0 + org.apache.dm + parent2 + Test parsing parent POM + 1.0 + + + + org.apache.dm + test2 + ${pom.version} + + + org.apache.dm + test3 + 1.0 + + + + Index: test/repositories/parentPom/ivysettings.xml =================================================================== --- test/repositories/parentPom/ivysettings.xml (revision 0) +++ test/repositories/parentPom/ivysettings.xml (revision 0) @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + Index: src/java/org/apache/ivy/plugins/parser/m2/PomModuleDescriptorParser.java =================================================================== --- src/java/org/apache/ivy/plugins/parser/m2/PomModuleDescriptorParser.java (revision 598762) +++ src/java/org/apache/ivy/plugins/parser/m2/PomModuleDescriptorParser.java (working copy) @@ -20,6 +20,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.net.MalformedURLException; import java.net.URL; import java.text.ParseException; import java.util.ArrayList; @@ -28,11 +29,14 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.Stack; import javax.xml.parsers.ParserConfigurationException; +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.Configuration; import org.apache.ivy.core.module.descriptor.DefaultArtifact; import org.apache.ivy.core.module.descriptor.DefaultDependencyArtifactDescriptor; @@ -50,6 +54,12 @@ import org.apache.ivy.plugins.parser.ParserSettings; import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorWriter; import org.apache.ivy.plugins.repository.Resource; +import org.apache.ivy.plugins.repository.url.URLResource; +import org.apache.ivy.plugins.resolver.AbstractResolver; +import org.apache.ivy.plugins.resolver.AbstractResourceResolver; +import org.apache.ivy.plugins.resolver.ChainResolver; +import org.apache.ivy.plugins.resolver.DependencyResolver; +import org.apache.ivy.plugins.resolver.DualResolver; import org.apache.ivy.util.Message; import org.apache.ivy.util.XMLHelper; import org.xml.sax.Attributes; @@ -115,6 +125,8 @@ private static final Map MAVEN2_CONF_MAPPING = new HashMap(); + private static Map PARENT_PROPERTIES = new HashMap(); + static { MAVEN2_CONF_MAPPING.put("compile", "compile->@(*),master(*);runtime->@(*)"); MAVEN2_CONF_MAPPING @@ -158,6 +170,16 @@ private String relocationModule; private String relocationRevision; + + private String dmGroupId; + + private String dmArtifactId; + + private String dmVersion; + + private static final String DEPENDENCY_MANAGEMENT = "dependency.management"; + + private static final String DEPENDENCY_MANAGEMENT_DELIMITER = "__"; public Parser(ModuleDescriptorParser parser, Resource res) { super(parser); @@ -229,9 +251,19 @@ fillMrid(); } else if ("project/parent/version".equals(context)) { properties.put("parent.version", revision); - } else if (((organisation != null && module != null && revision != null) || dd != null) + } else if (((organisation != null && module != null) || dd != null) && "project/dependencies/dependency".equals(context)) { + if ( revision == null ) { + // if the revision is null, see if we can get it from the dependency management + String key = DEPENDENCY_MANAGEMENT + DEPENDENCY_MANAGEMENT_DELIMITER + organisation + + DEPENDENCY_MANAGEMENT_DELIMITER + module; + revision = (String) properties.get(key); + } if (dd == null) { + // if we still don't have revision, then we are done. + if ( revision == null ) { + return; + } dd = new DefaultDependencyDescriptor(md, ModuleRevisionId.newInstance( organisation, module, revision), true, false, true); } @@ -307,6 +339,18 @@ md.addDependency(dd); dd = null; } + else if ("project/dependencyManagement/dependencies/dependency".equals(context)) { + if ( dmGroupId != null && dmArtifactId != null && dmVersion != null ) { + // Note: we can't use substitute pattern, fillMrid has not been called yet. + String key = DEPENDENCY_MANAGEMENT + DEPENDENCY_MANAGEMENT_DELIMITER + dmGroupId + + DEPENDENCY_MANAGEMENT_DELIMITER + dmArtifactId; + properties.put(key, dmVersion); + } + } + if (context.equals("project/parent")) { + // walk up the parent chain building dependencyManagement + parseParentPOM(); + } if ("project/dependencies/dependency".equals(context)) { organisation = null; module = null; @@ -332,6 +376,7 @@ String context = getContext(); if (context.equals("project/parent/groupId") && organisation == null) { organisation = txt; + properties.put("parent.groupId", txt); return; } if (context.equals("project/parent/version") && revision == null) { @@ -343,6 +388,12 @@ ext = txt; return; } + if (context.equals("project/parent/artifactId")) { + if ( properties.get("parent.artifactId") == null ) { + properties.put("parent.artifactId", txt); + } + return; + } if (context.equals("project/distributionManagement/relocation/groupId")) { relocationOrganisation= txt; return; @@ -358,6 +409,18 @@ if (context.startsWith("project/parent")) { return; } + if (context.equals("project/dependencyManagement/dependencies/dependency/groupId")) { + dmGroupId = txt; + return; + } + if (context.equals("project/dependencyManagement/dependencies/dependency/artifactId")) { + dmArtifactId = txt; + return; + } + if (context.equals("project/dependencyManagement/dependencies/dependency/version")) { + dmVersion = txt; + return; + } if (md.getModuleRevisionId() == null || context.startsWith("project/dependencies/dependency")) { if (context.equals("project/groupId")) { @@ -408,6 +471,187 @@ } return md; } + + public Map getProperties() { + return properties; + } + + public void setProperties(Map properties) { + this.properties = properties; + } + + /** + * Parse the parent POM and build the properties. Note that this will walk the parent chain and + * aggregate the results. + * + * Note: A failure parsing the parent POM does not fail the parse + * + */ + protected void parseParentPOM() { + String parentOrg = (String) properties.get("parent.groupId"); + String parentName = (String) properties.get("parent.artifactId"); + String parentVersion = (String) properties.get("parent.version"); + if ( parentOrg != null && parentName != null && parentVersion != null ) { + ModuleRevisionId mrid = ModuleRevisionId.newInstance(parentOrg, parentName, + parentVersion); + // get the list of Urls to try to find the parent POM + List pomUris = getPomUrls( mrid); + // default to findFirst = true, we could get this setting from the resolvers but with chains + // not sure where to look + boolean findFirst = true; + boolean found = false; + for ( int i = 0; i < pomUris.size(); i++ ) { + String pomUri = (String) pomUris.get(i); + // see if we already have built the parent properties + Map parentProps = getParentProperties( pomUri ); + if ( parentProps == null ) { + parentProps = new HashMap(); + try { + URL descriptorURL = new URL(pomUri); + URLResource res = new URLResource( descriptorURL); + PomModuleDescriptorParser parser = PomModuleDescriptorParser.getInstance(); + parser.parseDescriptor(descriptorURL, res, parentProps); + // if we are successful save the parent properties + storeParentProperties( pomUri, parentProps ); + found = true; + } + catch ( MalformedURLException ex ) { + // try the next pomUri + } + catch ( IOException ex ) { + // not all of the URLs are valid. We most likely have a list of uris to try + } + catch ( ParseException ex ) { + // do we want to try another pomUri? + } + } + else { + found = true; + } + if ( found ) { + // move the parent properties into ours + Set keys = parentProps.keySet(); + for ( Iterator iter = keys.iterator(); iter.hasNext(); ) { + String key = iter.next().toString(); + if ( key.startsWith("pom")) { + // don't see a need to copy pom values from parent... + // ignore + } + else if ( key.startsWith("parent")) { + // don't see a need to copy parent values from parent... + // ignore + } + else if ( key.startsWith(DEPENDENCY_MANAGEMENT)) { + // the key may need the groupId substituted + String _key = IvyPatternHelper.substituteVariables(key, + parentProps).trim(); + String _value = IvyPatternHelper.substituteVariables(parentProps.get(key).toString(), + parentProps).trim(); + properties.put(_key, _value); + } + } + if ( findFirst ) { + // if the properties are found then stop + break; + } + } + } + if ( !found ) { + // if none of the pomUris worked to find the parent Pom, should an exception be thrown? + } + } + } + + /** + * Save the parent pom properties, a simple memory cache + * + * @param pomUri + * @return + */ + protected Map getParentProperties(String pomUri) { + return (Map) PARENT_PROPERTIES.get(pomUri); + } + + /** + * Retrieve the parent pom properties if they exist + * + * @param pomUri + * @param parentProps the parent properties, can return NULL + */ + protected void storeParentProperties(String pomUri, Map parentProps) { + PARENT_PROPERTIES.put(pomUri, parentProps); + } + + /** + * Walk the resolves and build a list of Urls to try to find the parent POM + * + * Note: Currently supports AbstraceResourceResolver and will recurse into ChainResolvers + * TODO: What about dual resolvers. + * + * @param parent + * @return list of Urls + */ + protected List getPomUrls(ModuleRevisionId parent) { + List result = new ArrayList(); + // need to build url for parent + IvyContext context = IvyContext.getContext(); + DependencyResolver resolver = context.getSettings().getResolver(parent.getModuleId()); + + ModuleRevisionId mrid = ModuleRevisionId.newInstance(parent.getOrganisation(), + parent.getName(), parent.getRevision()); + + if ( resolver instanceof AbstractResolver ) { + getUrls(mrid, (AbstractResolver) resolver, result); + } + return result; + } + + private void getUrls( ModuleRevisionId mrid, AbstractResolver resolver, List urls ) { + if ( resolver instanceof AbstractResourceResolver ) { + if (((AbstractResourceResolver)resolver).isM2compatible()) { + mrid = convertM2IdForResourceSearch(mrid); + } + Artifact artifact = DefaultArtifact.newPomArtifact(mrid, new Date()); + List patterns = ((AbstractResourceResolver) resolver).getArtifactPatterns(); + for ( int i = 0; i < patterns.size(); i++ ) { + Object pattern = patterns.get(i); + String updatedPattern = IvyPatternHelper.substitute((String) pattern, mrid, artifact); + try { + + URL url = new URL(updatedPattern); + urls.add(url.toString()); + } + catch ( MalformedURLException ex ) { + // is there a better way to determine pattern is a file? + File f = new File(updatedPattern); + urls.add(f.toURI().toString()); + + } + } + } + else if ( resolver instanceof ChainResolver ) { + ChainResolver chain = ( ChainResolver) resolver; + List resolvers = chain.getResolvers(); + for ( int i = 0; i < resolvers.size(); i++ ) { + getUrls(mrid, (AbstractResolver) resolvers.get(i), urls); + } + } + else if ( resolver instanceof DualResolver ) { + DependencyResolver depResolver = ((DualResolver) resolver).getArtifactResolver(); + if ( depResolver instanceof AbstractResolver ) { + getUrls( mrid, (AbstractResolver) depResolver, urls); + } + } + } + + + public static ModuleRevisionId convertM2IdForResourceSearch(ModuleRevisionId mrid) { + if (mrid.getOrganisation().indexOf('.') == -1) { + return mrid; + } + return ModuleRevisionId.newInstance(mrid.getOrganisation().replace('.', '/'), mrid + .getName(), mrid.getBranch(), mrid.getRevision(), mrid.getExtraAttributes()); + } } private static final PomModuleDescriptorParser INSTANCE = new PomModuleDescriptorParser(); @@ -420,6 +664,34 @@ } + /** + * This signature is used to walk the parent POMs and parse the attributes. The interesting attributes + * can then be obtained from the supplied properties map. + * + * @param descriptorURL + * @param res + * @param properties + * @return + * @throws ParseException + * @throws IOException + */ + public void parseDescriptor(URL descriptorURL, Resource res, Map properties ) throws ParseException, IOException { + Parser parser = new Parser(this, res); + parser.setProperties(properties); + try { + XMLHelper.parse(descriptorURL, null, parser); + } catch (SAXException ex) { + ParseException pe = new ParseException(ex.getMessage() + " in " + descriptorURL, 0); + pe.initCause(ex); + throw pe; + } catch (ParserConfigurationException ex) { + IllegalStateException ise = new IllegalStateException(ex.getMessage() + " in " + + descriptorURL); + ise.initCause(ex); + throw ise; + } + } + public ModuleDescriptor parseDescriptor(ParserSettings settings, URL descriptorURL, Resource res, boolean validate) throws ParseException, IOException { Parser parser = new Parser(this, res);