You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/11/07 10:27:21 UTC

[sling-slingstart-maven-plugin] 04/13: SLING-5149 - Generate OSGi subsystem intermediary file in slingstart-maven-plugin

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

rombert pushed a commit to annotated tag slingstart-maven-plugin-1.4.0
in repository https://gitbox.apache.org/repos/asf/sling-slingstart-maven-plugin.git

commit b98555ae50014f9532229eae7d1a796a36b51f48
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Thu Nov 5 17:05:46 2015 +0000

    SLING-5149 - Generate OSGi subsystem intermediary file in
    slingstart-maven-plugin
    
    Submitted-By: David Bosschaert
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/tooling/maven/slingstart-maven-plugin@1712818 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |  52 ++++-
 .../sling/maven/slingstart/PreparePackageMojo.java | 211 +++++++++++++++++-
 src/main/resources/subsystem-base/readme.txt       |  36 ++++
 .../maven/slingstart/PreparePackageMojoTest.java   | 239 +++++++++++++++++++++
 4 files changed, 527 insertions(+), 11 deletions(-)

diff --git a/pom.xml b/pom.xml
index 554292c..1d30ec8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -86,7 +86,7 @@
        <dependency>
            <groupId>org.apache.sling</groupId>
            <artifactId>org.apache.sling.provisioning.model</artifactId>
-           <version>1.3.0</version>
+           <version>1.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.maven</groupId>
@@ -168,5 +168,55 @@
                 </exclusion>
             </exclusions>
         </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <version>1.10.19</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        
+        <!-- The following artifacts are purely used by the unit tests 
+             so these dependencies ensures that they are in the .m2 directory -->
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.classloader</artifactId>
+            <version>1.3.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.contentdetection</artifactId>
+            <version>1.0.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.json</artifactId>
+            <version>2.0.12</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.mime</artifactId>
+            <version>2.1.8</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.osgi</artifactId>
+            <version>2.3.0</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.threads</artifactId>
+            <version>3.2.0</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/src/main/java/org/apache/sling/maven/slingstart/PreparePackageMojo.java b/src/main/java/org/apache/sling/maven/slingstart/PreparePackageMojo.java
index 8e3d5e1..3871246 100644
--- a/src/main/java/org/apache/sling/maven/slingstart/PreparePackageMojo.java
+++ b/src/main/java/org/apache/sling/maven/slingstart/PreparePackageMojo.java
@@ -16,15 +16,25 @@
  */
 package org.apache.sling.maven.slingstart;
 
+import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.FileWriter;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.nio.file.Files;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
 import java.util.TreeSet;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.jar.Attributes;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
 
 import org.apache.commons.io.IOUtils;
 import org.apache.felix.cm.file.ConfigurationHandler;
@@ -41,9 +51,11 @@ import org.apache.maven.plugins.annotations.ResolutionScope;
 import org.apache.sling.provisioning.model.ArtifactGroup;
 import org.apache.sling.provisioning.model.Configuration;
 import org.apache.sling.provisioning.model.Feature;
+import org.apache.sling.provisioning.model.FeatureTypes;
 import org.apache.sling.provisioning.model.Model;
 import org.apache.sling.provisioning.model.ModelConstants;
 import org.apache.sling.provisioning.model.RunMode;
+import org.apache.sling.provisioning.model.Section;
 import org.codehaus.plexus.archiver.ArchiverException;
 import org.codehaus.plexus.archiver.UnArchiver;
 import org.codehaus.plexus.archiver.manager.ArchiverManager;
@@ -60,6 +72,7 @@ import org.codehaus.plexus.util.FileUtils;
         threadSafe = true
     )
 public class PreparePackageMojo extends AbstractSlingStartMojo {
+    private static final String ALL_RUNMODES_KEY = "_all_";
 
     private static final String BASE_DESTINATION = "resources";
 
@@ -93,6 +106,10 @@ public class PreparePackageMojo extends AbstractSlingStartMojo {
     public void execute() throws MojoExecutionException, MojoFailureException {
         final Model model = ProjectHelper.getEffectiveModel(this.project, getResolverOptions());
 
+        execute(model);
+    }
+
+    void execute(final Model model) throws MojoExecutionException {
         this.prepareGlobal(model);
         this.prepareStandaloneApp(model);
         this.prepareWebapp(model);
@@ -177,22 +194,34 @@ public class PreparePackageMojo extends AbstractSlingStartMojo {
         for(final Feature feature : model.getFeatures()) {
             if ( feature.isSpecial() && !feature.getName().equals(ModelConstants.FEATURE_BOOT)) {
                 continue;
-            }
-            for(final RunMode runMode : feature.getRunModes()) {
-                if ( packageRunMode == null ) {
-                    if ( runMode.isSpecial() ) {
-                        continue;
-                    }
-                    this.buildContentsMap(model, runMode, contentsMap, feature.getName().equals(ModelConstants.FEATURE_BOOT));
-                } else {
-                    if ( runMode.isRunMode(packageRunMode) ) {
+            } else if (FeatureTypes.SUBSYSTEM_APPLICATION.equals(feature.getType()) ||
+                    FeatureTypes.SUBSYSTEM_COMPOSITE.equals(feature.getType()) ||
+                    FeatureTypes.SUBSYSTEM_FEATURE.equals(feature.getType())) {
+                buildSubsystemBase(contentsMap, feature);
+            } else {
+                for(final RunMode runMode : feature.getRunModes()) {
+                    if ( packageRunMode == null ) {
+                        if ( runMode.isSpecial() ) {
+                            continue;
+                        }
                         this.buildContentsMap(model, runMode, contentsMap, feature.getName().equals(ModelConstants.FEATURE_BOOT));
+                    } else {
+                        if ( runMode.isRunMode(packageRunMode) ) {
+                            this.buildContentsMap(model, runMode, contentsMap, feature.getName().equals(ModelConstants.FEATURE_BOOT));
+                        }
                     }
                 }
             }
         }
     }
 
+    private void buildSubsystemBase(final Map<String, File> contentsMap, final Feature feature) throws MojoExecutionException {
+        AtomicInteger startLevelHolder = new AtomicInteger(); // Used as output argument
+        File subsystemFile = createSubsystemBaseFile(feature, startLevelHolder);
+        if (subsystemFile != null)
+            contentsMap.put(getPathForArtifact(startLevelHolder.get(), subsystemFile.getName()), subsystemFile);
+    }
+
     /**
      * Build a list of all artifacts from this run mode
      */
@@ -235,6 +264,164 @@ public class PreparePackageMojo extends AbstractSlingStartMojo {
         }
     }
 
+
+    private File createSubsystemBaseFile(Feature feature, AtomicInteger startLevelHolder) throws MojoExecutionException {
+        File subsystemFile = new File(getTmpDir(), feature.getName() + ".subsystem-base");
+        if (subsystemFile.exists()) {
+            // This subsystem has already been created
+            // TODO is there a better way to avoid calling this multiple times?
+            return null;
+        }
+
+        startLevelHolder.set(-1);
+
+        // The runmodes information has to be the first item in the archive so that we always have it available when the
+        // archive is being processed. For this reason a Jar file is used here as it's guaranteed to store the manifest
+        // first.
+        Manifest runModesManifest = getRunModesManifest(feature);
+
+        getLog().info("Creating subsystem base file: " + subsystemFile.getName());
+        subsystemFile.getParentFile().mkdirs();
+
+        try (JarOutputStream os = new JarOutputStream(new FileOutputStream(subsystemFile), runModesManifest)) {
+            Map<String, Integer> bsnStartOrderMap = new HashMap<>();
+
+            for (RunMode rm : feature.getRunModes()) {
+                for (ArtifactGroup ag : rm.getArtifactGroups()) {
+                    int startOrder = ag.getStartLevel(); // For subsystems the start level on the artifact group is used as start order.
+
+                    for (org.apache.sling.provisioning.model.Artifact a : ag) {
+                        Artifact artifact = ModelUtils.getArtifact(this.project, this.mavenSession, this.artifactHandlerManager, this.resolver,
+                                a.getGroupId(), a.getArtifactId(), a.getVersion(), a.getType(), a.getClassifier());
+                        File artifactFile = artifact.getFile();
+                        String entryName = getEntryName(artifactFile, startOrder);
+
+                        ZipEntry ze = new ZipEntry(entryName);
+                        try {
+                            os.putNextEntry(ze);
+                            Files.copy(artifactFile.toPath(), os);
+                        } finally {
+                            os.closeEntry();
+                        }
+                    }
+                }
+            }
+
+            int sl = createSubsystemManifest(feature, bsnStartOrderMap, os);
+            if (sl != -1)
+                startLevelHolder.set(sl);
+            addReadme(os);
+        } catch (IOException ioe) {
+            throw new MojoExecutionException("Problem creating subsystem .esa file " + subsystemFile, ioe);
+        }
+        return subsystemFile;
+    }
+
+    private Manifest getRunModesManifest(Feature feature) throws MojoExecutionException {
+        Map<String, StringBuilder> runModes = new HashMap<>();
+
+        for (RunMode rm : feature.getRunModes()) {
+            for (ArtifactGroup ag : rm.getArtifactGroups()) {
+                int startOrder = ag.getStartLevel(); // For subsystems the start level on the artifact group is used as start order.
+
+                for (org.apache.sling.provisioning.model.Artifact a : ag) {
+                    Artifact artifact = ModelUtils.getArtifact(this.project, this.mavenSession, this.artifactHandlerManager, this.resolver,
+                            a.getGroupId(), a.getArtifactId(), a.getVersion(), a.getType(), a.getClassifier());
+                    File artifactFile = artifact.getFile();
+                    String entryName = getEntryName(artifactFile, startOrder);
+
+                    String [] runModeNames = rm.getNames();
+                    if (runModeNames == null)
+                        runModeNames = new String[] {ALL_RUNMODES_KEY};
+
+                    for (String runModeName : runModeNames) {
+                        StringBuilder sb = runModes.get(runModeName);
+                        if (sb == null) {
+                            sb = new StringBuilder();
+                            runModes.put(runModeName, sb);
+                        } else {
+                            sb.append('|');
+                        }
+
+                        sb.append(entryName);
+                    }
+                }
+            }
+        }
+
+        Manifest mf = new Manifest();
+        Attributes attrs = mf.getMainAttributes();
+        attrs.putValue("Manifest-Version", "1.0"); // Manifest does not work without this value
+        attrs.putValue("About-This-Manifest", "This is not a real manifest, it is used as information when this archive is transformed into a real subsystem .esa file");
+        for (Map.Entry<String, StringBuilder> entry : runModes.entrySet()) {
+            attrs.putValue(entry.getKey(), entry.getValue().toString());
+        }
+        return mf;
+    }
+
+    private String getEntryName(File artifactFile, int startOrder) {
+        return "Potential_Bundles/" + startOrder + "/" + artifactFile.getName();
+    }
+
+    // This manifest will be used as the basis for the OSGI-INF/SUBSYSTEM.MF file when the real
+    // .esa file is generated. However since some contents of that file depend on the actual
+    // runmode that is being executed, additional information will be added to the SUBSYSTEM.MF
+    // file at startup time before it's finalized (example: Subsystem-Content).
+    private int createSubsystemManifest(Feature feature,
+            Map<String, Integer> startOrderMap, ZipOutputStream os) throws IOException {
+        int subsystemStartLevel = -1;
+        ZipEntry ze = new ZipEntry("SUBSYSTEM-MANIFEST-BASE.MF");
+        try {
+            os.putNextEntry(ze);
+
+            Manifest mf = new Manifest();
+            Attributes attributes = mf.getMainAttributes();
+            attributes.putValue("Manifest-Version", "1.0"); // Manifest does not work without this value
+            attributes.putValue("Subsystem-SymbolicName", feature.getName());
+            attributes.putValue("Subsystem-Version", "1"); // Version must be an integer (cannot be a long), TODO better idea?
+            attributes.putValue("Subsystem-Type", feature.getType());
+            for (Section section : feature.getAdditionalSections("subsystem-manifest")) {
+                String sl = section.getAttributes().get("startLevel");
+                try {
+                    subsystemStartLevel = Integer.parseInt(sl);
+                } catch (NumberFormatException nfe) {
+                    // Not a valid start level
+                }
+
+                BufferedReader br = new BufferedReader(new StringReader(section.getContents()));
+                String line = null;
+                while ((line = br.readLine()) != null) {
+                    int idx = line.indexOf(':');
+                    if (idx > 0) {
+                        String key = line.substring(0, idx);
+                        String value;
+                        idx++;
+                        if (line.length() > idx)
+                            value = line.substring(idx);
+                        else
+                            value = "";
+                        attributes.putValue(key.trim(), value.trim());
+                    }
+                }
+            }
+            mf.write(os);
+        } finally {
+            os.closeEntry();
+        }
+
+        return subsystemStartLevel;
+    }
+
+    private void addReadme(ZipOutputStream os) throws IOException {
+        ZipEntry ze = new ZipEntry("readme.txt");
+        try (InputStream is = getClass().getResourceAsStream("/subsystem-base/readme.txt")) {
+            os.putNextEntry(ze);
+            IOUtils.copy(is, os);
+        } finally {
+            os.closeEntry();
+        }
+    }
+
     /**
      * Build the settings for the given packaging run mode
      */
@@ -385,12 +572,16 @@ public class PreparePackageMojo extends AbstractSlingStartMojo {
         }
     }
 
+    private String getPathForArtifact(final int startLevel, final String artifactName) {
+        return getPathForArtifact(startLevel, artifactName, null, false);
+    }
+
     /**
      * Get the relative path for an artifact.
      */
     private String getPathForArtifact(final int startLevel, final String artifactName, final RunMode rm, final boolean isBoot) {
         final Set<String> runModesList = new TreeSet<String>();
-        if (rm.getNames() != null ) {
+        if ( rm != null && rm.getNames() != null ) {
             for(final String mode : rm.getNames()) {
                 runModesList.add(mode);
             }
diff --git a/src/main/resources/subsystem-base/readme.txt b/src/main/resources/subsystem-base/readme.txt
new file mode 100644
index 0000000..29c6711
--- /dev/null
+++ b/src/main/resources/subsystem-base/readme.txt
@@ -0,0 +1,36 @@
+A .subsystem-base jar file is produced from the sling provisioning model by the sling-maven-plugin as an intermediary file that is turned into a subsystem file .esa at runtime by the org.apache.sling.installer.factory.subsystems-base bundle. The actual content of the subsystem at runtime is determined by the current run mode. The transformer creates an .esa file that contains the right resources according to the current runmode.
+
+A subsystem-base file is generated from features in the sling provisioning model of type osgi.subsystem.application, osgi.subsystem.composite or osgi.subsystem.feature. For example:
+
+[feature name=mysubsystem type=osgi.subsystem.feature]
+
+[:subsystem-manifest startLevel=20]
+  Subsystem-Description: Extra subsystem headers can go here
+  Subsystem-Copyright: (c) 2015 for example
+
+[artifacts]
+  com.foo.bar/bundle/1
+
+[artifacts startLevel=10]
+  com.foo.bar/bundle1/1.2.3
+  com.foo.bar/bundle2/1.0.0
+
+[artifacts startLevel=15 runModes=myrunmode]
+  com.foo.bar/bundle3/0.0.1
+
+The above provisioning model description will generate a mysubsystem.subsystem-base jar file. The bundles in the subsystem will *all* have start level 20, from the startLevel in the :subsystem-manifest section. The mysubsystem.subsystem-base file will be placed in the install/20/mysubsystem.subsystem-base location of the generated sling-maven-plugin generated slingstart artifact. The startLevel attribute on the artifacts section are transformed into start-order attributes on the Subsyste [...]
+The generated mysubsystem.subsystem-base file will have the following content:
+
+META-INF/MANIFEST.MF
+Potential_Bundles/0/bundle-1.jar
+Potential_Bundles/10/bundle1-1.2.3.jar
+Potential_Bundles/10/bundle2-1.0.0.jar
+Potential_Bundles/15/bundle3-0.0.1.jar
+SUBSYSTEM-MANIFEST-BASE.MF
+readme.txt (this file).
+
+The META-INF/MANIFEST.MF file contains information about which resources should be deployed based on runmode. There is a special identifier _all_ which lists resources that should be deployed in all runmodes.
+The Potential_Bundles locations hold the actual resources in directories that denote their start-order.
+The SUBSYSTEM-MANIFEST-BASE.MF file is used as the basis for the OSGI-INF/SUBSYSTEM.MF file in the .esa file. The Subsystem-SymbolicName, Subsystem-Type, Subsystem-Version and Subsystem-Content are automatically generated. Additional headers listed under the [:subsystem-manifest] section are added to the ultimate SUBSYSTEM.MF. The Subsystem-Content is generated by the SubsystemBaseTransformer based on the current runmode.
+
+This file is included in the .subsystem-base file as a readme as this is not a common file format.
diff --git a/src/test/java/org/apache/sling/maven/slingstart/PreparePackageMojoTest.java b/src/test/java/org/apache/sling/maven/slingstart/PreparePackageMojoTest.java
new file mode 100644
index 0000000..4f8a427
--- /dev/null
+++ b/src/test/java/org/apache/sling/maven/slingstart/PreparePackageMojoTest.java
@@ -0,0 +1,239 @@
+/*
+ * 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.maven.slingstart;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.lang.reflect.Field;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.maven.artifact.DefaultArtifact;
+import org.apache.maven.artifact.handler.ArtifactHandler;
+import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
+import org.apache.maven.artifact.resolver.ArtifactResolver;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.model.Build;
+import org.apache.maven.project.MavenProject;
+import org.apache.sling.provisioning.model.Model;
+import org.apache.sling.provisioning.model.io.ModelReader;
+import org.codehaus.plexus.archiver.UnArchiver;
+import org.codehaus.plexus.archiver.manager.ArchiverManager;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+public class PreparePackageMojoTest {
+    @Test
+    public void testSubsystemBaseGeneration1() throws Exception {
+        // Provide the system with some artifacts that are known to be in the local .m2 repo
+        // These are explicitly included in the test section of the pom.xml
+        PreparePackageMojo ppm = getMojoUnderTest(
+                "org.apache.sling/org.apache.sling.commons.classloader/1.3.2",
+                "org.apache.sling/org.apache.sling.commons.classloader/1.3.2/app",
+                "org.apache.sling/org.apache.sling.commons.contentdetection/1.0.2",
+                "org.apache.sling/org.apache.sling.commons.json/2.0.12",
+                "org.apache.sling/org.apache.sling.commons.mime/2.1.8",
+                "org.apache.sling/org.apache.sling.commons.osgi/2.3.0",
+                "org.apache.sling/org.apache.sling.commons.threads/3.2.0");
+
+        try {
+            // The launchpad feature is a prerequisite for the model
+            String modelTxt =
+                    "[feature name=:launchpad]\n" +
+                    "[artifacts]\n" +
+                    "  org.apache.sling/org.apache.sling.commons.classloader/1.3.2\n" +
+                    "" +
+                    "[feature name=test1 type=osgi.subsystem.composite]\n" +
+                    "" +
+                    "[:subsystem-manifest startLevel=123]\n" +
+                    "  Subsystem-Description: Extra subsystem headers can go here including very long ones that would span multiple lines in a manifest\n" +
+                    "  Subsystem-Copyright: (c) 2015 yeah!\n" +
+                    "" +
+                    "[artifacts]\n" +
+                    "  org.apache.sling/org.apache.sling.commons.osgi/2.3.0\n" +
+                    "" +
+                    "[artifacts startLevel=10]\n" +
+                    "  org.apache.sling/org.apache.sling.commons.json/2.0.12\n" +
+                    "  org.apache.sling/org.apache.sling.commons.mime/2.1.8\n" +
+                    "" +
+                    "[artifacts startLevel=20 runModes=foo,bar]\n" +
+                    "  org.apache.sling/org.apache.sling.commons.threads/3.2.0\n" +
+                    "" +
+                    "[artifacts startLevel=100 runModes=bar]\n" +
+                    "  org.apache.sling/org.apache.sling.commons.contentdetection/1.0.2\n";
+            Model model = ModelReader.read(new StringReader(modelTxt), null);
+            ppm.execute(model);
+
+            File generatedFile = new File(ppm.getTmpDir() + "/test1.subsystem-base");
+            try (JarFile jf = new JarFile(generatedFile)) {
+                // Test META-INF/MANIFEST.MF
+                Manifest mf = jf.getManifest();
+                Attributes attrs = mf.getMainAttributes();
+                String expected = "Potential_Bundles/0/org.apache.sling.commons.osgi-2.3.0.jar|"
+                        + "Potential_Bundles/10/org.apache.sling.commons.json-2.0.12.jar|"
+                        + "Potential_Bundles/10/org.apache.sling.commons.mime-2.1.8.jar";
+                assertEquals(expected, attrs.getValue("_all_"));
+                assertEquals("Potential_Bundles/20/org.apache.sling.commons.threads-3.2.0.jar", attrs.getValue("foo"));
+                assertEquals("Potential_Bundles/20/org.apache.sling.commons.threads-3.2.0.jar|"
+                        + "Potential_Bundles/100/org.apache.sling.commons.contentdetection-1.0.2.jar", attrs.getValue("bar"));
+
+                // Test SUBSYSTEM-MANIFEST-BASE.MF
+                ZipEntry smbZE = jf.getEntry("SUBSYSTEM-MANIFEST-BASE.MF");
+                try (InputStream smbIS = jf.getInputStream(smbZE)) {
+                    Manifest smbMF = new Manifest(smbIS);
+                    Attributes smbAttrs = smbMF.getMainAttributes();
+                    assertEquals("test1", smbAttrs.getValue("Subsystem-SymbolicName"));
+                    assertEquals("osgi.subsystem.composite", smbAttrs.getValue("Subsystem-Type"));
+                    assertEquals("(c) 2015 yeah!", smbAttrs.getValue("Subsystem-Copyright"));
+                    assertEquals("Extra subsystem headers can go here including very long ones "
+                            + "that would span multiple lines in a manifest",
+                            smbAttrs.getValue("Subsystem-Description"));
+                }
+
+                // Test embedded bundles
+                File mrr = getMavenRepoRoot();
+                File soj = getMavenArtifactFile(mrr, "org.apache.sling", "org.apache.sling.commons.osgi", "2.3.0");
+                ZipEntry sojZE = jf.getEntry("Potential_Bundles/0/org.apache.sling.commons.osgi-2.3.0.jar");
+                try (InputStream is = jf.getInputStream(sojZE)) {
+                    assertArtifactsEqual(soj, is);
+                }
+
+                File sjj = getMavenArtifactFile(mrr, "org.apache.sling", "org.apache.sling.commons.json", "2.0.12");
+                ZipEntry sjZE = jf.getEntry("Potential_Bundles/10/org.apache.sling.commons.json-2.0.12.jar");
+                try (InputStream is = jf.getInputStream(sjZE)) {
+                    assertArtifactsEqual(sjj, is);
+                }
+
+                File smj = getMavenArtifactFile(mrr, "org.apache.sling", "org.apache.sling.commons.mime", "2.1.8");
+                ZipEntry smjZE = jf.getEntry("Potential_Bundles/10/org.apache.sling.commons.mime-2.1.8.jar");
+                try (InputStream is = jf.getInputStream(smjZE)) {
+                    assertArtifactsEqual(smj, is);
+                }
+
+                File stj = getMavenArtifactFile(mrr, "org.apache.sling", "org.apache.sling.commons.threads", "3.2.0");
+                ZipEntry stjZE = jf.getEntry("Potential_Bundles/20/org.apache.sling.commons.threads-3.2.0.jar");
+                try (InputStream is = jf.getInputStream(stjZE)) {
+                    assertArtifactsEqual(stj, is);
+                }
+
+                File ctj = getMavenArtifactFile(mrr, "org.apache.sling", "org.apache.sling.commons.contentdetection", "1.0.2");
+                ZipEntry ctjZE = jf.getEntry("Potential_Bundles/100/org.apache.sling.commons.contentdetection-1.0.2.jar");
+                try (InputStream is = jf.getInputStream(ctjZE)) {
+                    assertArtifactsEqual(ctj, is);
+                }
+            }
+        } finally {
+            FileUtils.deleteDirectory(new File(ppm.project.getBuild().getDirectory()));
+        }
+    }
+
+    private void assertArtifactsEqual(File f, InputStream is) throws IOException {
+        byte[] bytes1 = Files.readAllBytes(f.toPath());
+        byte[] bytes2 = IOUtils.toByteArray(is);
+        assertArrayEquals("Bytes not equal on file " + f.getName(), bytes1, bytes2);
+    }
+
+    private PreparePackageMojo getMojoUnderTest(String ... knownArtifacts) throws Exception {
+        File mrr = getMavenRepoRoot();
+
+        ArtifactHandler ah = Mockito.mock(ArtifactHandler.class);
+        ArtifactHandlerManager ahm = Mockito.mock(ArtifactHandlerManager.class);
+        Mockito.when(ahm.getArtifactHandler(Mockito.anyString())).thenReturn(ah);
+
+        Set<org.apache.maven.artifact.Artifact> artifacts = new HashSet<>();
+        for (String s : knownArtifacts) {
+            String[] parts = s.split("[/]");
+            switch (parts.length) {
+            case 3:
+                artifacts.add(getMavenArtifact(mrr, ah, parts[0], parts[1], parts[2]));
+                break;
+            case 4:
+                artifacts.add(getMavenArtifact(mrr, ah, parts[0], parts[1], parts[2], parts[3]));
+                break;
+            default: throw new IllegalStateException(s);
+            }
+        }
+
+        MavenProject mavenPrj = new MavenProject();
+        Build build = new Build();
+        Path tempDir = Files.createTempDirectory(getClass().getSimpleName());
+        build.setOutputDirectory(tempDir.toString());
+        build.setDirectory(tempDir.toString());
+        mavenPrj.setBuild(build);
+        mavenPrj.setDependencyArtifacts(artifacts);
+
+        PreparePackageMojo ppm = new PreparePackageMojo();
+        ppm.mavenSession = Mockito.mock(MavenSession.class);
+        ppm.project = mavenPrj;
+        ArchiverManager am = Mockito.mock(ArchiverManager.class);
+        UnArchiver ua = Mockito.mock(UnArchiver.class);
+        Mockito.when(am.getUnArchiver(Mockito.isA(File.class))).thenReturn(ua);
+        setPrivateField(ppm, "archiverManager", am);
+        setPrivateField(ppm, "artifactHandlerManager", ahm);
+        setPrivateField(ppm, "resolver", Mockito.mock(ArtifactResolver.class));
+        return ppm;
+    }
+
+    private org.apache.maven.artifact.Artifact getMavenArtifact(File repoRoot, ArtifactHandler ah, String gid, String aid, String ver) {
+        return getMavenArtifact(repoRoot, ah, gid, aid, ver, null);
+    }
+
+    private org.apache.maven.artifact.Artifact getMavenArtifact(File repoRoot, ArtifactHandler ah, String gid, String aid, String ver, String classifier) {
+        DefaultArtifact art = new DefaultArtifact(gid, aid, ver, "compile", "jar", classifier, ah);
+        art.setFile(getMavenArtifactFile(repoRoot, gid, aid, ver));
+        return art;
+    }
+
+    private File getMavenArtifactFile(File repoRoot, String gid, String aid, String ver) {
+        return new File(repoRoot, gid.replace('.', '/') + '/' + aid + '/' + ver + '/' + aid + '-' + ver + ".jar");
+    }
+
+    private File getMavenRepoRoot() throws IOException {
+        URL res = getClass().getClassLoader().getResource(
+                Test.class.getName().replace('.', '/') + ".class");
+
+        String u = res.toExternalForm();
+        if (u.startsWith("jar:"))
+            u = u.substring(4);
+
+        int idx = u.indexOf("junit");
+        if (idx < 0)
+            throw new IllegalStateException("Cannot infer maven repo root: " + res);
+
+        return new File(new URL(u.substring(0, idx)).getFile());
+    }
+
+    private void setPrivateField(Object obj, String name, Object val) throws Exception {
+        Field f = obj.getClass().getDeclaredField(name);
+        f.setAccessible(true);
+        f.set(obj, val);
+    }
+}

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.