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());