Merge pull request #16 from apache/SLING-8251_merged_after_releases

SLING-8251 - Support checking dependencies for content packages
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 cea38b5..09f7781 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
@@ -19,25 +19,27 @@
 import org.apache.sling.feature.Artifact;
 import org.apache.sling.feature.ArtifactId;
 import org.apache.sling.feature.Configuration;
+import org.apache.sling.feature.io.IOUtils;
 import org.apache.sling.feature.scanner.BundleDescriptor;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.File;
-import java.io.FileInputStream;
 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;
+import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Properties;
 import java.util.Set;
+import java.util.jar.JarFile;
 import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
 
 public class ContentPackageScanner {
 
@@ -81,128 +83,129 @@
 
             final List<File> toProcess = new ArrayList<>();
 
-            try (final ZipInputStream zis = new ZipInputStream(archive.openStream()) ) {
-                boolean done = false;
-                while ( !done ) {
-                    final ZipEntry entry = zis.getNextEntry();
-                    if ( entry == null ) {
-                        done = true;
-                    } else {
-                        final String entryName = entry.getName();
-                        if ( !entryName.endsWith("/") && entryName.startsWith("jcr_root/") ) {
-                            final String contentPath = entryName.substring(8);
+            try (final JarFile zipFile = IOUtils.getJarFileFromURL(archive, true, null)) {
+                Enumeration<? extends ZipEntry> entries = zipFile.entries();
+                while (entries.hasMoreElements()) {
+                    final ZipEntry entry = entries.nextElement();
+                    final String entryName = entry.getName();
+                    logger.debug("Content package entry {}", entryName);
+                    
+                    if ( !entryName.endsWith("/") && entryName.startsWith("jcr_root/") ) {
+                        final String contentPath = entryName.substring(8);
 
-                            FileType fileType = null;
+                        FileType fileType = null;
 
-                            if ( entryName.endsWith(".zip") ) {
-                                // embedded content package
-                                fileType = FileType.PACKAGE;
+                        if (entryName.endsWith(".zip")) {
+                            // embedded content package
+                            fileType = FileType.PACKAGE;
 
-                                // check for libs or apps
-                            } else if ( entryName.startsWith("jcr_root/libs/") || entryName.startsWith("jcr_root/apps/") ) {
+                            // check for libs or apps
+                        } else if (entryName.startsWith("jcr_root/libs/") || entryName.startsWith("jcr_root/apps/")) {
 
-                                // check if this is an install folder (I)
-                                // install folders are either named:
-                                // "install" or
-                                // "install.{runmode}"
-                                boolean isInstall = entryName.indexOf("/install/") != -1;
-                                if ( !isInstall ) {
-                                    final int pos = entryName.indexOf("/install.");
-                                    if ( pos != -1 ) {
+                            // check if this is an install folder (I)
+                            // install folders are either named:
+                            // "install" or
+                            // "install.{runmode}"
+                            boolean isInstall = entryName.indexOf("/install/") != -1;
+                            if (!isInstall) {
+                                final int pos = entryName.indexOf("/install.");
+                                if (pos != -1) {
+                                    final int endSlashPos = entryName.indexOf('/', pos + 1);
+                                    if (endSlashPos != -1) {
+                                        isInstall = true;
+                                    }
+                                }
+                            }
+                            if (!isInstall) {
+                                // check if this is an install folder (II)
+                                // config folders are either named:
+                                // "config" or
+                                // "config.{runmode}"
+                                isInstall = entryName.indexOf("/config/") != -1;
+                                if (!isInstall) {
+                                    final int pos = entryName.indexOf("/config.");
+                                    if (pos != -1) {
                                         final int endSlashPos = entryName.indexOf('/', pos + 1);
-                                        if ( endSlashPos != -1 ) {
+                                        if (endSlashPos != -1) {
                                             isInstall = true;
                                         }
                                     }
                                 }
-                                if ( !isInstall ) {
-                                    // check if this is an install folder (II)
-                                    // config folders are either named:
-                                    // "config" or
-                                    // "config.{runmode}"
-                                    isInstall = entryName.indexOf("/config/") != -1;
-                                    if ( !isInstall ) {
-                                        final int pos = entryName.indexOf("/config.");
-                                        if ( pos != -1 ) {
-                                            final int endSlashPos = entryName.indexOf('/', pos + 1);
-                                            if ( endSlashPos != -1 ) {
-                                                isInstall = true;
-                                            }
-                                        }
-                                    }
-                                }
-
-                                if (isInstall ) {
-
-                                   if ( entryName.endsWith(".jar") ) {
-                                       fileType = FileType.BUNDLE;
-                                   } else if ( entryName.endsWith(".xml") || entryName.endsWith(".config") ) {
-                                       fileType = FileType.CONFIG;
-                                   }
-                                }
                             }
 
-                            if ( fileType != null ) {
-                                logger.debug("- extracting : {}", entryName);
-                                final File newFile = new File(toDir, entryName.replace('/', File.separatorChar));
-                                newFile.getParentFile().mkdirs();
+                            if (isInstall) {
 
-                                try (final FileOutputStream fos = new FileOutputStream(newFile)) {
-                                    int len;
-                                    while ((len = zis.read(buffer)) > -1) {
-                                        fos.write(buffer, 0, len);
-                                    }
+                                if (entryName.endsWith(".jar")) {
+                                    fileType = FileType.BUNDLE;
+                                } else if (entryName.endsWith(".xml") || entryName.endsWith(".config")) {
+                                    fileType = FileType.CONFIG;
+                                }
+                            }
+                        }
+
+                        if (fileType != null) {
+                            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);
+                                }
+                            } 
+
+                            if (fileType == FileType.BUNDLE) {
+                                int startLevel = 20;
+                                final int lastSlash = contentPath.lastIndexOf('/');
+                                final int nextSlash = contentPath.lastIndexOf('/', lastSlash - 1);
+                                final String part = contentPath.substring(nextSlash + 1, lastSlash);
+                                try {
+                                    startLevel = Integer.valueOf(part);
+                                } catch (final NumberFormatException ignore) {
+                                    // ignore
                                 }
 
-                                if ( fileType == FileType.BUNDLE ) {
-                                    int startLevel = 20;
-                                    final int lastSlash = contentPath.lastIndexOf('/');
-                                    final int nextSlash = contentPath.lastIndexOf('/', lastSlash - 1);
-                                    final String part = contentPath.substring(nextSlash + 1, lastSlash);
-                                    try {
-                                        startLevel = Integer.valueOf(part);
-                                    } catch ( final NumberFormatException ignore ) {
-                                        // ignore
-                                    }
+                                final Artifact bundle = new Artifact(extractArtifactId(tempDir, newFile));
+                                final BundleDescriptor info = new BundleDescriptorImpl(bundle, newFile.toURI().toURL(),
+                                        startLevel);
+                                bundle.getMetadata().put("content-package", cp.getArtifact().getId().toMvnId());
+                                bundle.getMetadata().put("content-path", contentPath);
 
-                                    final Artifact bundle = new Artifact(extractArtifactId(tempDir, newFile));
-                                    final BundleDescriptor info = new BundleDescriptorImpl(bundle, newFile.toURI().toURL(), startLevel);
-                                    bundle.getMetadata().put("content-package", cp.getArtifact().getId().toMvnId());
-                                    bundle.getMetadata().put("content-path", contentPath);
+                                cp.bundles.add(info);
 
-                                    cp.bundles.add(info);
+                            } else if (fileType == FileType.CONFIG) {
 
-                                } else if ( fileType == FileType.CONFIG ) {
+                                final Configuration configEntry = this.process(newFile, cp.getArtifact(), contentPath);
+                                if (configEntry != null) {
 
-                                    final Configuration configEntry = this.process(newFile, cp.getArtifact(), contentPath);
-                                    if ( configEntry != null ) {
-
-                                        cp.configs.add(configEntry);
-                                    }
-
-                                } else if ( fileType == FileType.PACKAGE ) {
-                                    toProcess.add(newFile);
+                                    cp.configs.add(configEntry);
                                 }
 
+                            } else if (fileType == FileType.PACKAGE) {
+                                toProcess.add(newFile);
                             }
 
                         }
-                        zis.closeEntry();
+
                     }
+
                 }
 
-            }
+                for (final File f : toProcess) {
+                    extractContentPackage(cp, infos, f.toURI().toURL());
+                    final ContentPackageDescriptor i = new ContentPackageDescriptor(f.getName());
+                    final int lastDot = f.getName().lastIndexOf(".");
+                    i.setName(f.getName().substring(0, lastDot));
+                    i.setArtifactFile(f.toURI().toURL());
+                    i.setContentPackageInfo(cp.getArtifact(), f.getName());
+                    infos.add(i);
 
-            for(final File f : toProcess) {
-                extractContentPackage(cp, infos, f.toURI().toURL());
-                final ContentPackageDescriptor i = new ContentPackageDescriptor(f.getName());
-                final int lastDot = f.getName().lastIndexOf(".");
-                i.setName(f.getName().substring(0, lastDot));
-                i.setArtifactFile(f.toURI().toURL());
-                i.setContentPackageInfo(cp.getArtifact(), f.getName());
-                infos.add(i);
-
-                i.lock();
+                    i.lock();
+                }
             }
         } finally {
             deleteOnExitRecursive(tempDir);
@@ -228,31 +231,29 @@
         final File toDir = new File(tempDir, bundleFile.getName());
         toDir.mkdirs();
 
-        try (final ZipInputStream zis = new ZipInputStream(new FileInputStream(bundleFile)) ) {
-            boolean done = false;
-            while ( !done ) {
-                final ZipEntry entry = zis.getNextEntry();
-                if ( entry == null ) {
-                    done = true;
-                } else {
-                    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 JarFile zipFile = new JarFile(bundleFile)) {
+            Enumeration<? extends ZipEntry> entries = zipFile.entries();
+            
+            while ( entries.hasMoreElements() ) {
+                final ZipEntry entry = entries.nextElement();
+                
+                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)) {
-                            int len;
-                            while ((len = zis.read(buffer)) > -1) {
-                                fos.write(buffer, 0, len);
-                            }
+                    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);
                         }
-
                     }
-                    zis.closeEntry();
                 }
             }
-
         }
 
         // check for maven
diff --git a/src/main/java/org/apache/sling/feature/scanner/impl/FelixFrameworkScanner.java b/src/main/java/org/apache/sling/feature/scanner/impl/FelixFrameworkScanner.java
index 956a90c..12f7879 100644
--- a/src/main/java/org/apache/sling/feature/scanner/impl/FelixFrameworkScanner.java
+++ b/src/main/java/org/apache/sling/feature/scanner/impl/FelixFrameworkScanner.java
@@ -17,6 +17,22 @@
 package org.apache.sling.feature.scanner.impl;
 
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.Set;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.zip.ZipEntry;
+
 import org.apache.commons.text.StringSubstitutor;
 import org.apache.commons.text.lookup.StringLookup;
 import org.apache.felix.utils.manifest.Parser;
@@ -24,31 +40,15 @@
 import org.apache.sling.feature.Artifact;
 import org.apache.sling.feature.ArtifactId;
 import org.apache.sling.feature.builder.ArtifactProvider;
+import org.apache.sling.feature.io.IOUtils;
 import org.apache.sling.feature.scanner.BundleDescriptor;
 import org.apache.sling.feature.scanner.PackageInfo;
 import org.apache.sling.feature.scanner.spi.FrameworkScanner;
 import org.osgi.framework.Constants;
 import org.osgi.resource.Capability;
 
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.net.URL;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Properties;
-import java.util.Set;
-import java.util.jar.Manifest;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-
 public class FelixFrameworkScanner implements FrameworkScanner {
 
-
     @Override
     public BundleDescriptor scan(final ArtifactId framework,
             final Map<String,String> frameworkProps,
@@ -144,22 +144,20 @@
     Map<String,String> getFrameworkProperties(final Map<String,String> appProps, final URL framework)
     throws IOException {
         final Map<String, Properties> propsMap = new HashMap<>();
-        try (final ZipInputStream zis = new ZipInputStream(framework.openStream()) ) {
-            boolean done = false;
-            while ( !done ) {
-                final ZipEntry entry = zis.getNextEntry();
-                if ( entry == null ) {
-                    done = true;
-                } else {
+
+        try (final JarFile zipFile = IOUtils.getJarFileFromURL(framework, true, null)) {
+            Enumeration<? extends ZipEntry> entries = zipFile.entries();
+            
+            while ( entries.hasMoreElements() ) {
+                final ZipEntry entry = entries.nextElement();
+                try (final InputStream zis = zipFile.getInputStream(entry)) {
                     final String entryName = entry.getName();
                     if ( entryName.endsWith(".properties") ) {
                         final Properties props = new Properties();
                         props.load(zis);
-
                         propsMap.put(entryName, props);
                     }
-                    zis.closeEntry();
-                }
+                } 
             }
         }
 
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
new file mode 100644
index 0000000..fdf23ca
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/scanner/impl/ContentPackageScannerTest.java
@@ -0,0 +1,112 @@
+/*
+ * 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.sling.feature.scanner.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertNotNull;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Configuration;
+import org.apache.sling.feature.scanner.BundleDescriptor;
+import org.junit.Before;
+import org.junit.Test;
+
+public class ContentPackageScannerTest {
+    
+    private static final String COORDINATES_TEST_PACKAGE_A_10 = "my_packages:test_a:1.0";
+    private static ArtifactId TEST_PACKAGE_AID_A_10 = ArtifactId.fromMvnId(COORDINATES_TEST_PACKAGE_A_10);
+
+    private File file;
+    
+    private Artifact artifact;
+    
+    ContentPackageDescriptor test_descriptor;
+    
+    @Before
+    public void setUp() throws Exception {
+        file = getTestFile("/test-content.zip");
+        
+        artifact = new Artifact(TEST_PACKAGE_AID_A_10);
+        
+        test_descriptor = new ContentPackageDescriptor(file.getName());
+        test_descriptor.setName("test-content");
+        test_descriptor.setArtifact(artifact);
+        test_descriptor.setArtifactFile(file.toURI().toURL());
+    }
+    
+    @Test
+    public void testScan() throws URISyntaxException, IOException {
+        ContentPackageScanner scanner = new ContentPackageScanner();
+        Set<ContentPackageDescriptor> descriptors = scanner.scan(artifact, file.toURI().toURL()); 
+        for(ContentPackageDescriptor desc : descriptors) {
+            String name = desc.getName();
+            assertNotNull(name);
+            
+            if(name.equals(test_descriptor.getName())) {
+                assetDescriptor(desc, desc.getName());
+            } else {
+                assertEquals(name, "sub-content");
+            }
+        }
+    }
+    
+    private File getTestFile(String path) throws URISyntaxException {
+        return new File(getClass().getResource(path).toURI());
+    }
+    
+    private void assetDescriptor(ContentPackageDescriptor desc, String descName) {
+        assertEquals(descName, test_descriptor.getName());
+        assertEquals(desc.getArtifact().getId().getArtifactId(), test_descriptor.getArtifact().getId().getArtifactId());
+        assertEquals(desc.getArtifactFile().toString(), test_descriptor.getArtifactFile().toString());
+        
+        assertTrue(desc.bundles != null && !desc.bundles.isEmpty());
+        BundleDescriptor bundles[] = desc.bundles.toArray(new BundleDescriptor[desc.bundles.size()]);
+        
+        assertEquals(bundles[0].getArtifact().getId().toString(), "org.apache.felix:org.apache.felix.framework:jar:bundle:6.0.1");
+        
+        assertTrue(desc.configs != null && !desc.configs.isEmpty());
+        Configuration configs[] = desc.configs.toArray(new Configuration[desc.configs.size()]);
+        assertConfiguration(configs[0]);
+    }
+    
+    private void assertConfiguration(Configuration c) {
+        Dictionary<String, Object> props = c.getProperties();
+        String contentPath = (String) props.get(":configurator:feature:content-path");
+        assertEquals(contentPath, "/libs/config/com.example.some.Component.xml");
+    }
+    
+    private void printPackageEntries(File archive) throws IOException {
+        ZipFile zip = new ZipFile(archive);
+        Enumeration<? extends ZipEntry> entries = zip.entries();
+        System.out.println("ZIP Archive: " + zip.getName());
+        while(entries.hasMoreElements()) 
+            System.out.println("    " + entries.nextElement().getName());
+        System.out.println();
+    }
+    
+}
diff --git a/src/test/resources/test-content.zip b/src/test/resources/test-content.zip
new file mode 100644
index 0000000..672f0fa
--- /dev/null
+++ b/src/test/resources/test-content.zip
Binary files differ