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.