Merge pull request #43 from apache/issues/11734

SLING-11734 : ContentPackageScanner does not extract the right artifa…
diff --git a/src/main/java/org/apache/sling/feature/scanner/impl/ContentPackageScanner.java b/src/main/java/org/apache/sling/feature/scanner/impl/ContentPackageScanner.java
index 4b6f0b3..045b53a 100644
--- a/src/main/java/org/apache/sling/feature/scanner/impl/ContentPackageScanner.java
+++ b/src/main/java/org/apache/sling/feature/scanner/impl/ContentPackageScanner.java
@@ -18,10 +18,8 @@
 
 import java.io.File;
 import java.io.FileOutputStream;
-import java.io.FileReader;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.Reader;
 import java.net.URL;
 import java.nio.file.Files;
 import java.util.ArrayList;
@@ -214,7 +212,7 @@
                                     // ignore
                                 }
 
-                                final Artifact bundle = new Artifact(extractArtifactId(tempDir, newFile));
+                                final Artifact bundle = new Artifact(extractArtifactId(packageArtifact.getId(), newFile));
                                 bundle.setStartOrder(startLevel);
                                 final BundleDescriptor info = new BundleDescriptorImpl(bundle, newFile.toURI().toURL(),
                                         startLevel);
@@ -286,13 +284,10 @@
         }
     }
 
-    private ArtifactId extractArtifactId(final File tempDir, final File bundleFile)
-    throws IOException {
+    final List<Properties> getInitialCandidates(final File bundleFile)  throws IOException{
         logger.debug("Extracting Bundle {}", bundleFile.getName());
 
-        final File toDir = new File(tempDir, bundleFile.getName());
-        toDir.mkdirs();
-
+        final List<Properties> candidates = new ArrayList<>();
         try (final JarFile zipFile = new JarFile(bundleFile)) {
             Enumeration<? extends ZipEntry> entries = zipFile.entries();
 
@@ -302,79 +297,108 @@
                 final String entryName = entry.getName();
                 if ( !entryName.endsWith("/") && entryName.startsWith("META-INF/maven/") && entryName.endsWith("/pom.properties")) {
                     logger.debug("- extracting : {}", entryName);
-                    final File newFile = new File(toDir, entryName.replace('/', File.separatorChar));
-                    newFile.getParentFile().mkdirs();
 
-                    try (
-                            final FileOutputStream fos = new FileOutputStream(newFile);
-                            final InputStream zis = zipFile.getInputStream(entry)
-                    ) {
-                        int len;
-                        while ((len = zis.read(buffer)) > -1) {
-                            fos.write(buffer, 0, len);
-                        }
+                    final Properties props = new Properties();
+                    try (final InputStream zis = zipFile.getInputStream(entry)) {
+                        props.load(zis);
                     }
+                    candidates.add(props);
                 }
             }
         }
+        return candidates;
+    }
 
-        // check for maven
+    private String adjustVersion(final String version) {
+        final String parts[] = version.split("\\.");
+        if ( parts.length == 4 ) {
+            final int lastDot = version.lastIndexOf('.');
+            return version.substring(0, lastDot) + '-' + version.substring(lastDot + 1);
+        }
+        return version;
+    }
 
-        final File metaInfDir = new File(toDir, "META-INF");
-        if ( metaInfDir.exists() ) {
-            final File mavenDir = new File(metaInfDir, "maven");
-            if ( mavenDir.exists() ) {
-                File groupDir = null;
-                for(final File d : mavenDir.listFiles()) {
-                    if ( d.isDirectory() && !d.getName().startsWith(".") ) {
-                        groupDir = d;
-                        break;
-                    }
-                }
-                if ( groupDir != null ) {
-                    File artifactDir = null;
-                    for(final File d : groupDir.listFiles()) {
-                        if ( d.isDirectory() && !d.getName().startsWith(".") ) {
-                            artifactDir = d;
-                            break;
-                        }
-                    }
-                    if ( artifactDir != null ) {
-                        final File propsFile = new File(artifactDir, "pom.properties");
-                        if ( propsFile.exists() ) {
-                            final Properties props = new Properties();
-                            try ( final Reader r = new FileReader(propsFile) ) {
-                                props.load(r);
-                            }
-                            String groupId = props.getProperty("groupId");
-                            String artifactId = props.getProperty("artifactId");
-                            String version = props.getProperty("version");
-                            String classifier = null;
-
-                            // Capture classifier
-                            final int pos = bundleFile.getName().indexOf(version) + version.length();
-                            if ( bundleFile.getName().charAt(pos) == '-') {
-                                classifier = bundleFile.getName().substring(pos + 1, bundleFile.getName().lastIndexOf('.'));
-                            }
-
-                            final String parts[] = version.split("\\.");
-                            if ( parts.length == 4 ) {
-                                final int lastDot = version.lastIndexOf('.');
-                                version = version.substring(0, lastDot) + '-' + version.substring(lastDot + 1);
-                            }
-
-                            if ( groupId != null && artifactId != null && version != null ) {
-                                return new ArtifactId(groupId,
-                                        artifactId,
-                                        version, classifier, null);
-                            }
-                        }
-                    }
-                }
+    private ArtifactId adjustClassifier(ArtifactId id, final String bundleFileName) {
+        // check for classifier
+        final int versionStart = bundleFileName.indexOf(id.getVersion());
+        if ( versionStart != -1 ) {
+            // capture classifier
+            final int versionEnd = versionStart + id.getVersion().length();
+            if ( bundleFileName.length() > versionEnd && bundleFileName.charAt(versionEnd) == '-') {
+                id = id.changeClassifier(bundleFileName.substring(versionEnd + 1, bundleFileName.lastIndexOf('.')));
             }
         }
+        return id;
+    }
 
-        throw new IOException(bundleFile.getName() + " has no maven coordinates!");
+    private ArtifactId extractArtifactId(final ArtifactId packageArtifactId, final File bundleFile)
+    throws IOException {
+        final List<Properties> candidates = this.getInitialCandidates(bundleFile);
+        return extractArtifactId(candidates, bundleFile.getName(), packageArtifactId);
+    }
+
+    private List<ArtifactId> getArtifactIds(final List<Properties> candidates) {
+        final List<ArtifactId> idCandidates = new ArrayList<>();
+        for(final Properties props : candidates) {
+            final String version = props.getProperty("version");
+            final String groupId = props.getProperty("groupId");
+            final String artifactId = props.getProperty("artifactId");
+
+            if ( version != null && groupId != null && artifactId != null ) {
+                idCandidates.add(new ArtifactId(groupId, artifactId, adjustVersion(version), null, null));
+            }
+        }
+        return idCandidates;
+    }
+
+    private List<ArtifactId> filterCandidatesByVersion(final List<ArtifactId> ids, final String bundleFileName) {
+        final List<ArtifactId> idCandidates = new ArrayList<>();
+        for(final ArtifactId id : ids) {
+            final int versionStart = bundleFileName.indexOf(id.getVersion());
+            if ( versionStart != -1 ) {
+                idCandidates.add(id);
+            }
+        }
+        return idCandidates;
+    }
+
+    ArtifactId extractArtifactId(final List<Properties> candidates, final String bundleFileName, final ArtifactId packageArtifactId)
+    throws IOException {
+        logger.debug("Properties candidates for {} : {}", bundleFileName, candidates);
+
+        final List<ArtifactId> idCandidates = getArtifactIds(candidates);
+        logger.debug("Artifact candidates for {} : {}", bundleFileName, candidates);
+
+        // single candidate? return
+        if ( idCandidates.size() == 1 ) {
+            final ArtifactId result = adjustClassifier(idCandidates.get(0), bundleFileName);
+            logger.debug("Found single candidate : {}", result);
+            return result;
+        }
+
+        // more than one candidate, find matching version
+        final List<ArtifactId> versionIds = filterCandidatesByVersion(idCandidates, bundleFileName);
+        if ( versionIds.size() == 1 ) {
+            final ArtifactId result = adjustClassifier(versionIds.get(0), bundleFileName);
+            logger.debug("Found single candidate matching version : {}", result);
+            return result;
+        }
+        // check parent group id
+        for(final ArtifactId id : versionIds.isEmpty() ? idCandidates : versionIds) {
+            if ( id.getGroupId().equals(packageArtifactId.getGroupId()) ) {
+                final ArtifactId result = adjustClassifier(id, bundleFileName);
+                logger.debug("Found candidate with parent group id {} : {}",packageArtifactId.getGroupId(), result);
+                return result;
+            }
+        }
+        // randomly pick one
+        if ( idCandidates.size() > 0 ) {
+            final ArtifactId result = adjustClassifier(idCandidates.get(0), bundleFileName);
+            logger.debug("Picking random candidate : {}", result);
+            return result;
+        }
+
+        throw new IOException(bundleFileName + " has no maven coordinates!");
     }
 
     Configuration processConfiguration(final File configFile,
diff --git a/src/test/java/org/apache/sling/feature/scanner/impl/ContentPackageScannerTest.java b/src/test/java/org/apache/sling/feature/scanner/impl/ContentPackageScannerTest.java
index 6da6a09..dcbc26d 100644
--- a/src/test/java/org/apache/sling/feature/scanner/impl/ContentPackageScannerTest.java
+++ b/src/test/java/org/apache/sling/feature/scanner/impl/ContentPackageScannerTest.java
@@ -26,7 +26,10 @@
 import java.io.IOException;
 import java.net.URISyntaxException;
 import java.net.URL;
+import java.util.ArrayList;
 import java.util.Dictionary;
+import java.util.List;
+import java.util.Properties;
 import java.util.Set;
 
 import org.apache.sling.feature.Artifact;
@@ -56,6 +59,74 @@
             assertNotNull(desc.getManifest());
         }
     }
+    
+    @Test
+    public void testMultipleMavenPropertyDirectoryPicking() throws URISyntaxException, IOException {
+        // this test case is to cover where we have multiple pom.properties files present in our package.
+        final File file = getTestFile("/test-content-felix-bundle-multi-maven-properties.zip");
+
+        final String COORDINATES_TEST_PACKAGE_A_10 = "org.apache.felix:org.apache.felix.framework:6.0.1";
+        final ArtifactId TEST_PACKAGE_AID_A_10 = ArtifactId.fromMvnId(COORDINATES_TEST_PACKAGE_A_10);
+
+        ContentPackageScanner scanner = new ContentPackageScanner();
+        Set<ContentPackageDescriptorImpl> descriptors = scanner.scan(new Artifact(TEST_PACKAGE_AID_A_10), file.toURI().toURL());
+        
+        for(ContentPackageDescriptorImpl descriptor: descriptors){
+            if(descriptor.getName().equals("test-content-felix-bundle-multi-maven-properties")){
+                assertEquals("org.apache.felix:org.apache.felix.framework:6.0.1", descriptor.getBundles().get(0).getName());
+            }
+       
+        }
+    }
+
+    @Test
+    public void testMultipleMavenPropertiesMatchingParent() throws IOException {
+        final ContentPackageScanner scanner = new ContentPackageScanner();
+        final List<Properties> candidates = new ArrayList<>();
+        final Properties guava = new Properties();
+        guava.put("groupId", "com.google.guava");
+        guava.put("artifactId", "failureaccess");
+        guava.put("version", "14.0");
+        candidates.add(guava);
+        final Properties acs = new Properties();
+        acs.put("groupId", "com.adobe.acs");
+        acs.put("artifactId", "acs-aem-commons-bundle");
+        acs.put("version", "5.3.7");
+        candidates.add(acs);
+        final ArtifactId id = scanner.extractArtifactId(candidates, "acs-aem-commons-bundle-5.3.4.jar", ArtifactId.parse("com.adobe.acs:acs-aem-commons-content:zip:5.4.3.zip"));        // xample we are extracting com.acs.aem.acs-aem-commons-content-5.4.3.zip > acs-aem-commons-bundle-5.3.4.jar
+        assertEquals(ArtifactId.parse("com.adobe.acs:acs-aem-commons-bundle:5.3.7"), id);
+    }
+
+    @Test
+    public void testMavenPropertiesClassifier() throws IOException {
+        final ContentPackageScanner scanner = new ContentPackageScanner();
+        final List<Properties> candidates = new ArrayList<>();
+        final Properties acs = new Properties();
+        acs.put("groupId", "com.adobe.acs");
+        acs.put("artifactId", "acs-aem-commons-bundle");
+        acs.put("version", "5.3.4");
+        candidates.add(acs);
+        final ArtifactId id = scanner.extractArtifactId(candidates, "acs-aem-commons-bundle-5.3.4-test.jar", ArtifactId.parse("com.adobe.acs:acs-aem-commons-content:zip:5.4.3.zip"));        // xample we are extracting com.acs.aem.acs-aem-commons-content-5.4.3.zip > acs-aem-commons-bundle-5.3.4.jar
+        assertEquals(ArtifactId.parse("com.adobe.acs:acs-aem-commons-bundle:jar:test:5.3.4"), id);
+    }
+
+    @Test
+    public void testMultipleMavenPropertiesMatchingVersion() throws IOException {
+        final ContentPackageScanner scanner = new ContentPackageScanner();
+        final List<Properties> candidates = new ArrayList<>();
+        final Properties guava = new Properties();
+        guava.put("groupId", "com.google.guava");
+        guava.put("artifactId", "failureaccess");
+        guava.put("version", "14.0");
+        candidates.add(guava);
+        final Properties acs = new Properties();
+        acs.put("groupId", "com.adobe.acs");
+        acs.put("artifactId", "acs-aem-commons-bundle");
+        acs.put("version", "5.3.4");
+        candidates.add(acs);
+        final ArtifactId id = scanner.extractArtifactId(candidates, "acs-aem-commons-bundle-5.3.4.jar", ArtifactId.parse("something:acs-aem-commons-content:zip:5.4.3.zip"));        // xample we are extracting com.acs.aem.acs-aem-commons-content-5.4.3.zip > acs-aem-commons-bundle-5.3.4.jar
+        assertEquals(ArtifactId.parse("com.adobe.acs:acs-aem-commons-bundle:5.3.4"), id);
+    }
 
     private File getTestFile(String path) throws URISyntaxException {
         return new File(getClass().getResource(path).toURI());
@@ -68,7 +139,7 @@
 
         assertEquals(1, desc.getBundles().size());
 
-        assertEquals(desc.getBundles().get(0).getArtifact().getId().toString(), "org.apache.felix:org.apache.felix.framework:jar:bundle:6.0.1");
+        assertEquals(desc.getBundles().get(0).getArtifact().getId(), ArtifactId.parse("org.apache.felix:org.apache.felix.framework:6.0.1"));
         assertEquals("artifact start order",20, desc.getBundles().get(0).getArtifact().getStartOrder());
 
         assertEquals(1, desc.getConfigurations().size());
diff --git a/src/test/resources/test-content-felix-bundle-multi-maven-properties.zip b/src/test/resources/test-content-felix-bundle-multi-maven-properties.zip
new file mode 100644
index 0000000..053b2ab
--- /dev/null
+++ b/src/test/resources/test-content-felix-bundle-multi-maven-properties.zip
Binary files differ