SLING-9087 : Support creation of feature archives. Change format of archive to a pure repository archive, make sure to exclude META-INF
diff --git a/src/main/java/org/apache/sling/feature/io/archive/ArchiveReader.java b/src/main/java/org/apache/sling/feature/io/archive/ArchiveReader.java
index 00926ce..6bbad77 100644
--- a/src/main/java/org/apache/sling/feature/io/archive/ArchiveReader.java
+++ b/src/main/java/org/apache/sling/feature/io/archive/ArchiveReader.java
@@ -19,11 +19,14 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.util.Arrays;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 import java.util.jar.JarEntry;
 import java.util.jar.JarInputStream;
 import java.util.jar.Manifest;
+import java.util.stream.Collectors;
 
 import org.apache.sling.feature.Artifact;
 import org.apache.sling.feature.ArtifactId;
@@ -62,48 +65,33 @@
      * @return The feature models
      * @throws IOException If anything goes wrong
      */
-    @SuppressWarnings("resource")
     public static Set<Feature> read(final InputStream in,
                              final ArtifactConsumer consumer)
     throws IOException {
         final JarInputStream jis = new JarInputStream(in);
 
-        // check manifest
-        final Manifest manifest = jis.getManifest();
-        if ( manifest == null ) {
-            throw new IOException("Not a feature model archive - manifest is missing.");
-        }
-        // check manifest header
-        final String version = manifest.getMainAttributes().getValue(ArchiveWriter.MANIFEST_HEADER);
-        if ( version == null ) {
-            throw new IOException("Not a feature model archive - manifest header is missing.");
-        }
-        // validate manifest header
-        try {
-            final int number = Integer.valueOf(version);
-            if ( number < 1 || number > ArchiveWriter.ARCHIVE_VERSION ) {
-                throw new IOException("Not a feature model archive - invalid manifest header value: " + version);
-            }
-        } catch (final NumberFormatException nfe) {
-            throw new IOException("Not a feature model archive - invalid manifest header value: " + version);
-        }
+        // validate manifest and get feature ids
+        final String[] featureIds = checkHeaderAndExtractContents(jis.getManifest());
+        final List<String> featurePaths = Arrays.asList(featureIds).stream()
+                .map(id -> ArtifactId.fromMvnId(id).toMvnPath()).collect(Collectors.toList());
 
-        final Set<ArtifactId> artifacts = new HashSet<>();
 
         // read contents
         final Set<Feature> features = new HashSet<>();
+        final Set<ArtifactId> artifacts = new HashSet<>();
 
         JarEntry entry = null;
         while ( ( entry = jis.getNextJarEntry() ) != null ) {
-            if (!entry.isDirectory() && entry.getName().startsWith(ArchiveWriter.FEATURE_MODEL_PREFIX)) { // feature
-                features.add(FeatureJSONReader.read(new InputStreamReader(jis, "UTF-8"), entry.getName()));
-            } else if ( !entry.isDirectory() && entry.getName().startsWith(ArchiveWriter.ARTIFACTS_PREFIX) ) { // artifact
-                final ArtifactId id = ArtifactId
-                        .fromMvnPath(entry.getName().substring(ArchiveWriter.ARTIFACTS_PREFIX.length()));
-                if (consumer != null) {
-                    consumer.consume(id, jis);
+            if (!entry.isDirectory() && !entry.getName().startsWith("META-INF/")) {
+                if (featurePaths.contains(entry.getName())) { // feature
+                    features.add(FeatureJSONReader.read(new InputStreamReader(jis, "UTF-8"), entry.getName()));
+                } else { // artifact
+                    final ArtifactId id = ArtifactId.fromMvnPath(entry.getName());
+                    if (consumer != null) {
+                        consumer.consume(id, jis);
+                    }
+                    artifacts.add(id);
                 }
-                artifacts.add(id);
             }
             jis.closeEntry();
         }
@@ -131,4 +119,32 @@
         }
         return features;
     }
+
+    private static String[] checkHeaderAndExtractContents(final Manifest manifest) throws IOException {
+        if (manifest == null) {
+            throw new IOException("Not a feature model archive - manifest is missing.");
+        }
+        // check version header
+        final String version = manifest.getMainAttributes().getValue(ArchiveWriter.VERSION_HEADER);
+        if (version == null) {
+            throw new IOException("Not a feature model archive - version manifest header is missing.");
+        }
+        // validate version header
+        try {
+            final int number = Integer.valueOf(version);
+            if (number < 1 || number > ArchiveWriter.ARCHIVE_VERSION) {
+                throw new IOException("Not a feature model archive - invalid manifest header value: " + version);
+            }
+        } catch (final NumberFormatException nfe) {
+            throw new IOException("Not a feature model archive - invalid manifest header value: " + version);
+        }
+
+        // check contents header
+        final String contents = manifest.getMainAttributes().getValue(ArchiveWriter.CONTENTS_HEADER);
+        if (contents == null) {
+            throw new IOException("Not a feature model archive - contents manifest header is missing.");
+        }
+
+        return contents.split(",");
+    }
 }
diff --git a/src/main/java/org/apache/sling/feature/io/archive/ArchiveWriter.java b/src/main/java/org/apache/sling/feature/io/archive/ArchiveWriter.java
index 6f48967..e9de733 100644
--- a/src/main/java/org/apache/sling/feature/io/archive/ArchiveWriter.java
+++ b/src/main/java/org/apache/sling/feature/io/archive/ArchiveWriter.java
@@ -22,11 +22,13 @@
 import java.io.OutputStreamWriter;
 import java.io.Writer;
 import java.net.URL;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Set;
 import java.util.jar.JarEntry;
 import java.util.jar.JarOutputStream;
 import java.util.jar.Manifest;
+import java.util.stream.Collectors;
 import java.util.zip.Deflater;
 
 import org.apache.sling.feature.Artifact;
@@ -39,22 +41,20 @@
 
 /**
  * The feature archive writer can be used to create an archive based on a
- * feature model. The archive contains the feature model file and all artifacts.
+ * feature model. The archive contains the feature model file and all artifacts
+ * using a maven repository layout.
  */
 public class ArchiveWriter {
 
     /** The manifest header marking an archive as a feature archive. */
-    public static final String MANIFEST_HEADER = "Feature-Archive-Version";
+    public static final String VERSION_HEADER = "Feature-Archive-Version";
+
+    /** The manifest header listing the features in this archive. */
+    public static final String CONTENTS_HEADER = "Feature-Archive-Contents";
 
     /** Current support version of the feature model archive. */
     public static final int ARCHIVE_VERSION = 1;
 
-    /** The directory in the archive holding the features */
-    public static final String FEATURE_MODEL_PREFIX = "features/";
-
-    /** The directory in the archive holding the artifacts */
-    public static final String ARTIFACTS_PREFIX = "artifacts/";
-
     /**
      * Create a feature model archive. The output stream will not be closed by this
      * method. The caller must call {@link JarOutputStream#close()} or
@@ -82,7 +82,9 @@
         // create manifest
         final Manifest manifest = (baseManifest == null ? new Manifest() : new Manifest(baseManifest));
         manifest.getMainAttributes().putValue("Manifest-Version", "1.0");
-        manifest.getMainAttributes().putValue(MANIFEST_HEADER, String.valueOf(ARCHIVE_VERSION));
+        manifest.getMainAttributes().putValue(VERSION_HEADER, String.valueOf(ARCHIVE_VERSION));
+        manifest.getMainAttributes().putValue(CONTENTS_HEADER, String.join(",", Arrays.asList(features).stream()
+                .map(feature -> feature.getId().toMvnId()).collect(Collectors.toList())));
 
         // create archive
         final JarOutputStream jos = new JarOutputStream(out, manifest);
@@ -90,7 +92,7 @@
         // write models first with compression enabled
         jos.setLevel(Deflater.BEST_COMPRESSION);
         for (final Feature feature : features) {
-            final JarEntry entry = new JarEntry(FEATURE_MODEL_PREFIX.concat(feature.getId().toMvnPath()));
+            final JarEntry entry = new JarEntry(feature.getId().toMvnPath());
             jos.putNextEntry(entry);
             final Writer writer = new OutputStreamWriter(jos, "UTF-8");
             FeatureJSONWriter.write(writer, feature);
@@ -126,7 +128,7 @@
             final JarOutputStream jos,
             final byte[] buffer) throws IOException {
         if ( artifacts.add(artifact.getId())) {
-            final JarEntry artifactEntry = new JarEntry(ARTIFACTS_PREFIX + artifact.getId().toMvnPath());
+            final JarEntry artifactEntry = new JarEntry(artifact.getId().toMvnPath());
             jos.putNextEntry(artifactEntry);
 
             final URL url = provider.provide(artifact.getId());