You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by da...@apache.org on 2018/04/27 09:53:17 UTC

[sling-org-apache-sling-feature-analyser] 01/28: Move feature model to whiteboard git

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

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

commit b62568157da3d817884c24e9581cc41eb4667f4e
Author: Carsten Ziegeler <cz...@adobe.com>
AuthorDate: Fri Nov 3 15:06:50 2017 +0100

    Move feature model to whiteboard git
---
 pom.xml                                            | 145 +++++++++
 .../feature/analyser/ApplicationDescriptor.java    |  39 +++
 .../sling/feature/analyser/ArtifactDescriptor.java |  39 +++
 .../sling/feature/analyser/BundleDescriptor.java   |  95 ++++++
 .../feature/analyser/ContainerDescriptor.java      |  70 ++++
 .../apache/sling/feature/analyser/Descriptor.java  |  93 ++++++
 .../sling/feature/analyser/FeatureDescriptor.java  |  28 ++
 .../analyser/impl/ApplicationDescriptorImpl.java   |  52 +++
 .../analyser/impl/BundleDescriptorImpl.java        | 159 +++++++++
 .../analyser/impl/FeatureDescriptorImpl.java       |  39 +++
 .../apache/sling/feature/analyser/main/Main.java   |  74 +++++
 .../sling/feature/analyser/package-info.java       |  23 ++
 .../sling/feature/analyser/service/Analyser.java   | 124 +++++++
 .../sling/feature/analyser/service/Scanner.java    | 228 +++++++++++++
 .../feature/analyser/service/package-info.java     |  23 ++
 .../sling/feature/analyser/task/AnalyserTask.java  |  38 +++
 .../feature/analyser/task/AnalyserTaskContext.java |  49 +++
 .../task/impl/CheckBundleExportsImports.java       | 218 +++++++++++++
 .../task/impl/CheckBundlesForInitialContent.java   |  84 +++++
 .../task/impl/CheckBundlesForResources.java        |  81 +++++
 .../task/impl/CheckRequirementsCapabilities.java   | 100 ++++++
 .../sling/feature/analyser/task/package-info.java  |  23 ++
 .../apache/sling/feature/resolver/Resolver.java    |  32 ++
 .../sling/feature/resolver/ResolverContext.java    |  46 +++
 .../sling/feature/resolver/package-info.java       |  23 ++
 .../sling/feature/scanner/ExtensionScanner.java    |  48 +++
 .../sling/feature/scanner/FrameworkScanner.java    |  44 +++
 .../scanner/impl/ContentPackageDescriptor.java     | 142 ++++++++
 .../scanner/impl/ContentPackageScanner.java        | 358 +++++++++++++++++++++
 .../impl/ContentPackagesExtensionScanner.java      |  73 +++++
 .../scanner/impl/FelixFrameworkScanner.java        | 183 +++++++++++
 .../feature/scanner/impl/RepoInitScanner.java      |  62 ++++
 .../apache/sling/feature/scanner/package-info.java |  23 ++
 ...apache.sling.feature.analyser.task.AnalyserTask |   5 +
 ...g.apache.sling.feature.scanner.ExtensionScanner |   3 +
 ...g.apache.sling.feature.scanner.FrameworkScanner |   2 +
 .../sling/feature/analyser/AnalyserTest.java       |  69 ++++
 src/test/resources/feature_complete.json           |  31 ++
 src/test/resources/feature_incomplete.json         |  33 ++
 39 files changed, 3001 insertions(+)

diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..75533a8
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,145 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+    <!--
+        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.
+    -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.sling</groupId>
+        <artifactId>sling</artifactId>
+        <version>32</version>
+        <relativePath />
+    </parent>
+
+    <artifactId>org.apache.sling.feature.analyser</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+
+    <name>Apache Sling Feature Analyser</name>
+    <description>
+        A feature describes an OSGi system
+    </description>
+
+    <properties>
+        <sling.java.version>8</sling.java.version>
+    </properties>
+
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/tooling/support/feature-analyser</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/tooling/support/feature-analyser</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/trunk/tooling/support/feature-analyser</url>
+    </scm>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <executions>
+                <execution>
+                    <id>unpack-dependencies</id>
+                    <phase>prepare-package</phase>
+                    <goals>
+                        <goal>unpack-dependencies</goal>
+                    </goals>
+                    <configuration>
+                        <excludes>META-INF/**</excludes>
+                        <outputDirectory>${project.build.directory}/classes</outputDirectory>
+                        <overWriteReleases>false</overWriteReleases>
+                        <overWriteSnapshots>true</overWriteSnapshots>
+                        <includeArtifactIds>org.apache.felix.converter,org.apache.sling.feature,org.apache.sling.feature.support,org.apache.sling.commons.johnzon,org.apache.sling.commons.osgi,osgi.core,slf4j-api,slf4j-simple</includeArtifactIds>
+                    </configuration>
+                </execution>
+            </executions>
+            </plugin>
+                <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <configuration>
+                    <archive>
+                        <manifest>
+                            <mainClass>org.apache.sling.feature.analyser.main.Main</mainClass>
+                        </manifest>
+                    </archive>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.rat</groupId>
+                <artifactId>apache-rat-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                        <exclude>src/main/resources/META-INF/services/org.apache.sling.feature.analyser.task.AnalyserTask</exclude>
+                        <exclude>src/main/resources/META-INF/services/org.apache.sling.feature.scanner.ExtensionScanner</exclude>
+                        <exclude>src/main/resources/META-INF/services/org.apache.sling.feature.scanner.FrameworkScanner</exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+    <dependencies>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>osgi.core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.feature</artifactId>
+            <version>0.0.1-SNAPSHOT</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.feature.support</artifactId>
+            <version>0.0.1-SNAPSHOT</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.osgi</artifactId>
+            <version>2.4.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.johnzon</artifactId>
+            <version>1.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+		    <artifactId>org.apache.felix.converter</artifactId>
+		    <version>0.1.0-SNAPSHOT</version>
+            <scope>provided</scope>
+        </dependency>
+      <!-- Testing -->
+        <dependency>
+        	<groupId>junit</groupId>
+        	<artifactId>junit</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.configurator</artifactId>
+            <version>0.0.1-SNAPSHOT</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/src/main/java/org/apache/sling/feature/analyser/ApplicationDescriptor.java b/src/main/java/org/apache/sling/feature/analyser/ApplicationDescriptor.java
new file mode 100644
index 0000000..d76f686
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/analyser/ApplicationDescriptor.java
@@ -0,0 +1,39 @@
+/*
+ * 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.analyser;
+
+import org.apache.sling.feature.Application;
+
+/**
+ * Information about an application.
+ * This is the aggregated information.
+ */
+public abstract class ApplicationDescriptor extends ContainerDescriptor {
+
+    public abstract Application getApplication();
+
+    public abstract BundleDescriptor getFrameworkDescriptor();
+
+    @Override
+    public void lock() {
+        if ( this.isLocked() ) {
+            return;
+        }
+        this.aggregate(this.getFrameworkDescriptor());
+        super.lock();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/feature/analyser/ArtifactDescriptor.java b/src/main/java/org/apache/sling/feature/analyser/ArtifactDescriptor.java
new file mode 100644
index 0000000..8517d2e
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/analyser/ArtifactDescriptor.java
@@ -0,0 +1,39 @@
+/*
+ * 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.analyser;
+
+import java.io.File;
+
+import org.apache.sling.feature.Artifact;
+
+/**
+ * Information about an artifact
+ */
+public abstract class ArtifactDescriptor extends Descriptor {
+
+    /**
+     * Get the artifact file
+     * @return The artifact file
+     */
+    public abstract File getArtifactFile();
+
+    /**
+     * Get the artifact
+     * @return The artifact
+     */
+    public abstract Artifact getArtifact();
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/feature/analyser/BundleDescriptor.java b/src/main/java/org/apache/sling/feature/analyser/BundleDescriptor.java
new file mode 100644
index 0000000..c186fa5
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/analyser/BundleDescriptor.java
@@ -0,0 +1,95 @@
+/*
+ * 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.analyser;
+
+import java.util.jar.Manifest;
+
+import org.apache.sling.feature.analyser.impl.BundleDescriptorImpl;
+import org.apache.sling.feature.support.util.PackageInfo;
+
+/**
+ * Information about a bundle
+ */
+public abstract class BundleDescriptor extends ArtifactDescriptor implements Comparable<BundleDescriptor> {
+
+    /**
+     * Get the bundle symbolic name.
+     * @return The bundle symbolic name
+     */
+    public abstract String getBundleSymbolicName();
+
+    /**
+     * Get the bundle version
+     * @return The bundle version
+     */
+    public abstract String getBundleVersion();
+
+    /**
+     * Get the start level
+     * @return The start level.
+     */
+    public abstract int getBundleStartLevel();
+
+    /**
+     * If the artifact has a manifest, return it
+     * @return The manifest
+     */
+    public abstract Manifest getManifest();
+
+    public boolean isExportingPackage(final String packageName) {
+        for(final PackageInfo i : getExportedPackages()) {
+            if ( i.getName().equals(packageName) ) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public boolean isExportingPackage(final PackageInfo info) {
+        for(final PackageInfo i : getExportedPackages()) {
+            if ( i.getName().equals(info.getName())
+                 && (info.getVersion() == null || info.getPackageVersionRange().includes(i.getPackageVersion()))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if ( obj instanceof BundleDescriptorImpl ) {
+            return this.getBundleSymbolicName().equals(((BundleDescriptorImpl)obj).getBundleSymbolicName()) && this.getBundleVersion().equals(((BundleDescriptorImpl)obj).getBundleVersion());
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return (this.getBundleSymbolicName() + ':' + this.getBundleVersion()).hashCode();
+
+    }
+
+    @Override
+    public String toString() {
+        return "BundleInfo [symbolicName=" + getBundleSymbolicName() + ", version=" + this.getBundleVersion() + "]";
+    }
+
+    @Override
+    public int compareTo(final BundleDescriptor o) {
+        return (this.getBundleSymbolicName() + ':' + this.getBundleVersion()).compareTo((o.getBundleSymbolicName() + ':' + o.getBundleVersion()));
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/feature/analyser/ContainerDescriptor.java b/src/main/java/org/apache/sling/feature/analyser/ContainerDescriptor.java
new file mode 100644
index 0000000..69efb78
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/analyser/ContainerDescriptor.java
@@ -0,0 +1,70 @@
+/*
+ * 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.analyser;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Information about a container (feature/application).
+ * This is the aggregated information.
+ */
+public abstract class ContainerDescriptor extends Descriptor {
+
+    private final Set<BundleDescriptor> bundles = new HashSet<>();
+
+    private final Set<ArtifactDescriptor> artifacts = new HashSet<>();
+
+    /**
+     * Return a set of bundle descriptors.
+     *
+     * The requirements and capabilities of the returned bundles are
+     * available as an aggregate from {@link Descriptor#getCapabilities()},
+     * {@link Descriptor#getRequirements()}, {@link Descriptor#getDynamicImportedPackages()}
+     * {@link Descriptor#getExportedPackages()} and {@link Descriptor#getImportedPackages()}
+     * @return The set of bundle descriptors (might be empty)
+     */
+    public final Set<BundleDescriptor> getBundleDescriptors() {
+        return this.isLocked() ? Collections.unmodifiableSet(bundles) : bundles;
+    }
+
+    /**
+     * Return a set of artifact descriptors
+     * The requirements and capabilities of the returned artifacts are
+     * available as an aggregate from {@link Descriptor#getCapabilities()},
+     * {@link Descriptor#getRequirements()}.
+     * @return The set of artifact descriptors (might be empty)
+     */
+    public final Set<ArtifactDescriptor> getArtifactDescriptors() {
+        return this.isLocked() ? Collections.unmodifiableSet(artifacts) : artifacts;
+    }
+
+    @Override
+    public void lock() {
+        if ( this.isLocked() ) {
+            return;
+        }
+        for(final BundleDescriptor bd : this.bundles) {
+            this.aggregate(bd);
+        }
+        for(final ArtifactDescriptor d : this.artifacts) {
+            this.aggregate(d);
+        }
+        super.lock();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/feature/analyser/Descriptor.java b/src/main/java/org/apache/sling/feature/analyser/Descriptor.java
new file mode 100644
index 0000000..145344d
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/analyser/Descriptor.java
@@ -0,0 +1,93 @@
+/*
+ * 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.analyser;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.sling.feature.Capability;
+import org.apache.sling.feature.Requirement;
+import org.apache.sling.feature.support.util.PackageInfo;
+
+/**
+ * A descriptor holds information about requirements and capabilities
+ */
+public abstract class Descriptor  {
+
+    private boolean locked;
+
+    private final Set<PackageInfo> exports = new HashSet<>();
+
+    private final Set<PackageInfo> imports = new HashSet<>();
+
+    private final Set<PackageInfo> dynImports = new HashSet<>();
+
+    private final Set<Requirement> reqs = new HashSet<>();
+
+    private final Set<Capability> caps = new HashSet<>();
+
+    public void lock() {
+        this.locked = true;
+    }
+
+    public final boolean isLocked() {
+        return this.locked;
+    }
+
+    protected void checkLocked() {
+        if (this.locked) {
+            throw new IllegalStateException("Descriptor is locked.");
+        }
+    }
+
+    protected void aggregate(final Descriptor d) {
+        reqs.addAll(d.getRequirements());
+        caps.addAll(d.getCapabilities());
+        dynImports.addAll(d.getDynamicImportedPackages());
+        imports.addAll(d.getImportedPackages());
+        exports.addAll(d.getExportedPackages());
+    }
+
+    public final Set<PackageInfo> getExportedPackages() {
+        return locked ? Collections.unmodifiableSet(exports) : exports;
+    }
+
+    public final Set<PackageInfo> getImportedPackages() {
+        return locked ? Collections.unmodifiableSet(imports) : imports;
+    }
+
+    public final Set<PackageInfo> getDynamicImportedPackages() {
+        return locked ? Collections.unmodifiableSet(dynImports) : dynImports;
+    }
+
+    /**
+     * Return the list of requirements.
+     * @return The list of requirements. The list might be empty.
+     */
+    public final Set<Requirement> getRequirements() {
+        return locked ? Collections.unmodifiableSet(reqs) : reqs;
+    }
+
+    /**
+     * Return the list of capabilities.
+     * @return The list of capabilities. The list might be empty.
+     */
+    public final Set<Capability> getCapabilities() {
+        return locked ?  Collections.unmodifiableSet(caps) : caps;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/feature/analyser/FeatureDescriptor.java b/src/main/java/org/apache/sling/feature/analyser/FeatureDescriptor.java
new file mode 100644
index 0000000..89739ce
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/analyser/FeatureDescriptor.java
@@ -0,0 +1,28 @@
+/*
+ * 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.analyser;
+
+import org.apache.sling.feature.Feature;
+
+/**
+ * Information about a feature.
+ * This is the aggregated information.
+ */
+public abstract class FeatureDescriptor extends ContainerDescriptor {
+
+    public abstract Feature getFeature();
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/feature/analyser/impl/ApplicationDescriptorImpl.java b/src/main/java/org/apache/sling/feature/analyser/impl/ApplicationDescriptorImpl.java
new file mode 100644
index 0000000..0ef8d89
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/analyser/impl/ApplicationDescriptorImpl.java
@@ -0,0 +1,52 @@
+/*
+ * 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.analyser.impl;
+
+import org.apache.sling.feature.Application;
+import org.apache.sling.feature.analyser.ApplicationDescriptor;
+import org.apache.sling.feature.analyser.BundleDescriptor;
+
+/**
+ * Information about an application.
+ * This is the aggregated information.
+ */
+public class ApplicationDescriptorImpl
+    extends ApplicationDescriptor {
+
+    private BundleDescriptor frameworkDescriptor;
+
+    private final Application app;
+
+    public ApplicationDescriptorImpl(final Application app) {
+        this.app = app;
+    }
+
+    @Override
+    public Application getApplication() {
+        return this.app;
+    }
+
+    @Override
+    public BundleDescriptor getFrameworkDescriptor() {
+        return frameworkDescriptor;
+    }
+
+    public void setFrameworkDescriptor(final BundleDescriptor frameworkDescriptor) {
+        checkLocked();
+        this.frameworkDescriptor = frameworkDescriptor;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/feature/analyser/impl/BundleDescriptorImpl.java b/src/main/java/org/apache/sling/feature/analyser/impl/BundleDescriptorImpl.java
new file mode 100644
index 0000000..1ed0ce2
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/analyser/impl/BundleDescriptorImpl.java
@@ -0,0 +1,159 @@
+/*
+ * 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.analyser.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Set;
+import java.util.jar.Manifest;
+
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.Capability;
+import org.apache.sling.feature.Requirement;
+import org.apache.sling.feature.analyser.BundleDescriptor;
+import org.apache.sling.feature.support.util.ManifestParser;
+import org.apache.sling.feature.support.util.ManifestUtil;
+import org.apache.sling.feature.support.util.PackageInfo;
+import org.osgi.framework.Constants;
+
+/**
+ * Information about a bundle
+ */
+public class BundleDescriptorImpl
+    extends BundleDescriptor {
+
+    /** The bundle symbolic name. */
+    private String symbolicName;
+
+    /** The bundle version. */
+    private String bundleVersion;
+
+    /** The start level of this artifact. */
+    private final int startLevel;
+
+    /** Manifest */
+    private final Manifest manifest;
+
+    /** The physical file for analyzing. */
+    private final File artifactFile;
+
+    /** The corresponding artifact from the feature. */
+    private final Artifact artifact;
+
+    public BundleDescriptorImpl(final Artifact a,
+            final File file,
+            final int startLevel) throws IOException  {
+        this.artifact = a;
+        this.artifactFile = file;
+        this.startLevel = startLevel;
+
+        this.manifest = ManifestUtil.getManifest(file);
+        if ( this.manifest == null ) {
+            throw new IOException("File has no manifest");
+        }
+        this.analyze();
+        this.lock();
+    }
+
+    public BundleDescriptorImpl(final Artifact artifact,
+            final Set<PackageInfo> pcks,
+            final Set<Requirement> reqs,
+            final Set<Capability> caps) throws IOException {
+        this.artifact = artifact;
+        this.artifactFile = null;
+        this.startLevel = 0;
+
+        this.symbolicName = Constants.SYSTEM_BUNDLE_SYMBOLICNAME;
+        this.bundleVersion = artifact.getId().getOSGiVersion().toString();
+        this.getExportedPackages().addAll(pcks);
+        this.getRequirements().addAll(reqs);
+        this.getCapabilities().addAll(caps);
+        this.manifest = null;
+        this.lock();
+    }
+
+    /**
+     * Get the bundle symbolic name.
+     * @return The bundle symbolic name
+     */
+    @Override
+    public String getBundleSymbolicName() {
+        return symbolicName;
+    }
+
+    /**
+     * Get the bundle version
+     * @return The bundle version
+     */
+    @Override
+    public String getBundleVersion() {
+        return bundleVersion;
+    }
+
+    /**
+     * Get the start level
+     * @return The start level or {@code 0} for the default.
+     */
+    @Override
+    public int getBundleStartLevel() {
+        return startLevel;
+    }
+
+    @Override
+    public File getArtifactFile() {
+        return artifactFile;
+    }
+
+    @Override
+    public Artifact getArtifact() {
+        return artifact;
+    }
+
+    @Override
+    public Manifest getManifest() {
+        return this.manifest;
+    }
+
+    protected void analyze() throws IOException {
+        final String name = this.manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
+        if ( name != null ) {
+            final String version = this.manifest.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
+            if ( version == null ) {
+                throw new IOException("Unable to get bundle version from artifact " + getArtifact().getId().toMvnId());
+            }
+            this.symbolicName = name;
+            this.bundleVersion = version;
+            final String newBundleName = this.getArtifact().getMetadata().get("bundle:rename-bsn");
+            if (newBundleName != null) {
+                this.symbolicName = newBundleName;
+            }
+
+            this.getExportedPackages().addAll(ManifestUtil.extractExportedPackages(this.manifest));
+            this.getImportedPackages().addAll(ManifestUtil.extractImportedPackages(this.manifest));
+            this.getDynamicImportedPackages().addAll(ManifestUtil.extractDynamicImportedPackages(this.manifest));
+            try {
+                ManifestParser parser = new ManifestParser(this.manifest);
+                this.getCapabilities().addAll(ManifestUtil.extractCapabilities(parser));
+                this.getRequirements().addAll(ManifestUtil.extractRequirements(parser));
+            } catch (Exception ex) {
+                throw new IOException(ex);
+            }
+        } else {
+            throw new IOException("Unable to get bundle symbolic name from artifact " + getArtifact().getId().toMvnId());
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/feature/analyser/impl/FeatureDescriptorImpl.java b/src/main/java/org/apache/sling/feature/analyser/impl/FeatureDescriptorImpl.java
new file mode 100644
index 0000000..d7ac09a
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/analyser/impl/FeatureDescriptorImpl.java
@@ -0,0 +1,39 @@
+/*
+ * 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.analyser.impl;
+
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.analyser.FeatureDescriptor;
+
+/**
+ * Information about a feature.
+ * This is the aggregated information.
+ */
+public class FeatureDescriptorImpl
+    extends FeatureDescriptor {
+
+    private final Feature feature;
+
+    public FeatureDescriptorImpl(final Feature feature) {
+        this.feature = feature;
+    }
+
+    @Override
+    public Feature getFeature() {
+        return this.feature;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/feature/analyser/main/Main.java b/src/main/java/org/apache/sling/feature/analyser/main/Main.java
new file mode 100644
index 0000000..37c7548
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/analyser/main/Main.java
@@ -0,0 +1,74 @@
+/*
+ * 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.analyser.main;
+
+import org.apache.sling.feature.Application;
+import org.apache.sling.feature.analyser.service.Analyser;
+import org.apache.sling.feature.analyser.service.Scanner;
+import org.apache.sling.feature.support.ArtifactManagerConfig;
+import org.apache.sling.feature.support.FeatureUtil;
+import org.apache.sling.feature.support.json.ApplicationJSONReader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+
+public class Main {
+
+    public static void main(final String[] args) {
+        // setup logging
+        System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "info");
+        System.setProperty("org.slf4j.simpleLogger.showThreadName", "false");
+        System.setProperty("org.slf4j.simpleLogger.levelInBrackets", "true");
+        System.setProperty("org.slf4j.simpleLogger.showLogName", "false");
+
+        final Logger logger = LoggerFactory.getLogger("analyser");
+        logger.info("Apache Sling Application Analyser");
+        logger.info("");
+
+        if ( args.length == 0 ) {
+            logger.error("Required argument missing: application file");
+            System.exit(1);
+        }
+        if ( args.length > 1 ) {
+            logger.error("Too many arguments. Only one (application file) is supported");
+            System.exit(1);
+        }
+        final File f = new File(args[0]);
+        Application app = null;
+        try ( final FileReader r = new FileReader(f)) {
+            app = ApplicationJSONReader.read(r);
+        } catch ( final IOException ioe) {
+            logger.error("Unable to read application: {}", f, ioe);
+            System.exit(1);
+        }
+        if ( app.getFramework() == null ) {
+            app.setFramework(FeatureUtil.getFelixFrameworkId(null));
+        }
+
+        try {
+            final Scanner scanner = new Scanner(new ArtifactManagerConfig());
+            final Analyser analyser = new Analyser(scanner);
+            analyser.analyse(app);
+        } catch ( final Exception e) {
+            logger.error("Unable to analyse application: {}", f, e);
+            System.exit(1);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/feature/analyser/package-info.java b/src/main/java/org/apache/sling/feature/analyser/package-info.java
new file mode 100644
index 0000000..3042a56
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/analyser/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+@org.osgi.annotation.versioning.Version("1.0.0")
+package org.apache.sling.feature.analyser;
+
+
diff --git a/src/main/java/org/apache/sling/feature/analyser/service/Analyser.java b/src/main/java/org/apache/sling/feature/analyser/service/Analyser.java
new file mode 100644
index 0000000..42962fd
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/analyser/service/Analyser.java
@@ -0,0 +1,124 @@
+/*
+ * 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.analyser.service;
+
+import org.apache.sling.feature.Application;
+import org.apache.sling.feature.analyser.ApplicationDescriptor;
+import org.apache.sling.feature.analyser.task.AnalyserTask;
+import org.apache.sling.feature.analyser.task.AnalyserTaskContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.ServiceLoader;
+import java.util.Set;
+
+public class Analyser {
+
+    private final AnalyserTask[] tasks;
+
+    private final Scanner scanner;
+
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    public Analyser(final Scanner scanner,
+            final AnalyserTask...tasks)
+    throws IOException {
+        this.tasks = tasks;
+        this.scanner = scanner;
+    }
+
+    public Analyser(final Scanner scanner,
+            final String... taskIds)
+    throws IOException {
+        this(scanner, getTasks(taskIds));
+        if ( this.tasks.length != taskIds.length ) {
+            throw new IOException("Couldn't find all tasks " + taskIds);
+        }
+    }
+
+    public Analyser(final Scanner scanner)
+    throws IOException {
+        this(scanner, getTasks((String[])null));
+    }
+
+    private static AnalyserTask[] getTasks(final String... taskIds) {
+        final Set<String> ids = taskIds == null ? null : new HashSet<>(Arrays.asList(taskIds));
+        final ServiceLoader<AnalyserTask> loader = ServiceLoader.load(AnalyserTask.class);
+        final List<AnalyserTask> list = new ArrayList<>();
+        for(final AnalyserTask task : loader) {
+            if ( ids == null || ids.contains(task.getId()) ) {
+                list.add(task);
+            }
+        }
+        return list.toArray(new AnalyserTask[list.size()]);
+    }
+
+    public void analyse(final Application app)
+    throws Exception {
+        logger.info("Starting application analyzer...");
+
+        final ApplicationDescriptor appDesc = scanner.scan(app);
+
+        final List<String> warnings = new ArrayList<>();
+        final List<String> errors = new ArrayList<>();
+
+        // execute analyser tasks
+        for(final AnalyserTask task : tasks) {
+            logger.info("- Executing {}...", task.getName());
+            task.execute(new AnalyserTaskContext() {
+
+                @Override
+                public Application getApplication() {
+                    return app;
+                }
+
+                @Override
+                public ApplicationDescriptor getDescriptor() {
+                    return appDesc;
+                }
+
+                @Override
+                public void reportWarning(final String message) {
+                    warnings.add(message);
+                }
+
+                @Override
+                public void reportError(final String message) {
+                    errors.add(message);
+                }
+            });
+        }
+
+        for(final String msg : warnings) {
+            logger.warn(msg);
+        }
+        for(final String msg : errors) {
+            logger.error(msg);
+        }
+
+        if ( !errors.isEmpty() ) {
+            throw new Exception("Analyser detected errors. See log output for error messages.");
+        }
+
+        logger.info("Provisioning model analyzer finished");
+    }
+}
diff --git a/src/main/java/org/apache/sling/feature/analyser/service/Scanner.java b/src/main/java/org/apache/sling/feature/analyser/service/Scanner.java
new file mode 100644
index 0000000..4b2e5a6
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/analyser/service/Scanner.java
@@ -0,0 +1,228 @@
+/*
+ * 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.analyser.service;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceLoader;
+
+import org.apache.sling.feature.Application;
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.Bundles;
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.Extensions;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.analyser.ApplicationDescriptor;
+import org.apache.sling.feature.analyser.BundleDescriptor;
+import org.apache.sling.feature.analyser.ContainerDescriptor;
+import org.apache.sling.feature.analyser.FeatureDescriptor;
+import org.apache.sling.feature.analyser.impl.ApplicationDescriptorImpl;
+import org.apache.sling.feature.analyser.impl.BundleDescriptorImpl;
+import org.apache.sling.feature.analyser.impl.FeatureDescriptorImpl;
+import org.apache.sling.feature.scanner.ExtensionScanner;
+import org.apache.sling.feature.scanner.FrameworkScanner;
+import org.apache.sling.feature.support.ArtifactManager;
+import org.apache.sling.feature.support.ArtifactManagerConfig;
+
+/**
+ * The scanner is a service that scans items and provides descriptions for these.
+ * The following items can be scanned individually
+ * <ul>
+ *   <li>A bundle artifact
+ *   <li>An artifact (requires {@link ArtifactScannner}s)
+ *   <li>An extension (requires {@link ExtensionScanner}s)
+ *   <li>A feature (requires {@link ArtifactScannner}s and {@link ExtensionScanner}s)
+ *   <li>A framework (requires {@link FrameworkScanner}s
+ *   <li>An application (requires all scanner types)
+ * </ul>
+ */
+public class Scanner {
+
+    private final ArtifactManager artifactManager;
+
+    private final List<ExtensionScanner> extensionScanners;
+
+    private final List<FrameworkScanner> frameworkScanners;
+
+    /**
+     * Create a new scanner
+     *
+     * @param amConfig The artifact manager configuration
+     * @param artifactScanners A list of artifact scanners
+     * @param extensionScanners A list of extension scanners
+     * @param frameworkScanners A list of framework scanners
+     * @throws IOException If something goes wrong
+     */
+    public Scanner(final ArtifactManagerConfig amConfig,
+            final List<ExtensionScanner> extensionScanners,
+            final List<FrameworkScanner> frameworkScanners)
+    throws IOException {
+        this.artifactManager = ArtifactManager.getArtifactManager(amConfig);
+        this.extensionScanners = extensionScanners == null ? getServices(ExtensionScanner.class) : extensionScanners;
+        this.frameworkScanners = frameworkScanners == null ? getServices(FrameworkScanner.class) : frameworkScanners;
+    }
+
+    /**
+     * Create a new scanner and use the service loader to find the scanners
+     *
+     * @param amConfig The artifact manager configuration
+     * @throws IOException If something goes wrong
+     */
+    public Scanner(final ArtifactManagerConfig amConfig)
+    throws IOException {
+        this(amConfig, null, null);
+    }
+
+    /**
+     * Get services from the service loader
+     *
+     * @param clazz The service class
+     * @return The list of services might be empty.
+     */
+    private static <T> List<T> getServices(final Class<T> clazz) {
+        final ServiceLoader<T> loader = ServiceLoader.load(clazz);
+        final List<T> list = new ArrayList<>();
+        for(final T task : loader) {
+            list.add(task);
+        }
+        return list;
+    }
+
+    /**
+     * Scan a bundle
+     *
+     * @param bundle The bundle artifact
+     * @param startLevel The start level of the bundle
+     * @return The bundle descriptor
+     * @throws IOException If something goes wrong or the provided artifact is not a bundle.
+     */
+    public BundleDescriptor scan(final Artifact bundle, final int startLevel) throws IOException {
+        final File file = artifactManager.getArtifactHandler(bundle.getId().toMvnUrl()).getFile();
+        if ( file == null ) {
+            throw new IOException("Unable to find file for " + bundle.getId());
+        }
+
+        return new BundleDescriptorImpl(bundle, file, startLevel);
+    }
+
+    /**
+     * Get all bundle descriptors for a feature / application
+     * @param bundles The bundles
+     * @param desc The descriptor
+     * @throws IOException If something goes wrong or no suitable scanner is found.
+     */
+    private void getBundleInfos(final Bundles bundles, final ContainerDescriptor desc)
+    throws IOException {
+        for(final Map.Entry<Integer, List<Artifact>> entry : bundles.getBundlesByStartLevel().entrySet()) {
+            for(final Artifact bundle : entry.getValue() ) {
+                final BundleDescriptor bundleDesc = scan(bundle, entry.getKey());
+                desc.getBundleDescriptors().add(bundleDesc);
+            }
+        }
+    }
+
+    private void scan(final Extensions extensions, final ContainerDescriptor desc)
+    throws IOException {
+        for(final Extension ext : extensions) {
+            ContainerDescriptor extDesc = null;
+            for(final ExtensionScanner scanner : this.extensionScanners) {
+                extDesc = scanner.scan(ext, this.artifactManager);
+                if ( extDesc != null ) {
+                    break;
+                }
+            }
+            if ( extDesc == null ) {
+                throw new IOException("No extension scanner found for extension named " + ext.getName() + " of type " + ext.getType().name());
+            }
+            desc.getRequirements().addAll(extDesc.getRequirements());
+            desc.getCapabilities().addAll(extDesc.getCapabilities());
+            desc.getExportedPackages().addAll(extDesc.getExportedPackages());
+            desc.getImportedPackages().addAll(extDesc.getImportedPackages());
+            desc.getDynamicImportedPackages().addAll(extDesc.getDynamicImportedPackages());
+
+            desc.getArtifactDescriptors().addAll(extDesc.getArtifactDescriptors());
+            desc.getBundleDescriptors().addAll(extDesc.getBundleDescriptors());
+        }
+    }
+
+    private void compact(final ContainerDescriptor desc) {
+        // TBD remove all import packages / dynamic import packages which are resolved by this bundle set
+        // same with requirements
+
+    }
+
+    /**
+     * Scan a feature
+     *
+     * @param feature The feature
+     * @return The feature descriptor
+     * @throws IOException If something goes wrong or a scanner is missing
+     */
+    public FeatureDescriptor scan(final Feature feature) throws IOException {
+        final FeatureDescriptorImpl desc = new FeatureDescriptorImpl(feature);
+
+        getBundleInfos(feature.getBundles(), desc);
+        scan(feature.getExtensions(), desc);
+
+        compact(desc);
+
+        desc.lock();
+
+        return desc;
+    }
+
+    /**
+     * Scan an application
+     *
+     * @param app The application
+     * @return The application descriptor
+     * @throws IOException If something goes wrong or a scanner is missing
+     */
+    public ApplicationDescriptor scan(final Application app) throws IOException {
+        final ApplicationDescriptorImpl desc = new ApplicationDescriptorImpl(app);
+
+        getBundleInfos(app.getBundles(), desc);
+        scan(app.getExtensions(), desc);
+
+        compact(desc);
+
+        // framework
+        final File file = artifactManager.getArtifactHandler(app.getFramework().toMvnUrl()).getFile();
+        if ( file == null ) {
+            throw new IOException("Unable to find file for " + app.getFramework());
+        }
+
+        BundleDescriptor fwk = null;
+        for(final FrameworkScanner scanner : this.frameworkScanners) {
+            fwk = scanner.scan(app.getFramework(), file, app.getFrameworkProperties());
+            if ( fwk != null ) {
+                break;
+            }
+        }
+        if ( fwk == null ) {
+            throw new IOException("No scanner found for framework " + app.getFramework().toMvnId());
+        }
+
+        desc.setFrameworkDescriptor(fwk);
+
+        desc.lock();
+        return desc;
+    }
+}
diff --git a/src/main/java/org/apache/sling/feature/analyser/service/package-info.java b/src/main/java/org/apache/sling/feature/analyser/service/package-info.java
new file mode 100644
index 0000000..7d81b73
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/analyser/service/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+@org.osgi.annotation.versioning.Version("1.0.0")
+package org.apache.sling.feature.analyser.service;
+
+
diff --git a/src/main/java/org/apache/sling/feature/analyser/task/AnalyserTask.java b/src/main/java/org/apache/sling/feature/analyser/task/AnalyserTask.java
new file mode 100644
index 0000000..8ef21bb
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/analyser/task/AnalyserTask.java
@@ -0,0 +1,38 @@
+/*
+ * 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.analyser.task;
+
+/**
+ * A analyser task analyses a specific part of the assembled
+ * application. It can report errors and warnings.
+ */
+public interface AnalyserTask {
+
+    /** A unique (short) id. */
+    default String getId() {
+        return getClass().getName();
+    };
+
+    /** A human readable name to identify the task. */
+    default String getName() {
+        return getClass().getSimpleName();
+    };
+
+    /** Execute the task. */
+    void execute(AnalyserTaskContext ctx) throws Exception;
+}
+
diff --git a/src/main/java/org/apache/sling/feature/analyser/task/AnalyserTaskContext.java b/src/main/java/org/apache/sling/feature/analyser/task/AnalyserTaskContext.java
new file mode 100644
index 0000000..870e03a
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/analyser/task/AnalyserTaskContext.java
@@ -0,0 +1,49 @@
+/*
+ * 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.analyser.task;
+
+import org.apache.sling.feature.Application;
+import org.apache.sling.feature.analyser.ApplicationDescriptor;
+
+public interface AnalyserTaskContext {
+
+    /**
+     * The assembled application.
+     * @return The application.
+     */
+    Application getApplication();
+
+    /**
+     * The application descriptor
+     */
+    ApplicationDescriptor getDescriptor();
+
+    /**
+     * This method is invoked by a {@link AnalyserTask} to report
+     * a warning.
+     * @param message The message.
+     */
+    void reportWarning(String message);
+
+    /**
+     * This method is invoked by a {@link AnalyserTask} to report
+     * an error.
+     * @param message The message.
+     */
+    void reportError(String message);
+}
+
diff --git a/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckBundleExportsImports.java b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckBundleExportsImports.java
new file mode 100644
index 0000000..ee0dffd
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckBundleExportsImports.java
@@ -0,0 +1,218 @@
+/*
+ * 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.analyser.task.impl;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import org.apache.sling.feature.analyser.BundleDescriptor;
+import org.apache.sling.feature.analyser.task.AnalyserTask;
+import org.apache.sling.feature.analyser.task.AnalyserTaskContext;
+import org.apache.sling.feature.support.util.PackageInfo;
+import org.osgi.framework.Version;
+
+public class CheckBundleExportsImports implements AnalyserTask {
+
+    @Override
+    public String getName() {
+        return "Bundle Import/Export Check";
+    }
+
+    @Override
+    public String getId() {
+        return "bundle-packages";
+    }
+
+    public static final class Report {
+
+        public List<PackageInfo> exportWithoutVersion = new ArrayList<>();
+
+        public List<PackageInfo> exportMatchingSeveral = new ArrayList<>();
+
+        public List<PackageInfo> importWithoutVersion = new ArrayList<>();
+
+        public List<PackageInfo> missingExports = new ArrayList<>();
+
+        public List<PackageInfo> missingExportsWithVersion = new ArrayList<>();
+
+        public List<PackageInfo> missingExportsForOptional = new ArrayList<>();
+    }
+
+    private Report getReport(final Map<BundleDescriptor, Report> reports, final BundleDescriptor info) {
+        Report report = reports.get(info);
+        if ( report == null ) {
+            report = new Report();
+            reports.put(info, report);
+        }
+        return report;
+    }
+
+    private void checkForVersionOnExportedPackages(final AnalyserTaskContext ctx, final Map<BundleDescriptor, Report> reports) {
+        for(final BundleDescriptor info : ctx.getDescriptor().getBundleDescriptors()) {
+            if ( info.getExportedPackages() != null ) {
+                for(final PackageInfo i : info.getExportedPackages()) {
+                    if ( i.getPackageVersion().compareTo(Version.emptyVersion) == 0 ) {
+                        getReport(reports, info).exportWithoutVersion.add(i);
+                    }
+                }
+            }
+        }
+    }
+
+    private void checkForVersionOnImportingPackages(final AnalyserTaskContext ctx, final Map<BundleDescriptor, Report> reports) {
+        for(final BundleDescriptor info : ctx.getDescriptor().getBundleDescriptors()) {
+            if ( info.getImportedPackages() != null ) {
+                for(final PackageInfo i : info.getImportedPackages()) {
+                    if ( i.getVersion() == null ) {
+                        // don't report for javax and org.w3c. packages (TODO)
+                        if ( !i.getName().startsWith("javax.")
+                             && !i.getName().startsWith("org.w3c.")) {
+                            getReport(reports, info).importWithoutVersion.add(i);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+
+    @Override
+    public void execute(final AnalyserTaskContext ctx) throws IOException {
+        // basic checks
+        final Map<BundleDescriptor, Report> reports = new HashMap<>();
+        checkForVersionOnExportedPackages(ctx, reports);
+        checkForVersionOnImportingPackages(ctx, reports);
+
+        final SortedMap<Integer, List<BundleDescriptor>> bundlesMap = new TreeMap<>();
+        for(final BundleDescriptor bi : ctx.getDescriptor().getBundleDescriptors()) {
+            List<BundleDescriptor> list = bundlesMap.get(bi.getBundleStartLevel());
+            if ( list == null ) {
+                list = new ArrayList<>();
+                bundlesMap.put(bi.getBundleStartLevel(), list);
+            }
+            list.add(bi);
+        }
+
+        // add all system packages
+        final List<BundleDescriptor> exportingBundles = new ArrayList<>();
+        exportingBundles.add(ctx.getDescriptor().getFrameworkDescriptor());
+
+        for(final Map.Entry<Integer, List<BundleDescriptor>> entry : bundlesMap.entrySet()) {
+            // first add all exporting bundles
+            for(final BundleDescriptor info : entry.getValue()) {
+                if ( info.getExportedPackages() != null ) {
+                    exportingBundles.add(info);
+                }
+            }
+            // check importing bundles
+            for(final BundleDescriptor info : entry.getValue()) {
+                if ( info.getImportedPackages() != null ) {
+                    for(final PackageInfo pck : info.getImportedPackages() ) {
+                        final List<BundleDescriptor> candidates = getCandidates(exportingBundles, pck);
+                        if ( candidates.isEmpty() ) {
+                            if ( pck.isOptional() ) {
+                                getReport(reports, info).missingExportsForOptional.add(pck);
+                            } else {
+                                getReport(reports, info).missingExports.add(pck);
+                            }
+                        } else {
+                            final List<BundleDescriptor> matchingCandidates = new ArrayList<>();
+                            for(final BundleDescriptor i : candidates) {
+                                if ( i.isExportingPackage(pck) ) {
+                                    matchingCandidates.add(i);
+                                }
+                            }
+                            if ( matchingCandidates.isEmpty() ) {
+                                if ( pck.isOptional() ) {
+                                    getReport(reports, info).missingExportsForOptional.add(pck);
+                                } else {
+                                    getReport(reports, info).missingExportsWithVersion.add(pck);
+                                }
+                            } else if ( matchingCandidates.size() > 1 ) {
+                                getReport(reports, info).exportMatchingSeveral.add(pck);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        for(final Map.Entry<BundleDescriptor, Report> entry : reports.entrySet()) {
+            final String key = "Bundle " + entry.getKey().getArtifact().getId().getArtifactId() + ":" + entry.getKey().getArtifact().getId().getVersion();
+
+            if ( !entry.getValue().importWithoutVersion.isEmpty() ) {
+                ctx.reportWarning(key + " is importing package(s) " + getPackageInfo(entry.getValue().importWithoutVersion, false) + " without specifying a version range.");
+            }
+            if ( !entry.getValue().exportWithoutVersion.isEmpty() ) {
+                ctx.reportWarning(key + " is exporting package(s) " + getPackageInfo(entry.getValue().importWithoutVersion, false) + " without a version.");
+            }
+
+            if ( !entry.getValue().missingExports.isEmpty() ) {
+                ctx.reportError(key + " is importing package(s) " + getPackageInfo(entry.getValue().missingExports, false) + " in start level " +
+                        String.valueOf(entry.getKey().getBundleStartLevel()) + " but no bundle is exporting these for that start level.");
+            }
+            if ( !entry.getValue().missingExportsWithVersion.isEmpty() ) {
+                ctx.reportError(key + " is importing package(s) " + getPackageInfo(entry.getValue().missingExportsWithVersion, true) + " in start level " +
+                        String.valueOf(entry.getKey().getBundleStartLevel()) + " but no bundle is exporting these for that start level in the required version range.");
+            }
+        }
+    }
+
+    private String getPackageInfo(final List<PackageInfo> pcks, final boolean includeVersion) {
+        if ( pcks.size() == 1 ) {
+            if (includeVersion) {
+                return pcks.get(0).toString();
+            } else {
+                return pcks.get(0).getName();
+            }
+        }
+        final StringBuilder sb = new StringBuilder();
+        boolean first = true;
+        sb.append('[');
+        for(final PackageInfo info : pcks) {
+            if ( first ) {
+                first = false;
+            } else {
+                sb.append(", ");
+            }
+            if (includeVersion) {
+                sb.append(info.toString());
+            } else {
+                sb.append(info.getName());
+            }
+        }
+        sb.append(']');
+        return sb.toString();
+    }
+
+    private List<BundleDescriptor> getCandidates(final List<BundleDescriptor> exportingBundles, final PackageInfo pck) {
+        final List<BundleDescriptor> candidates = new ArrayList<>();
+        for(final BundleDescriptor info : exportingBundles) {
+            if ( info.isExportingPackage(pck.getName()) ) {
+                candidates.add(info);
+            }
+        }
+        return candidates;
+    }
+}
diff --git a/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckBundlesForInitialContent.java b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckBundlesForInitialContent.java
new file mode 100644
index 0000000..4dd0100
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckBundlesForInitialContent.java
@@ -0,0 +1,84 @@
+/*
+ * 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.analyser.task.impl;
+
+import org.apache.sling.commons.osgi.ManifestHeader;
+import org.apache.sling.feature.analyser.BundleDescriptor;
+import org.apache.sling.feature.analyser.task.AnalyserTask;
+import org.apache.sling.feature.analyser.task.AnalyserTaskContext;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.jar.Manifest;
+
+public class CheckBundlesForInitialContent implements AnalyserTask {
+
+    /** The manifest header to specify initial content to be loaded. */
+    private static final String CONTENT_HEADER = "Sling-Initial-Content";
+
+    /**
+     * The path directive specifying the target node where initial content will
+     * be loaded.
+     */
+    private static final String PATH_DIRECTIVE = "path";
+
+    @Override
+    public String getName() {
+        return "Bundle Initial Content Check";
+    }
+
+    @Override
+    public String getId() {
+        return "bundle-content";
+    }
+
+    @Override
+    public void execute(final AnalyserTaskContext ctx) {
+        // check for initial content
+        for(final BundleDescriptor info : ctx.getDescriptor().getBundleDescriptors()) {
+            final List<String> initialContent = extractInitialContent(info.getManifest());
+
+            if ( !initialContent.isEmpty() ) {
+                ctx.reportWarning("Found initial content in " + info.getArtifact() + " : " + initialContent);
+            }
+        }
+    }
+
+    private List<String> extractInitialContent(final Manifest m) {
+        final List<String> initialContent = new ArrayList<>();
+        if ( m != null ) {
+            final String root =  m.getMainAttributes().getValue(CONTENT_HEADER);
+            if (root != null) {
+                final ManifestHeader header = ManifestHeader.parse(root);
+                for (final ManifestHeader.Entry entry : header.getEntries()) {
+
+                    String path = entry.getDirectiveValue(PATH_DIRECTIVE);
+                    if (path == null) {
+                        path = "/";
+                    } else if (!path.startsWith("/")) {
+                        // make relative path absolute
+                        path = "/" + path;
+                    }
+                    initialContent.add(path);
+                }
+            }
+        }
+        return initialContent;
+    }
+}
diff --git a/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckBundlesForResources.java b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckBundlesForResources.java
new file mode 100644
index 0000000..603cfe4
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckBundlesForResources.java
@@ -0,0 +1,81 @@
+/*
+ * 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.analyser.task.impl;
+
+import org.apache.sling.commons.osgi.ManifestHeader;
+import org.apache.sling.feature.analyser.BundleDescriptor;
+import org.apache.sling.feature.analyser.task.AnalyserTask;
+import org.apache.sling.feature.analyser.task.AnalyserTaskContext;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.jar.Manifest;
+
+public class CheckBundlesForResources implements AnalyserTask {
+
+    /** The manifest header to specify bundle resources. */
+    private static final String BUNDLE_RESOURCE_ROOTS = "Sling-Bundle-Resources";
+
+    /**
+     * The path directive specifying the target node where initial content will
+     * be loaded.
+     */
+    private static final String PATH_DIRECTIVE = "path";
+
+    @Override
+    public String getName() {
+        return "Bundle Resources Check";
+    }
+
+    @Override
+    public String getId() {
+        return "bundle-resources";
+    }
+
+    @Override
+    public void execute(final AnalyserTaskContext ctx) {
+        // check for initial content
+        for(final BundleDescriptor info : ctx.getDescriptor().getBundleDescriptors()) {
+            final List<String> bundleResources = extractBundleResources(info.getManifest());
+            if ( !bundleResources.isEmpty() ) {
+                ctx.reportWarning("Found bundle resources in " + info.getArtifact() + " : " + bundleResources);
+            }
+        }
+    }
+
+    private List<String> extractBundleResources(final Manifest m) {
+        final List<String> bundleResources = new ArrayList<>();
+        if ( m != null ) {
+            final String root =  m.getMainAttributes().getValue(BUNDLE_RESOURCE_ROOTS);
+            if (root != null) {
+                final ManifestHeader header = ManifestHeader.parse(root);
+                for (final ManifestHeader.Entry entry : header.getEntries()) {
+                    final String resourceRoot = entry.getValue();
+                    final String pathDirective = entry.getDirectiveValue(PATH_DIRECTIVE);
+                    if (pathDirective != null) {
+                        bundleResources.add(resourceRoot + "!" + pathDirective);
+                    } else {
+                        bundleResources.add(resourceRoot);
+                    }
+                }
+            }
+        }
+        return bundleResources;
+    }
+}
diff --git a/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckRequirementsCapabilities.java b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckRequirementsCapabilities.java
new file mode 100644
index 0000000..0ea2799
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckRequirementsCapabilities.java
@@ -0,0 +1,100 @@
+/*
+ * 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.analyser.task.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.stream.Collectors;
+
+import org.apache.sling.feature.Requirement;
+import org.apache.sling.feature.analyser.ArtifactDescriptor;
+import org.apache.sling.feature.analyser.BundleDescriptor;
+import org.apache.sling.feature.analyser.task.AnalyserTask;
+import org.apache.sling.feature.analyser.task.AnalyserTaskContext;
+import org.apache.sling.feature.support.util.CapabilityMatcher;
+
+public class CheckRequirementsCapabilities implements AnalyserTask {
+    private final String format = "Artifact %s:%s requires %s in start level %d but %s";
+
+    @Override
+    public void execute(AnalyserTaskContext ctx) throws Exception {
+        final SortedMap<Integer, List<ArtifactDescriptor>> artifactsMap = new TreeMap<>();
+        for(final BundleDescriptor bi : ctx.getDescriptor().getBundleDescriptors()) {
+            List<ArtifactDescriptor> list = artifactsMap.get(bi.getBundleStartLevel());
+            if ( list == null ) {
+                list = new ArrayList<>();
+                artifactsMap.put(bi.getBundleStartLevel(), list);
+            }
+            list.add(bi);
+        }
+
+        if (!ctx.getDescriptor().getArtifactDescriptors().isEmpty()) {
+            artifactsMap.put(
+                    (artifactsMap.isEmpty() ? 0 : artifactsMap.lastKey()) + 1,
+                    new ArrayList<>(ctx.getDescriptor().getArtifactDescriptors())
+                    );
+        }
+
+        // add system artifact
+        final List<ArtifactDescriptor> artifacts = new ArrayList<>();
+        artifacts.add(ctx.getDescriptor().getFrameworkDescriptor());
+
+        for(final Map.Entry<Integer, List<ArtifactDescriptor>> entry : artifactsMap.entrySet()) {
+            // first add all providing artifacts
+            for (final ArtifactDescriptor info : entry.getValue()) {
+                if (info.getCapabilities() != null) {
+                    artifacts.add(info);
+                }
+            }
+            // check requiring artifacts
+            for (final ArtifactDescriptor info : entry.getValue()) {
+                if (info.getRequirements() != null)
+                {
+                    for (Requirement requirement : info.getRequirements()) {
+                        List<ArtifactDescriptor> candidates = getCandidates(artifacts, requirement);
+
+                        if (candidates.isEmpty()) {
+                            if ( "osgi.service".equals(requirement.getNamespace())  ){
+                                // osgi.service is special - we don't provide errors or warnings in this case
+                                continue;
+                            }
+                            if (!CapabilityMatcher.isOptional(requirement)) {
+                                ctx.reportError(String.format(format, info.getArtifact().getId().getArtifactId(), info.getArtifact().getId().getVersion(), requirement.toString(), entry.getKey(), "no artifact is providing a matching capability in this start level."));
+                            }
+                            else {
+                                ctx.reportWarning(String.format(format, info.getArtifact().getId().getArtifactId(), info.getArtifact().getId().getVersion(), requirement.toString(), entry.getKey(), "while the requirement is optional no artifact is providing a matching capability in this start level."));
+                            }
+                        }
+                        else if ( candidates.size() > 1 ) {
+                            ctx.reportWarning(String.format(format, info.getArtifact().getId().getArtifactId(), info.getArtifact().getId().getVersion(), requirement.toString(), entry.getKey(), "there is more than one matching capability in this start level."));
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private List<ArtifactDescriptor> getCandidates(List<ArtifactDescriptor> artifactDescriptors, Requirement requirement) {
+        return artifactDescriptors.stream()
+                .filter(artifactDescriptor -> artifactDescriptor.getCapabilities() != null)
+                .filter(artifactDescriptor -> artifactDescriptor.getCapabilities().stream().anyMatch(capability -> CapabilityMatcher.matches(capability, requirement)))
+                .collect(Collectors.toList());
+    }
+}
diff --git a/src/main/java/org/apache/sling/feature/analyser/task/package-info.java b/src/main/java/org/apache/sling/feature/analyser/task/package-info.java
new file mode 100644
index 0000000..d265303
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/analyser/task/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+@org.osgi.annotation.versioning.Version("1.0.0")
+package org.apache.sling.feature.analyser.task;
+
+
diff --git a/src/main/java/org/apache/sling/feature/resolver/Resolver.java b/src/main/java/org/apache/sling/feature/resolver/Resolver.java
new file mode 100644
index 0000000..f18626e
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/resolver/Resolver.java
@@ -0,0 +1,32 @@
+/*
+ * 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.resolver;
+
+import org.apache.sling.feature.Feature;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Resolver {
+
+    public List<Feature> resolve(final ResolverContext ctx) {
+        final List<Feature> result = new ArrayList<>(ctx.getRequiredFeatures());
+
+        return result;
+    }
+}
+
diff --git a/src/main/java/org/apache/sling/feature/resolver/ResolverContext.java b/src/main/java/org/apache/sling/feature/resolver/ResolverContext.java
new file mode 100644
index 0000000..3490525
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/resolver/ResolverContext.java
@@ -0,0 +1,46 @@
+/*
+ * 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.resolver;
+
+import org.apache.sling.feature.Feature;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ResolverContext {
+
+    private final List<Feature> requiredFeatures = new ArrayList<>();
+
+    private final List<Feature> optionalFeatures = new ArrayList<>();
+
+    public List<Feature> getRequiredFeatures() {
+        return requiredFeatures;
+    }
+
+    public List<Feature> getOptionalFeatures() {
+        return optionalFeatures;
+    }
+
+    public void addRequiredFeatures(final List<Feature> features) {
+        this.requiredFeatures.addAll(features);
+    }
+
+    public void addOptionalFeatures(final List<Feature> features) {
+        this.optionalFeatures.addAll(features);
+    }
+}
+
diff --git a/src/main/java/org/apache/sling/feature/resolver/package-info.java b/src/main/java/org/apache/sling/feature/resolver/package-info.java
new file mode 100644
index 0000000..932b891
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/resolver/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+@org.osgi.annotation.versioning.Version("1.0.0")
+package org.apache.sling.feature.resolver;
+
+
diff --git a/src/main/java/org/apache/sling/feature/scanner/ExtensionScanner.java b/src/main/java/org/apache/sling/feature/scanner/ExtensionScanner.java
new file mode 100644
index 0000000..ca5f6cb
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/scanner/ExtensionScanner.java
@@ -0,0 +1,48 @@
+/*
+ * 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;
+
+import java.io.IOException;
+
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.analyser.ContainerDescriptor;
+import org.apache.sling.feature.support.ArtifactManager;
+import org.osgi.annotation.versioning.ConsumerType;
+
+/**
+ * The extension scanner scans an extension.
+ */
+@ConsumerType
+public interface ExtensionScanner  {
+
+    /** A unique (short) id. */
+    String getId();
+
+    /** A human readable name to identify the scanner. */
+    String getName();
+
+    /**
+     * Try to scan the extension and return a descriptor
+     *
+     * @param extension The extension
+     * @param manager Artifact manager
+     * @return The descriptor or {@code null} if the scanner does not know the extension
+     * @throws IOException If an error occurs while scanning the extension or the extension is invalid
+     */
+    ContainerDescriptor scan(Extension extension,
+            ArtifactManager manager) throws IOException;
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/feature/scanner/FrameworkScanner.java b/src/main/java/org/apache/sling/feature/scanner/FrameworkScanner.java
new file mode 100644
index 0000000..8661097
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/scanner/FrameworkScanner.java
@@ -0,0 +1,44 @@
+/*
+ * 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;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.KeyValueMap;
+import org.apache.sling.feature.analyser.BundleDescriptor;
+import org.osgi.annotation.versioning.ConsumerType;
+
+/**
+ * The framework scanner scans the framework
+ */
+@ConsumerType
+public interface FrameworkScanner  {
+
+    /**
+     * Try to scan the artifact
+     * @param framework The framework artifact id
+     * @param file The framework artifact
+     * @param frameworkProps framework properties to launch the framework
+     * @return A descriptor or {@code null}
+     * @throws IOException If an error occurs while scanning the platform or the artifact is invalid
+     */
+    BundleDescriptor scan(ArtifactId framework,
+            File platformFile,
+            KeyValueMap frameworkProps) throws IOException;
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..7f75dd3
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/scanner/impl/ContentPackageDescriptor.java
@@ -0,0 +1,142 @@
+/*
+ * 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 java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.Configuration;
+import org.apache.sling.feature.analyser.ArtifactDescriptor;
+import org.apache.sling.feature.analyser.BundleDescriptor;
+
+/**
+ * Information about a content package.
+ */
+public class ContentPackageDescriptor extends ArtifactDescriptor {
+
+    /** The content package name. */
+    private String name;
+
+    /** Bundles in the content package. */
+    public final List<BundleDescriptor> bundles = new ArrayList<>();
+
+    /** Configurations in the content package. */
+    public final List<Configuration> configs = new ArrayList<>();
+
+    private File artifactFile;
+
+    private Artifact artifact;
+
+    /**
+     * Get the artifact file
+     * @return The artifact file
+     */
+    @Override
+    public File getArtifactFile() {
+        return artifactFile;
+    }
+
+    /**
+     * Get the artifact
+     * @return The artifact
+     */
+    @Override
+    public Artifact getArtifact() {
+        return artifact;
+    }
+
+    /**
+     * Set the artifact
+     * @param artifact The artifact
+     */
+    public void setArtifact(Artifact artifact) {
+        checkLocked();
+        this.artifact = artifact;
+    }
+
+    /**
+     * Set the artifact file
+     * @param artifactFile The artifact file
+     */
+    public void setArtifactFile(File artifactFile) {
+        checkLocked();
+        this.artifactFile = artifactFile;
+    }
+
+    /** Optional: the artifact of the content package. */
+    private Artifact contentPackage;
+
+    /** Optional: the path inside of the content package. */
+    private String contentPath;
+
+    /**
+     * Get the content package
+     * @return The content package or {@code null}
+     */
+    public Artifact getContentPackage() {
+        return contentPackage;
+    }
+
+    /**
+     * Get the content path
+     * @return The content path or {@code null}
+     */
+    public String getContentPath() {
+        return this.contentPath;
+    }
+
+    /**
+     * Whether this artifact is embedded in a content package
+     * @return {@code true} if embedded.
+     */
+    public boolean isEmbeddedInContentPackage() {
+        return this.contentPath != null;
+    }
+
+    /**
+     * Set the information about the content package containing this artifact
+     * @param artifact The package
+     * @param path The path inside the package
+     */
+    public void setContentPackageInfo(final Artifact artifact, final String path) {
+        checkLocked();
+        this.contentPackage = artifact;
+        this.contentPath = path;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(final String value) {
+        checkLocked();
+        this.name = value;
+    }
+
+
+    public boolean hasEmbeddedArtifacts() {
+        return !this.bundles.isEmpty() || !this.configs.isEmpty();
+    }
+
+    @Override
+    public String toString() {
+        return "ContentPackage [" + name + "]";
+    }
+}
+
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
new file mode 100644
index 0000000..90b84c7
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/scanner/impl/ContentPackageScanner.java
@@ -0,0 +1,358 @@
+/*
+ * 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 java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Configuration;
+import org.apache.sling.feature.analyser.BundleDescriptor;
+import org.apache.sling.feature.analyser.impl.BundleDescriptorImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ContentPackageScanner {
+
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    private final byte[] buffer = new byte[65536];
+
+    private enum FileType {
+        BUNDLE,
+        CONFIG,
+        PACKAGE
+    }
+
+    public Set<ContentPackageDescriptor> scan(final Artifact desc, final File file) throws IOException {
+        if (!file.getName().endsWith(".zip") ) {
+            throw new IOException("Artifact seems to be no content package (not a zip file): " + desc.getId().toMvnId());
+        }
+
+        final Set<ContentPackageDescriptor> contentPackages = new HashSet<>();
+        final ContentPackageDescriptor cp = new ContentPackageDescriptor();
+        final int lastDot = file.getName().lastIndexOf(".");
+        cp.setName(file.getName().substring(0, lastDot));
+        cp.setArtifact(desc);
+        cp.setArtifactFile(file);
+
+        extractContentPackage(cp, contentPackages, file);
+
+        contentPackages.add(cp);
+        cp.lock();
+
+        return contentPackages;
+    }
+
+    private void extractContentPackage(final ContentPackageDescriptor cp,
+            final Set<ContentPackageDescriptor> infos,
+            final File archive)
+    throws IOException {
+        logger.debug("Analyzing Content Package {}", archive.getName());
+
+        final File tempDir = Files.createTempDirectory(null).toFile();
+        try {
+            final File toDir = new File(tempDir, archive.getName());
+            toDir.mkdirs();
+
+            final List<File> toProcess = new ArrayList<>();
+
+            try (final ZipInputStream zis = new ZipInputStream(new FileInputStream(archive)) ) {
+                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);
+
+                            FileType fileType = null;
+
+                            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 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 ) {
+                                                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();
+
+                                try (final FileOutputStream fos = new FileOutputStream(newFile)) {
+                                    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
+                                    }
+
+                                    final Artifact bundle = new Artifact(extractArtifactId(tempDir, newFile));
+                                    final BundleDescriptor info = new BundleDescriptorImpl(bundle, newFile, startLevel);
+                                    bundle.getMetadata().put("content-package", cp.getArtifact().getId().toMvnId());
+                                    bundle.getMetadata().put("content-path", contentPath);
+
+                                    cp.bundles.add(info);
+
+                                } else if ( fileType == FileType.CONFIG ) {
+
+                                    final Configuration configEntry = this.process(newFile, cp.getArtifact(), contentPath);
+                                    if ( configEntry != null ) {
+
+                                        cp.configs.add(configEntry);
+                                    }
+
+                                } else if ( fileType == FileType.PACKAGE ) {
+                                    toProcess.add(newFile);
+                                }
+
+                            }
+
+                        }
+                        zis.closeEntry();
+                    }
+                }
+
+            }
+
+            for(final File f : toProcess) {
+                extractContentPackage(cp, infos, f);
+                final ContentPackageDescriptor i = new ContentPackageDescriptor();
+                final int lastDot = f.getName().lastIndexOf(".");
+                i.setName(f.getName().substring(0, lastDot));
+                i.setArtifactFile(f);
+                i.setContentPackageInfo(cp.getArtifact(), f.getName());
+                infos.add(i);
+
+                i.lock();
+            }
+        } finally {
+            if ( tempDir.exists() ) {
+                tempDir.delete();
+            }
+        }
+    }
+
+    private ArtifactId extractArtifactId(final File tempDir, final File bundleFile)
+    throws IOException {
+        logger.debug("Extracting Bundle {}", bundleFile.getName());
+
+        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 FileOutputStream fos = new FileOutputStream(newFile)) {
+                            int len;
+                            while ((len = zis.read(buffer)) > -1) {
+                                fos.write(buffer, 0, len);
+                            }
+                        }
+
+                    }
+                    zis.closeEntry();
+                }
+            }
+
+        }
+
+        // check for maven
+
+        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);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        throw new IOException(bundleFile.getName() + " has no maven coordinates!");
+    }
+
+    private Configuration process(final File configFile,
+            final Artifact packageArtifact,
+            final String contentPath)
+    throws IOException {
+
+        boolean isConfig = true;
+        if ( configFile.getName().endsWith(".xml") ) {
+            final String contents = Files.readAllLines(configFile.toPath()).toString();
+            if ( contents.indexOf("jcr:primaryType=\"sling:OsgiConfig\"") == -1 ) {
+                isConfig = false;
+            }
+        }
+
+        if ( isConfig ) {
+            final String id;
+            if ( ".content.xml".equals(configFile.getName()) ) {
+                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);
+            }
+
+            final String pid, factoryPid;
+            final int slashPos = id.indexOf('-');
+            if ( slashPos == -1 ) {
+                pid = id;
+                factoryPid = null;
+            } else {
+                pid = id.substring(slashPos + 1);
+                factoryPid = id.substring(0, slashPos);
+            }
+
+            final Configuration cfg;
+            if ( factoryPid != null ) {
+                cfg = new Configuration(factoryPid, pid);
+            } else {
+                cfg = new Configuration(pid);
+            }
+            cfg.getProperties().put("content-path", contentPath);
+            cfg.getProperties().put("content-package", packageArtifact.getId().toMvnId());
+
+            return cfg;
+        }
+
+        return null;
+    }
+}
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
new file mode 100644
index 0000000..5c91554
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/scanner/impl/ContentPackagesExtensionScanner.java
@@ -0,0 +1,73 @@
+/*
+ * 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 java.io.File;
+import java.io.IOException;
+import java.util.Set;
+
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.ExtensionType;
+import org.apache.sling.feature.analyser.ContainerDescriptor;
+import org.apache.sling.feature.scanner.ExtensionScanner;
+import org.apache.sling.feature.support.ArtifactManager;
+
+public class ContentPackagesExtensionScanner implements ExtensionScanner {
+
+    @Override
+    public String getId() {
+        return "content-packages";
+    }
+
+    @Override
+    public String getName() {
+        return "Content Packages Scanner";
+    }
+
+    @Override
+    public ContainerDescriptor scan(final Extension extension,
+            final ArtifactManager artifactManager)
+    throws IOException {
+        if (!Extension.NAME_CONTENT_PACKAGES.equals(extension.getName()) ) {
+            return null;
+        }
+        if ( extension.getType() != ExtensionType.ARTIFACTS ) {
+            return null;
+        }
+
+        final ContentPackageScanner scanner = new ContentPackageScanner();
+        final ContainerDescriptor cd = new ContainerDescriptor() {};
+
+        for(final Artifact a : extension.getArtifacts()) {
+            final File file = artifactManager.getArtifactHandler(a.getId().toMvnUrl()).getFile();
+            if ( file == null ) {
+                throw new IOException("Unable to find file for " + a.getId());
+            }
+
+            final Set<ContentPackageDescriptor> pcks = scanner.scan(a, file);
+            for(final ContentPackageDescriptor desc : pcks) {
+                cd.getArtifactDescriptors().add(desc);
+                cd.getBundleDescriptors().addAll(desc.bundles);
+            }
+        }
+
+        cd.lock();
+
+        return cd;
+    }
+}
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
new file mode 100644
index 0000000..eb2124a
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/scanner/impl/FelixFrameworkScanner.java
@@ -0,0 +1,183 @@
+/*
+ * 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.apache.sling.feature.support.util.LambdaUtil.rethrowFunction;
+import static org.apache.sling.feature.support.util.ManifestParser.convertProvideCapabilities;
+import static org.apache.sling.feature.support.util.ManifestParser.normalizeCapabilityClauses;
+import static org.apache.sling.feature.support.util.ManifestParser.parseStandardHeader;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.HashSet;
+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;
+
+import org.apache.sling.commons.osgi.ManifestHeader;
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Capability;
+import org.apache.sling.feature.KeyValueMap;
+import org.apache.sling.feature.analyser.BundleDescriptor;
+import org.apache.sling.feature.scanner.FrameworkScanner;
+import org.apache.sling.feature.support.util.PackageInfo;
+import org.apache.sling.feature.support.util.SubstVarUtil;
+import org.osgi.framework.Constants;
+
+public class FelixFrameworkScanner implements FrameworkScanner {
+
+
+    @Override
+    public BundleDescriptor scan(final ArtifactId framework,
+            final File platformFile,
+            final KeyValueMap frameworkProps)
+    throws IOException {
+        final KeyValueMap fwkProps = getFrameworkProperties(frameworkProps, platformFile);
+        if ( fwkProps == null ) {
+            return null;
+        }
+        final Set<PackageInfo> pcks = calculateSystemPackages(fwkProps);
+        final Set<Capability> capabilities = calculateSystemCapabilities(fwkProps);
+
+        final BundleDescriptor d = new BundleDescriptor() {
+
+            @Override
+            public String getBundleSymbolicName() {
+                return Constants.SYSTEM_BUNDLE_SYMBOLICNAME;
+            }
+
+            @Override
+            public String getBundleVersion() {
+                return framework.getOSGiVersion().toString();
+            }
+
+            @Override
+            public int getBundleStartLevel() {
+                return 0;
+            }
+
+            @Override
+            public File getArtifactFile() {
+                return platformFile;
+            }
+
+            @Override
+            public Artifact getArtifact() {
+                return new Artifact(framework);
+            }
+
+            @Override
+            public Manifest getManifest() {
+                return new Manifest();
+            }
+        };
+        d.getCapabilities().addAll(capabilities);
+        d.getExportedPackages().addAll(pcks);
+        d.lock();
+        return d;
+    }
+
+    private Set<Capability> calculateSystemCapabilities(final KeyValueMap fwkProps) {
+        return Stream.of(
+                    fwkProps.get(Constants.FRAMEWORK_SYSTEMCAPABILITIES),
+                    fwkProps.get(Constants.FRAMEWORK_SYSTEMCAPABILITIES_EXTRA)
+                )
+                .filter(Objects::nonNull)
+                .flatMap(
+                        rethrowFunction(header ->
+                            convertProvideCapabilities(normalizeCapabilityClauses(parseStandardHeader(header), "2")).stream()
+                )).collect(Collectors.toSet());
+    }
+
+    private Set<PackageInfo> calculateSystemPackages(final KeyValueMap fwkProps) {
+        final String system = fwkProps.get(Constants.FRAMEWORK_SYSTEMPACKAGES);
+        final String extra = fwkProps.get(Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA);
+        final Set<PackageInfo> packages = new HashSet<>();
+        for(int i=0;i<2;i++) {
+            final String value = (i == 0 ? system : extra);
+            if ( value != null ) {
+                final ManifestHeader header = ManifestHeader.parse(value);
+                for(final ManifestHeader.Entry entry : header.getEntries()) {
+                    String version = entry.getAttributeValue("version");
+                    if ( version == null ) {
+                        version = "0.0.0";
+                    }
+
+                    final PackageInfo exportedPackageInfo = new PackageInfo(entry.getValue(),
+                            version, false);
+                    packages.add(exportedPackageInfo);
+                }
+            }
+        }
+        return packages;
+    }
+
+    private static final String DEFAULT_PROPERTIES = "default.properties";
+
+    private KeyValueMap getFrameworkProperties(final KeyValueMap appProps, final File framework)
+    throws IOException {
+        final Map<String, Properties> propsMap = new HashMap<>();
+        try (final ZipInputStream zis = new ZipInputStream(new FileInputStream(framework)) ) {
+            boolean done = false;
+            while ( !done ) {
+                final ZipEntry entry = zis.getNextEntry();
+                if ( entry == null ) {
+                    done = true;
+                } else {
+                    final String entryName = entry.getName();
+                    if ( entryName.endsWith(".properties") ) {
+                        final Properties props = new Properties();
+                        props.load(zis);
+
+                        propsMap.put(entryName, props);
+                    }
+                    zis.closeEntry();
+                }
+            }
+        }
+
+        final Properties defaultMap = propsMap.get(DEFAULT_PROPERTIES);
+        if ( defaultMap == null ) {
+            return null;
+        }
+
+        final KeyValueMap frameworkProps = new KeyValueMap();
+        frameworkProps.putAll(appProps);
+
+        // replace variables
+        defaultMap.put("java.specification.version",
+                System.getProperty("java.specification.version", "1.8"));
+        for(final Object name : defaultMap.keySet()) {
+            if ( frameworkProps.get(name.toString()) == null ) {
+                final String value = (String)defaultMap.get(name);
+                final String substValue = SubstVarUtil.substVars(value, name.toString(), null, (Map) defaultMap);
+                frameworkProps.put(name.toString(), substValue);
+            }
+        }
+
+        return frameworkProps;
+    }
+}
diff --git a/src/main/java/org/apache/sling/feature/scanner/impl/RepoInitScanner.java b/src/main/java/org/apache/sling/feature/scanner/impl/RepoInitScanner.java
new file mode 100644
index 0000000..7b4e221
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/scanner/impl/RepoInitScanner.java
@@ -0,0 +1,62 @@
+/*
+ * 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 java.io.IOException;
+
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.ExtensionType;
+import org.apache.sling.feature.Requirement;
+import org.apache.sling.feature.analyser.ContainerDescriptor;
+import org.apache.sling.feature.scanner.ExtensionScanner;
+import org.apache.sling.feature.support.ArtifactManager;
+
+public class RepoInitScanner implements ExtensionScanner {
+
+    @Override
+    public String getId() {
+        return "repoinit";
+    }
+
+    @Override
+    public String getName() {
+        return "Apache Sling Repoinit Scanner";
+    }
+
+    @Override
+    public ContainerDescriptor scan(final Extension extension,
+            final ArtifactManager artifactManager)
+    throws IOException {
+        if (!Extension.NAME_REPOINIT.equals(extension.getName()) ) {
+            return null;
+        }
+        if ( extension.getType() != ExtensionType.TEXT ) {
+            return null;
+        }
+
+        final ContainerDescriptor cd = new ContainerDescriptor() {};
+
+        final Requirement req = new Requirement("osgi.implementation");
+        req.getDirectives().put("filter",
+                "(&(osgi.implementation=org.apache.sling.jcr.repoinit)(version>=1.0)(!(version>=2.0)))");
+        cd.getRequirements().add(req);
+
+        cd.lock();
+
+        return cd;
+    }
+}
diff --git a/src/main/java/org/apache/sling/feature/scanner/package-info.java b/src/main/java/org/apache/sling/feature/scanner/package-info.java
new file mode 100644
index 0000000..4c65dc3
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/scanner/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+@org.osgi.annotation.versioning.Version("1.0.0")
+package org.apache.sling.feature.scanner;
+
+
diff --git a/src/main/resources/META-INF/services/org.apache.sling.feature.analyser.task.AnalyserTask b/src/main/resources/META-INF/services/org.apache.sling.feature.analyser.task.AnalyserTask
new file mode 100644
index 0000000..dabd8fb
--- /dev/null
+++ b/src/main/resources/META-INF/services/org.apache.sling.feature.analyser.task.AnalyserTask
@@ -0,0 +1,5 @@
+org.apache.sling.feature.analyser.task.impl.CheckBundleExportsImports
+org.apache.sling.feature.analyser.task.impl.CheckBundlesForInitialContent
+org.apache.sling.feature.analyser.task.impl.CheckBundlesForResources
+org.apache.sling.feature.analyser.task.impl.CheckRequirementsCapabilities
+
diff --git a/src/main/resources/META-INF/services/org.apache.sling.feature.scanner.ExtensionScanner b/src/main/resources/META-INF/services/org.apache.sling.feature.scanner.ExtensionScanner
new file mode 100644
index 0000000..643c7cf
--- /dev/null
+++ b/src/main/resources/META-INF/services/org.apache.sling.feature.scanner.ExtensionScanner
@@ -0,0 +1,3 @@
+org.apache.sling.feature.scanner.impl.ContentPackagesExtensionScanner
+org.apache.sling.feature.scanner.impl.RepoInitScanner
+
diff --git a/src/main/resources/META-INF/services/org.apache.sling.feature.scanner.FrameworkScanner b/src/main/resources/META-INF/services/org.apache.sling.feature.scanner.FrameworkScanner
new file mode 100644
index 0000000..ad10c99
--- /dev/null
+++ b/src/main/resources/META-INF/services/org.apache.sling.feature.scanner.FrameworkScanner
@@ -0,0 +1,2 @@
+org.apache.sling.feature.scanner.impl.FelixFrameworkScanner
+
diff --git a/src/test/java/org/apache/sling/feature/analyser/AnalyserTest.java b/src/test/java/org/apache/sling/feature/analyser/AnalyserTest.java
new file mode 100644
index 0000000..872ec3c
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/analyser/AnalyserTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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.analyser;
+
+import org.apache.sling.feature.Application;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.analyser.service.Analyser;
+import org.apache.sling.feature.analyser.service.Scanner;
+import org.apache.sling.feature.support.ArtifactManager;
+import org.apache.sling.feature.support.ArtifactManagerConfig;
+import org.apache.sling.feature.support.FeatureUtil;
+import org.apache.sling.feature.support.json.FeatureJSONReader;
+import org.junit.Test;
+
+import java.io.InputStreamReader;
+import java.io.Reader;
+
+import static junit.framework.TestCase.fail;
+
+public class AnalyserTest {
+    @Test
+    public void testAnalyserWithCompleteFeature() throws Exception {
+        final Scanner scanner = new Scanner(new ArtifactManagerConfig());
+        final Analyser analyser = new Analyser(scanner);
+        try ( final Reader reader = new InputStreamReader(AnalyserTest.class.getResourceAsStream("/feature_complete.json"),
+                "UTF-8") ) {
+            Feature feature = FeatureJSONReader.read(reader, "feature");
+
+            Application app = FeatureUtil.assembleApplication(null, ArtifactManager.getArtifactManager(new ArtifactManagerConfig()), feature);
+
+            analyser.analyse(app);
+        }
+    }
+
+    @Test
+    public void testAnalyserWithInCompleteFeature() throws Exception {
+        final Scanner scanner = new Scanner(new ArtifactManagerConfig());
+        final Analyser analyser = new Analyser(scanner);
+        try ( final Reader reader = new InputStreamReader(AnalyserTest.class.getResourceAsStream("/feature_incomplete.json"),
+                "UTF-8") ) {
+            Feature feature = FeatureJSONReader.read(reader, "feature");
+
+            Application app = FeatureUtil.assembleApplication(null, ArtifactManager.getArtifactManager(new ArtifactManagerConfig()), feature);
+
+            try {
+                analyser.analyse(app);
+
+                fail("Expected an exception");
+            }
+            catch (Exception ex) {
+                // Pass
+            }
+        }
+    }
+}
diff --git a/src/test/resources/feature_complete.json b/src/test/resources/feature_complete.json
new file mode 100644
index 0000000..692ddfe
--- /dev/null
+++ b/src/test/resources/feature_complete.json
@@ -0,0 +1,31 @@
+{
+    "id" : "test/test.complete/0.1",
+
+    "bundles" : {
+      "1" : [
+        "org.apache.sling/org.apache.sling.commons.log/5.0.0",
+        "org.apache.sling/org.apache.sling.commons.logservice/1.0.6",
+        "org.slf4j/jcl-over-slf4j/1.7.21",
+        "org.slf4j/log4j-over-slf4j/1.7.21",
+        "org.slf4j/slf4j-api/1.7.21",
+        "org.apache.felix/org.apache.felix.configadmin/1.8.14"
+      ],
+      "4" : [
+        "org.apache.felix/org.apache.felix.eventadmin/1.4.8",
+        "org.apache.felix/org.apache.felix.metatype/1.1.2",
+        "org.apache.felix/org.apache.felix.scr/2.0.12"
+      ],
+      "5" : [
+        "org.apache.felix/org.apache.felix.http.jetty/3.4.2",
+        "org.apache.felix/org.apache.felix.http.servlet-api/1.1.2",
+        "commons-io/commons-io/2.5",
+        "commons-fileupload/commons-fileupload/1.3.2",
+        "org.apache.felix/org.apache.felix.inventory/1.0.4",
+        "org.apache.felix/org.apache.felix.webconsole.plugins.ds/2.0.6",
+        "org.apache.felix/org.apache.felix.webconsole.plugins.event/1.1.6",
+        "org.apache.felix/org.apache.felix.webconsole.plugins.packageadmin/1.0.4",
+        "org.apache.felix/org.apache.felix.webconsole/4.3.4",
+        "org.apache.sling/org.apache.sling.commons.log.webconsole/1.0.0"
+      ]
+    }
+}
diff --git a/src/test/resources/feature_incomplete.json b/src/test/resources/feature_incomplete.json
new file mode 100644
index 0000000..fee0e7f
--- /dev/null
+++ b/src/test/resources/feature_incomplete.json
@@ -0,0 +1,33 @@
+{
+    "id" : "test/test.incomplete/0.1",
+
+    "bundles" : {
+      "1" : [
+        "org.apache.sling/org.apache.sling.commons.log/5.0.0",
+        "org.apache.sling/org.apache.sling.commons.logservice/1.0.6",
+        "org.slf4j/jcl-over-slf4j/1.7.21",
+        "org.slf4j/log4j-over-slf4j/1.7.21",
+        "org.slf4j/slf4j-api/1.7.21",
+        "org.apache.felix/org.apache.felix.configadmin/1.8.14"
+      ],
+      "4" : [
+        "org.apache.felix/org.apache.felix.eventadmin/1.4.8",
+        "org.apache.felix/org.apache.felix.metatype/1.1.2",
+        "org.apache.felix/org.apache.felix.scr/2.0.12"
+      ],
+      "5" : [
+        "org.apache.felix/org.apache.felix.http.servlet-api/1.1.2",
+        "commons-io/commons-io/2.5",
+        "commons-fileupload/commons-fileupload/1.3.2",
+        "org.apache.felix/org.apache.felix.inventory/1.0.4",
+        "org.apache.felix/org.apache.felix.webconsole.plugins.ds/2.0.6",
+        "org.apache.felix/org.apache.felix.webconsole.plugins.event/1.1.6",
+        "org.apache.felix/org.apache.felix.webconsole.plugins.packageadmin/1.0.4",
+        "org.apache.felix/org.apache.felix.webconsole/4.3.4",
+        "org.apache.sling/org.apache.sling.commons.log.webconsole/1.0.0"
+      ],
+      "6" : [
+        "org.apache.sling/org.apache.sling.i18n/2.5.8"
+      ]
+    }
+}

-- 
To stop receiving notification emails like this one, please contact
davidb@apache.org.