You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by cz...@apache.org on 2021/09/11 14:45:28 UTC

[sling-org-apache-sling-feature-analyser] 01/02: SLING-10803 : Improve content package scanning

This is an automated email from the ASF dual-hosted git repository.

cziegeler pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature-analyser.git

commit 2188d6ce664b8bb8622b5ccb26370df7bd410b6b
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Sat Sep 11 16:42:08 2021 +0200

    SLING-10803 : Improve content package scanning
---
 pom.xml                                            |   1 +
 .../scanner/impl/ArtifactDescriptorImpl.java       |  25 +--
 .../scanner/impl/ContentPackageDescriptor.java     |   9 +-
 .../scanner/impl/ContentPackageScanner.java        | 238 ++++++++++++---------
 .../impl/ContentPackagesExtensionScanner.java      |   2 +-
 .../impl/CheckContentPackagesForPathsTest.java     |   4 +-
 .../scanner/impl/ContentPackageScannerTest.java    | 125 ++++++++---
 src/test/resources/test-config.xml                 |   4 +
 src/test/resources/test-noconfig.xml               |   6 +
 9 files changed, 260 insertions(+), 154 deletions(-)

diff --git a/pom.xml b/pom.xml
index 2771039..10c120d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -54,6 +54,7 @@
                         <exclude>src/main/resources/META-INF/services/org.apache.sling.feature.scanner.spi.FrameworkScanner</exclude>
                         <exclude>src/test/resources/origins/**</exclude>
                         <exclude>src/test/resources/test-bundle-src/**</exclude>
+                        <exclude>src/test/resources/**.xml</exclude>
                     </excludes>
                 </configuration>
             </plugin>
diff --git a/src/main/java/org/apache/sling/feature/scanner/impl/ArtifactDescriptorImpl.java b/src/main/java/org/apache/sling/feature/scanner/impl/ArtifactDescriptorImpl.java
index cbfbdf7..6d0956c 100644
--- a/src/main/java/org/apache/sling/feature/scanner/impl/ArtifactDescriptorImpl.java
+++ b/src/main/java/org/apache/sling/feature/scanner/impl/ArtifactDescriptorImpl.java
@@ -16,7 +16,6 @@
  */
 package org.apache.sling.feature.scanner.impl;
 
-import java.io.IOException;
 import java.net.URL;
 import java.util.jar.Manifest;
 
@@ -43,37 +42,19 @@ public class ArtifactDescriptorImpl
      * @param name Optional name
      * @param artifact The artifact, must be provided
      * @param url Optional url
-     * @param hasManifest Whether that artifact must have a metafest
-     * @param isManifestOptional Whether the manifest is optional
-     * @throws IOException If processing fails
+     * @param Manifest manifest (optional)
      * @throws NullPointerException If artifact is {@code null}
      */
     public ArtifactDescriptorImpl(
             final String name,
             final Artifact artifact,
             final URL url,
-            final boolean hasManifest,
-            final boolean isManifestOptional) throws IOException  {
+            final Manifest manifest) {
         super(name != null ? name : artifact.getId().toMvnId());
         this.artifact = artifact;
         this.artifact.getId(); // throw NPE if artifact is null
         this.artifactFile = url;
-        Manifest mf = null;
-        if ( hasManifest ) {
-            try {
-                final Manifest origMf = url == null ? null : BundleDescriptorImpl.getManifest(url);
-                if ( origMf != null ) {
-                    mf = new Manifest(origMf);
-                } else if ( !isManifestOptional ) {
-                    throw new IOException(url + " : No manifest found.");
-                }
-            } catch ( final IOException ioe) {
-                if ( !isManifestOptional ) {
-                    throw ioe;
-                }
-            }
-        }
-        this.manifest = mf;
+        this.manifest = manifest;
     }
 
     @Override
diff --git a/src/main/java/org/apache/sling/feature/scanner/impl/ContentPackageDescriptor.java b/src/main/java/org/apache/sling/feature/scanner/impl/ContentPackageDescriptor.java
index eae5513..13f1dde 100644
--- a/src/main/java/org/apache/sling/feature/scanner/impl/ContentPackageDescriptor.java
+++ b/src/main/java/org/apache/sling/feature/scanner/impl/ContentPackageDescriptor.java
@@ -16,10 +16,10 @@
  */
 package org.apache.sling.feature.scanner.impl;
 
-import java.io.IOException;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.jar.Manifest;
 
 import org.apache.sling.feature.Artifact;
 import org.apache.sling.feature.Configuration;
@@ -60,13 +60,14 @@ public class ContentPackageDescriptor extends ArtifactDescriptorImpl {
      * @param name The name
      * @param artifact The artifact
      * @param url The url to the binary
-     * @throws IOException If processing fails
+     * @param manifest The manifest (optional)
      * @throws NullPointerException If artifact is {@code null}
      */
     public ContentPackageDescriptor(final String name,
             final Artifact artifact,
-            final URL url) throws IOException  {
-        super(name, artifact, url, true, true);
+            final URL url,
+            final Manifest manifest) {
+        super(name, artifact, url, manifest);
     }
 
     /**
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 268735d..fff0c2a 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
@@ -25,12 +25,14 @@ import java.io.Reader;
 import java.net.URL;
 import java.nio.file.Files;
 import java.util.ArrayList;
+import java.util.Arrays;
 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.jar.Manifest;
 import java.util.zip.ZipEntry;
 
 import org.apache.sling.feature.Artifact;
@@ -50,12 +52,14 @@ public class ContentPackageScanner {
 
     private final byte[] buffer = new byte[65536];
 
-    private enum FileType {
+    enum FileType {
         BUNDLE,
         CONFIG,
-        PACKAGE
+        PACKAGE,
     }
 
+    private static final List<String> CFG_EXTENSIONS = Arrays.asList(".config", ".cfg", ".cfg.json", ".xml");
+
     /**
      * Scan the content package for embedded artifacts
      * @param artifact The content package
@@ -66,97 +70,120 @@ public class ContentPackageScanner {
     public Set<ContentPackageDescriptor> scan(final Artifact artifact, final URL url) throws IOException {
         final Set<ContentPackageDescriptor> contentPackages = new HashSet<>();
         if (url != null) {
-            final int lastDot = url.getPath().lastIndexOf(".");
-            final ContentPackageDescriptor cp = new ContentPackageDescriptor(url.getPath().substring(url.getPath().lastIndexOf("/") + 1, lastDot), artifact, url);
-
-            extractContentPackage(cp, contentPackages, url);
-
-            contentPackages.add(cp);
-            cp.lock();
+            final String path = url.getPath();
+            final int lastDotInUrl = path.lastIndexOf(".");
+            final String name = path.substring(path.lastIndexOf("/") + 1, lastDotInUrl);
+             extractContentPackage(null, null, artifact, name, url, contentPackages);
         }
 
         return contentPackages;
     }
 
-    private void extractContentPackage(final ContentPackageDescriptor cp,
-            final Set<ContentPackageDescriptor> infos,
-            final URL archive)
+    /**
+     * Detect the file type, bundle, configuration or embedded package from the content path
+     * @param contentPath The content path
+     * @return The detected file type or {@code null}
+     */
+    FileType detectContentFileType(final String contentPath) {
+        FileType fileType = null;
+        if (contentPath.endsWith(".zip")) {
+            // embedded content package
+            fileType = FileType.PACKAGE;
+
+            // check for libs or apps
+        } else if (contentPath.startsWith("/libs/") || contentPath.startsWith("/apps/")) {
+
+            // check if this is an install folder (I)
+            // install folders are either named:
+            // "install" or
+            // "install.{runmode}"
+            boolean isInstall = contentPath.indexOf("/install/") != -1;
+            if (!isInstall) {
+                final int pos = contentPath.indexOf("/install.");
+                if (pos != -1) {
+                    final int endSlashPos = contentPath.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 = contentPath.indexOf("/config/") != -1;
+                if (!isInstall) {
+                    final int pos = contentPath.indexOf("/config.");
+                    if (pos != -1) {
+                        final int endSlashPos = contentPath.indexOf('/', pos + 1);
+                        if (endSlashPos != -1) {
+                            isInstall = true;
+                        }
+                    }
+                }
+            }
+
+            if (isInstall) {
+
+                if (contentPath.endsWith(".jar")) {
+                    fileType = FileType.BUNDLE;
+                } else {
+                    for(final String ext : CFG_EXTENSIONS) {
+                        if ( contentPath.endsWith(ext) ) {
+                            fileType = FileType.CONFIG;
+                            break;
+                        }
+                    }
+                } 
+            }
+        }
+        return fileType;
+    }
+
+    private ContentPackageDescriptor extractContentPackage(final ContentPackageDescriptor parentPackage,
+            final String parentContentPath,
+            final Artifact packageArtifact,
+            final String name,
+            final URL archiveUrl,
+            final Set<ContentPackageDescriptor> infos)
     throws IOException {
-        logger.debug("Analyzing Content Package {}", archive);
+        logger.debug("Analyzing Content Package {}", archiveUrl);
 
         final File tempDir = Files.createTempDirectory(null).toFile();
         try {
-            final File toDir = new File(tempDir, archive.getPath().substring(archive.getPath().lastIndexOf("/") + 1));
+            final File toDir = new File(tempDir, archiveUrl.getPath().substring(archiveUrl.getPath().lastIndexOf("/") + 1));
             toDir.mkdirs();
 
+            Manifest manifest = null;
             final List<File> toProcess = new ArrayList<>();
-
-            try (final JarFile zipFile = IOUtils.getJarFileFromURL(archive, true, null)) {
+            final List<String> contentPaths = new ArrayList<>();
+            final List<BundleDescriptor> bundles = new ArrayList<>();
+            final List<Configuration> configs = new ArrayList<>();
+    
+            try (final JarFile zipFile = IOUtils.getJarFileFromURL(archiveUrl, true, null)) {
                 Enumeration<? extends ZipEntry> entries = zipFile.entries();
                 while (entries.hasMoreElements()) {
                     final ZipEntry entry = entries.nextElement();
                     final String entryName = entry.getName();
+
+                    // skip dirs
+                    if ( entryName.endsWith("/") ) {
+                        continue;
+                    }
+                    
                     logger.debug("Content package entry {}", entryName);
 
-                    if ( !entryName.endsWith("/") && entryName.startsWith("jcr_root/") ) {
+                    if ( entryName.startsWith("jcr_root/") ) {
                         final String contentPath = entryName.substring(8);
-                        cp.paths.add(contentPath);
-
-                        FileType fileType = null;
-
-                        if (entryName.endsWith(".zip")) {
-                            // embedded content package
-                            fileType = FileType.PACKAGE;
-
-                            // check for libs or apps
-                        } else if (contentPath.startsWith("/libs/") || contentPath.startsWith("/apps/")) {
-
-                            // check if this is an install folder (I)
-                            // install folders are either named:
-                            // "install" or
-                            // "install.{runmode}"
-                            boolean isInstall = contentPath.indexOf("/install/") != -1;
-                            if (!isInstall) {
-                                final int pos = contentPath.indexOf("/install.");
-                                if (pos != -1) {
-                                    final int endSlashPos = contentPath.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 = contentPath.indexOf("/config/") != -1;
-                                if (!isInstall) {
-                                    final int pos = contentPath.indexOf("/config.");
-                                    if (pos != -1) {
-                                        final int endSlashPos = contentPath.indexOf('/', pos + 1);
-                                        if (endSlashPos != -1) {
-                                            isInstall = true;
-                                        }
-                                    }
-                                }
-                            }
-
-                            if (isInstall) {
-
-                                if (contentPath.endsWith(".jar")) {
-                                    fileType = FileType.BUNDLE;
-                                } else if (contentPath.endsWith(".xml") || contentPath.endsWith(".config") || contentPath.endsWith(".cfg.json")) {
-                                    fileType = FileType.CONFIG;
-                                }
-                            }
-                        }
+                        contentPaths.add(contentPath);
 
+                        final FileType fileType = detectContentFileType(contentPath);
                         if (fileType != null) {
-                            logger.debug("- extracting : {}", entryName);
+                            logger.debug("- extracting : {}", contentPath);
                             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);
@@ -166,7 +193,7 @@ public class ContentPackageScanner {
                                     fos.write(buffer, 0, len);
                                 }
                             }
-
+    
                             if (fileType == FileType.BUNDLE) {
                                 int startLevel = 20;
                                 final int lastSlash = contentPath.lastIndexOf('/');
@@ -177,46 +204,57 @@ public class ContentPackageScanner {
                                 } catch (final NumberFormatException ignore) {
                                     // ignore
                                 }
-
+    
                                 final Artifact bundle = new Artifact(extractArtifactId(tempDir, newFile));
                                 bundle.setStartOrder(startLevel);
                                 final BundleDescriptor info = new BundleDescriptorImpl(bundle, newFile.toURI().toURL(),
                                         startLevel);
                                 bundle.getMetadata().put(ContentPackageDescriptor.METADATA_PACKAGE,
-                                        cp.getArtifact().getId().toMvnId());
+                                        packageArtifact.getId().toMvnId());
                                 bundle.getMetadata().put(ContentPackageDescriptor.METADATA_PATH, contentPath);
-
-                                cp.bundles.add(info);
-
+    
+                                bundles.add(info);
+    
                             } else if (fileType == FileType.CONFIG) {
-
-                                final Configuration configEntry = this.process(newFile, cp.getArtifact(), contentPath);
+    
+                                final Configuration configEntry = this.processConfiguration(newFile, packageArtifact.getId(), contentPath);
                                 if (configEntry != null) {
-
-                                    cp.configs.add(configEntry);
+                                    configs.add(configEntry);
                                 }
-
+    
                             } else if (fileType == FileType.PACKAGE) {
                                 toProcess.add(newFile);
-                            }
-
+                            } 
+                        }
+                    } else if ( entryName.equals("META-INF/MANIFEST.MF") ) {
+                        try ( final InputStream zis = zipFile.getInputStream(entry)) {
+                            manifest = new Manifest(zis);
                         }
-
                     }
+                }
 
+                final ContentPackageDescriptor desc = new ContentPackageDescriptor(name, packageArtifact, archiveUrl, manifest);
+                if ( parentPackage != null ) {
+                    desc.setContentPackageInfo(parentPackage.getArtifact(), parentContentPath);
                 }
+                desc.bundles.addAll(bundles);
+                desc.configs.addAll(configs);
+                desc.paths.addAll(contentPaths);
 
                 for (final File f : toProcess) {
-                    extractContentPackage(cp, infos, f.toURI().toURL());
                     final int lastDot = f.getName().lastIndexOf(".");
+                    final String subName = f.getName().substring(0, lastDot);
+                    final String contentPath = f.getAbsolutePath().substring(toDir.getAbsolutePath().length()).replace(File.separatorChar, '/');
+
                     // create synthetic artifact with a synthetic id containing the file name
-                    final Artifact artifact = new Artifact(cp.getArtifact().getId().changeClassifier(f.getName()));
-                    final ContentPackageDescriptor i = new ContentPackageDescriptor(f.getName().substring(0, lastDot), artifact, f.toURI().toURL());
-                    i.setContentPackageInfo(cp.getArtifact(), f.getName());
-                    infos.add(i);
+                    final Artifact subArtifact = new Artifact(packageArtifact.getId().changeClassifier(subName));
 
-                    i.lock();
+                    extractContentPackage(desc, contentPath, subArtifact, subName, f.toURI().toURL(), infos);
                 }
+
+                infos.add(desc);
+                desc.lock();
+                return desc;
             }
         } finally {
             deleteOnExitRecursive(tempDir);
@@ -326,13 +364,13 @@ public class ContentPackageScanner {
         throw new IOException(bundleFile.getName() + " has no maven coordinates!");
     }
 
-    private Configuration process(final File configFile,
-            final Artifact packageArtifact,
+    Configuration processConfiguration(final File configFile,
+            final ArtifactId packageArtifactId,
             final String contentPath)
     throws IOException {
 
         boolean isConfig = true;
-        if ( configFile.getName().endsWith(".xml") ) {
+        if ( contentPath.endsWith(".xml") ) {
             final String contents = Files.readAllLines(configFile.toPath()).toString();
             if ( contents.indexOf("jcr:primaryType=\"sling:OsgiConfig\"") == -1 ) {
                 isConfig = false;
@@ -341,13 +379,19 @@ public class ContentPackageScanner {
 
         if ( isConfig ) {
             final String id;
-            if ( ".content.xml".equals(configFile.getName()) ) {
+            if ( contentPath.endsWith("/.content.xml") ) {
                 final int lastSlash = contentPath.lastIndexOf('/');
                 final int previousSlash = contentPath.lastIndexOf('/', lastSlash - 1);
                 id = contentPath.substring(previousSlash + 1, lastSlash);
             } else {
-                final int lastDot = configFile.getName().lastIndexOf('.');
-                id = configFile.getName().substring(0, lastDot);
+                String name = contentPath;
+                final int lastSlash = contentPath.lastIndexOf('/');
+                for(final String ext : CFG_EXTENSIONS) {
+                    if ( name.endsWith(ext) ) {
+                        name = name.substring(lastSlash + 1, name.length() - ext.length());
+                    }
+                }
+                id = name;
             }
 
             final String pid;
@@ -361,7 +405,7 @@ public class ContentPackageScanner {
             final Configuration cfg = new Configuration(pid);
             cfg.getProperties().put(Configuration.PROP_PREFIX + ContentPackageDescriptor.METADATA_PATH, contentPath);
             cfg.getProperties().put(Configuration.PROP_PREFIX + ContentPackageDescriptor.METADATA_PACKAGE,
-                    packageArtifact.getId().toMvnId());
+                    packageArtifactId.toMvnId());
 
             return cfg;
         }
diff --git a/src/main/java/org/apache/sling/feature/scanner/impl/ContentPackagesExtensionScanner.java b/src/main/java/org/apache/sling/feature/scanner/impl/ContentPackagesExtensionScanner.java
index 7233cee..e082acc 100644
--- a/src/main/java/org/apache/sling/feature/scanner/impl/ContentPackagesExtensionScanner.java
+++ b/src/main/java/org/apache/sling/feature/scanner/impl/ContentPackagesExtensionScanner.java
@@ -78,7 +78,7 @@ public class ContentPackagesExtensionScanner implements ExtensionScanner {
             }
             else {
                 final int lastDot = a.getId().toMvnPath().lastIndexOf(".");
-                ContentPackageDescriptor desc = new ContentPackageDescriptor(a.getId().toMvnPath().substring(a.getId().toMvnPath().lastIndexOf("/") + 1, lastDot), a, url);
+                ContentPackageDescriptor desc = new ContentPackageDescriptor(a.getId().toMvnPath().substring(a.getId().toMvnPath().lastIndexOf("/") + 1, lastDot), a, url, null);
                 desc.lock();
                 cd.getArtifactDescriptors().add(desc);
             }
diff --git a/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckContentPackagesForPathsTest.java b/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckContentPackagesForPathsTest.java
index a88834a..c88cbbf 100644
--- a/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckContentPackagesForPathsTest.java
+++ b/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckContentPackagesForPathsTest.java
@@ -71,7 +71,7 @@ public class CheckContentPackagesForPathsTest {
 
         final Rules r = analyser.getRules(ctx);
         final ContentPackageDescriptor desc = new ContentPackageDescriptor("name", new Artifact(ArtifactId.parse("g:a:1")),
-            new URL("https://sling.apache.org"));
+            new URL("https://sling.apache.org"), null);
         desc.paths.add("/b/foo");
         desc.paths.add("/a");
         desc.paths.add("/a/foo");
@@ -95,7 +95,7 @@ public class CheckContentPackagesForPathsTest {
 
         final Rules r = analyser.getRules(ctx);
         final ContentPackageDescriptor desc = new ContentPackageDescriptor("name", new Artifact(ArtifactId.parse("g:a:1")),
-            new URL("https://sling.apache.org"));
+            new URL("https://sling.apache.org"), null);
         desc.paths.add("/a/foo");
         desc.paths.add("/a/bar");
 
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 25ba20f..75192f8 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
@@ -18,14 +18,17 @@ package org.apache.sling.feature.scanner.impl;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import java.io.File;
 import java.io.IOException;
 import java.net.URISyntaxException;
+import java.net.URL;
 import java.util.Dictionary;
 import java.util.Set;
 
+import org.apache.felix.utils.repository.UrlLoader;
 import org.apache.sling.feature.Artifact;
 import org.apache.sling.feature.ArtifactId;
 import org.apache.sling.feature.Configuration;
@@ -35,36 +38,22 @@ 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("test-content", artifact, file.toURI().toURL());
-    }
 
     @Test
     public void testScan() throws URISyntaxException, IOException {
+        final File file = getTestFile("/test-content.zip");
+
+        final String COORDINATES_TEST_PACKAGE_A_10 = "my_packages:test_a:1.0";
+        final ArtifactId TEST_PACKAGE_AID_A_10 = ArtifactId.fromMvnId(COORDINATES_TEST_PACKAGE_A_10);
+
         ContentPackageScanner scanner = new ContentPackageScanner();
-        Set<ContentPackageDescriptor> descriptors = scanner.scan(artifact, file.toURI().toURL());
+        Set<ContentPackageDescriptor> descriptors = scanner.scan(new Artifact(TEST_PACKAGE_AID_A_10), file.toURI().toURL());
+        assertEquals(2, descriptors.size());
         for(ContentPackageDescriptor desc : descriptors) {
-            String name = desc.getName();
-            assertNotNull(name);
-
-            if(name.equals(test_descriptor.getName())) {
-                assetDescriptor(desc, desc.getName());
+            if (desc.getName().equals("test-content")) {
+                assetDescriptor(desc, "test-content", TEST_PACKAGE_AID_A_10, file.toURI().toURL());
             } else {
-                assertEquals(name, "sub-content");
+                assertEquals(desc.getName(), "sub-content");
             }
             assertNotNull(desc.getManifest());
         }
@@ -75,10 +64,10 @@ public class ContentPackageScannerTest {
     }
 
     @SuppressWarnings("deprecation")
-    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());
+    private void assetDescriptor(ContentPackageDescriptor desc, String descName, final ArtifactId descArtifactId, final URL descUrl) {
+        assertEquals(descName, desc.getName());
+        assertEquals(descArtifactId, desc.getArtifact().getId());
+        assertEquals(descUrl, desc.getArtifactFile());
 
         assertTrue(desc.bundles != null && !desc.bundles.isEmpty());
         BundleDescriptor bundles[] = desc.bundles.toArray(new BundleDescriptor[desc.bundles.size()]);
@@ -97,4 +86,84 @@ public class ContentPackageScannerTest {
         String contentPath = (String) props.get(":configurator:feature-content-path");
         assertEquals("/libs/config/com.example.some.Component.xml", contentPath);
     }
+
+    @Test
+    public void testDetectContentFileType() {
+        final ContentPackageScanner scanner = new ContentPackageScanner();
+
+        // any file ending in zip is a content package
+        assertEquals(ContentPackageScanner.FileType.PACKAGE, scanner.detectContentFileType("/etc/package/my-package.zip"));
+        assertEquals(ContentPackageScanner.FileType.PACKAGE, scanner.detectContentFileType("/libs/app/install/component.zip"));
+
+        // configs need to be below /libs, /apps in install or config folders
+        assertEquals(ContentPackageScanner.FileType.CONFIG, scanner.detectContentFileType("/libs/app/install/component.cfg"));
+        assertEquals(ContentPackageScanner.FileType.CONFIG, scanner.detectContentFileType("/libs/app/install/component.config"));
+        assertEquals(ContentPackageScanner.FileType.CONFIG, scanner.detectContentFileType("/libs/app/install/component.xml"));
+        assertEquals(ContentPackageScanner.FileType.CONFIG, scanner.detectContentFileType("/libs/app/install/component.cfg.json"));
+        assertNull(scanner.detectContentFileType("/libs/app/install/component.txt"));
+        assertNull(scanner.detectContentFileType("/content/app/install/component.cfg"));
+        assertNull(scanner.detectContentFileType("/content/app/install/component.config"));
+        assertNull(scanner.detectContentFileType("/content/app/install/component.xml"));
+        assertNull(scanner.detectContentFileType("/content/app/install/component.cfg.json"));
+
+        // bundles need to be below /libs, /apps in install orfolders
+        assertEquals(ContentPackageScanner.FileType.BUNDLE, scanner.detectContentFileType("/libs/app/install/component.jar"));
+        assertNull(scanner.detectContentFileType("/content/app/install/component.jar"));
+    }
+
+    @Test
+    public void testProcessConfiguration() throws IOException, URISyntaxException {
+        final ContentPackageScanner scanner = new ContentPackageScanner();
+
+        final ArtifactId pckId = ArtifactId.parse("g:a:1");
+
+        Configuration cfg = scanner.processConfiguration(null, pckId, "/libs/app/install/my.component.cfg");
+        assertEquals("my.component", cfg.getPid());
+        assertNull(cfg.getFactoryPid());
+
+        cfg = scanner.processConfiguration(null, pckId, "/libs/app/install/my.component.config");
+        assertEquals("my.component", cfg.getPid());
+        assertNull(cfg.getFactoryPid());
+
+        cfg = scanner.processConfiguration(null, pckId, "/libs/app/install/my.component.cfg.json");
+        assertEquals("my.component", cfg.getPid());
+        assertNull(cfg.getFactoryPid());
+
+        cfg = scanner.processConfiguration(null, pckId, "/libs/app/install/my.component-name.cfg");
+        assertEquals("my.component~name", cfg.getPid());
+        assertEquals("my.component", cfg.getFactoryPid());
+        assertEquals("name", cfg.getName());
+
+        cfg = scanner.processConfiguration(null, pckId, "/libs/app/install/my.component-name.config");
+        assertEquals("my.component~name", cfg.getPid());
+        assertEquals("my.component", cfg.getFactoryPid());
+        assertEquals("name", cfg.getName());
+
+        cfg = scanner.processConfiguration(null, pckId, "/libs/app/install/my.component~name.cfg.json");
+        assertEquals("my.component~name", cfg.getPid());
+        assertEquals("my.component", cfg.getFactoryPid());
+        assertEquals("name", cfg.getName());
+
+        final File cfgFile = getTestFile("/test-config.xml");
+        cfg = scanner.processConfiguration(cfgFile, pckId, "/libs/app/install/my.component.xml");
+        assertEquals("my.component", cfg.getPid());
+        assertNull(cfg.getFactoryPid());
+
+        cfg = scanner.processConfiguration(cfgFile, pckId, "/libs/app/install/my.component~name.xml");
+        assertEquals("my.component~name", cfg.getPid());
+        assertEquals("my.component", cfg.getFactoryPid());
+        assertEquals("name", cfg.getName());
+
+        cfg = scanner.processConfiguration(cfgFile, pckId, "/libs/app/install/my.component/.content.xml");
+        assertEquals("my.component", cfg.getPid());
+        assertNull(cfg.getFactoryPid());
+
+        cfg = scanner.processConfiguration(cfgFile, pckId, "/libs/app/install/my.component~name/.content.xml");
+        assertEquals("my.component~name", cfg.getPid());
+        assertEquals("my.component", cfg.getFactoryPid());
+        assertEquals("name", cfg.getName());
+
+        final File nocfgFile = getTestFile("/test-noconfig.xml");
+        assertNull(scanner.processConfiguration(nocfgFile, pckId, "/libs/app/install/my.component.xml"));
+    }
 }
diff --git a/src/test/resources/test-config.xml b/src/test/resources/test-config.xml
new file mode 100644
index 0000000..2157869
--- /dev/null
+++ b/src/test/resources/test-config.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0"
+    jcr:primaryType="sling:OsgiConfig"
+    foo="bar"/>
\ No newline at end of file
diff --git a/src/test/resources/test-noconfig.xml b/src/test/resources/test-noconfig.xml
new file mode 100644
index 0000000..754346e
--- /dev/null
+++ b/src/test/resources/test-noconfig.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
+    jcr:primaryType="sling:Component"
+    jcr:title="Quick Search"
+    sling:resourceSuperType="core/wcm/components/search/v1/search"
+    componentGroup="My Site - Structure"/>
\ No newline at end of file