SLING-4728 - support nested provisioning models (not fully tested yet)

git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1685564 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/pom.xml b/pom.xml
index 7831805..804ab53 100644
--- a/pom.xml
+++ b/pom.xml
@@ -135,7 +135,7 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.provisioning.model</artifactId>
-            <version>1.1.0</version>
+            <version>1.2.0</version>
             <scope>provided</scope>
         </dependency>
          <dependency>
diff --git a/src/main/java/org/apache/sling/crankstart/launcher/ArtifactsVisitor.java b/src/main/java/org/apache/sling/crankstart/launcher/ArtifactsVisitor.java
index dd3e0e6..f3a41a9 100644
--- a/src/main/java/org/apache/sling/crankstart/launcher/ArtifactsVisitor.java
+++ b/src/main/java/org/apache/sling/crankstart/launcher/ArtifactsVisitor.java
@@ -24,7 +24,7 @@
 
 /** Visit the Artifacts of a Model */
 public abstract class ArtifactsVisitor {
-    private final Model model;
+    protected final Model model;
     
     public ArtifactsVisitor(Model m) {
         model = m;
diff --git a/src/main/java/org/apache/sling/crankstart/launcher/Launcher.java b/src/main/java/org/apache/sling/crankstart/launcher/Launcher.java
index abf2616..cedc244 100644
--- a/src/main/java/org/apache/sling/crankstart/launcher/Launcher.java
+++ b/src/main/java/org/apache/sling/crankstart/launcher/Launcher.java
@@ -38,16 +38,16 @@
 import org.apache.sling.provisioning.model.Feature;
 import org.apache.sling.provisioning.model.Model;
 import org.apache.sling.provisioning.model.ModelUtility;
-import org.apache.sling.provisioning.model.RunMode;
 import org.apache.sling.provisioning.model.ModelUtility.VariableResolver;
+import org.apache.sling.provisioning.model.RunMode;
 import org.apache.sling.provisioning.model.io.ModelReader;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /** Launch an OSGi app instance using the Sling provisioning model */
 public class Launcher {
-    private final Model model = new Model();
-    private final Logger log = LoggerFactory.getLogger(getClass());
+    private Model model = new Model();
+    private static final Logger log = LoggerFactory.getLogger(Launcher.class);
     
     public static final String CRANKSTART_FEATURE = ":crankstart";
     public static final String MODEL_KEY = "model";
@@ -85,7 +85,9 @@
         }
     };
     
-    public Launcher(String ... args) throws IOException {
+    public Launcher(String ... args) throws Exception {
+        MavenResolver.setup();
+
         // Find all files to read and sort the list, to be deterministic
         final SortedSet<File> toRead = new TreeSet<File>();
         
@@ -100,19 +102,32 @@
                 toRead.add(f);
             }
         }
-        
+
+        // Merge all model files 
         for(File f : toRead) {
             mergeModel(f);
         }
+        
+        // And merge nested models (supporting one level of nesting only so far)
+        new NestedModelsMerger(model).visit();
+        computeEffectiveModel();
+    }
+    
+    public void computeEffectiveModel() {
+        model = ModelUtility.getEffectiveModel(model, overridingVariableResolver);
+    }
+    
+    public Model getModel() {
+        return model;
     }
     
     /** Can be called before launch() to read and merge additional models.
      *  @param r provisioning model to read, closed by this method after reading */ 
-    public void mergeModel(Reader r, String sourceInfo) throws IOException {
+    public static void mergeModel(Model mergeInto, Reader r, String sourceInfo) throws IOException {
+        log.info("Merging provisioning model {}", sourceInfo);
         try {
-            log.info("Merging provisioning model {}", sourceInfo);
             final Model m = ModelReader.read(r, sourceInfo);
-            ModelUtility.merge(model, ModelUtility.getEffectiveModel(m, overridingVariableResolver));
+            ModelUtility.merge(mergeInto, m);
         } finally {
             r.close();
         }
@@ -120,13 +135,10 @@
     
     /** Can be called before launch() to read and merge additional models */
     public void mergeModel(File f) throws IOException {
-        mergeModel(new BufferedReader(new FileReader(f)), f.getAbsolutePath());
+        mergeModel(model, new BufferedReader(new FileReader(f)), f.getAbsolutePath());
     }
     
     public void launch() throws Exception {
-        // Enable pax URL for mvn: protocol
-        System.setProperty( "java.protocol.handler.pkgs", "org.ops4j.pax.url" );
-
         // Setup initial classpath to launch the OSGi framework
         for(URL u : getClasspathURLs(model, CRANKSTART_FEATURE)) {
             addToClasspath(u);
@@ -165,7 +177,7 @@
             for(RunMode rm : f.getRunModes()) {
                 for(ArtifactGroup g : rm.getArtifactGroups()) {
                     for(Artifact a : g) {
-                        final String url = mvnUrl(a);
+                        final String url = MavenResolver.mvnUrl(a);
                         try {
                             result.add(new URL(url));
                         } catch(MalformedURLException e) {
@@ -180,10 +192,6 @@
         return result;
     }
     
-    public static String mvnUrl(Artifact a) {
-        return "mvn:" + a.getGroupId() + "/" + a.getArtifactId() + "/" + a.getVersion();
-    }
-    
      public static void main(String [] args) throws Exception {
         if(args.length < 1) {
             System.err.println("Usage: " + Launcher.class.getSimpleName() + " provisioning-model [provisioning-model ...]"); 
diff --git a/src/main/java/org/apache/sling/crankstart/launcher/MavenResolver.java b/src/main/java/org/apache/sling/crankstart/launcher/MavenResolver.java
new file mode 100644
index 0000000..521ee56
--- /dev/null
+++ b/src/main/java/org/apache/sling/crankstart/launcher/MavenResolver.java
@@ -0,0 +1,57 @@
+/*
+ * 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.crankstart.launcher;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.apache.sling.provisioning.model.Artifact;
+
+/** Resolve artifacts using Maven URLs - assumes Pax URL is installed */ 
+public class MavenResolver {
+    public static void setup() {
+        // Enable pax URL for mvn: protocol
+        System.setProperty( "java.protocol.handler.pkgs", "org.ops4j.pax.url" );
+    }
+    
+    public static String mvnUrl(Artifact a) {
+        final StringBuilder sb = new StringBuilder();
+        sb
+        .append("mvn:")
+        .append(a.getGroupId())
+        .append("/")
+        .append(a.getArtifactId())
+        .append("/")
+        .append(a.getVersion());
+        
+        if(a.getType() != null) {
+            sb.append("/").append(a.getType());
+        }
+        
+        if(a.getClassifier() != null) {
+            sb.append("/").append(a.getClassifier());
+        }
+        
+        return sb.toString();
+    }
+    
+    public static InputStream resolve(Artifact a) throws MalformedURLException, IOException {
+        return new URL(mvnUrl(a)).openStream();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/crankstart/launcher/NestedModelsMerger.java b/src/main/java/org/apache/sling/crankstart/launcher/NestedModelsMerger.java
new file mode 100644
index 0000000..dec9f2c
--- /dev/null
+++ b/src/main/java/org/apache/sling/crankstart/launcher/NestedModelsMerger.java
@@ -0,0 +1,78 @@
+/*
+ * 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.crankstart.launcher;
+
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.sling.provisioning.model.Artifact;
+import org.apache.sling.provisioning.model.ArtifactGroup;
+import org.apache.sling.provisioning.model.Feature;
+import org.apache.sling.provisioning.model.Model;
+import org.apache.sling.provisioning.model.RunMode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/** Merge nested models, provided by slingfeature/slingstart artifacts */
+public class NestedModelsMerger extends ArtifactsVisitor {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    private List<Artifact> toMerge;
+    
+    public NestedModelsMerger(Model m) {
+        super(m);
+    }
+    
+    @Override
+    public synchronized void visit() throws Exception {
+        toMerge = new ArrayList<Artifact>();
+        super.visit();
+        log.info("{} nested models found in provisioning model", toMerge.size());
+        
+        for(Artifact a : toMerge) {
+            log.info("Resolving and merging nested model {}", a);
+            InputStream is = null;
+            Reader r = null;
+            try {
+                is = MavenResolver.resolve(a);
+                r = new InputStreamReader(is);
+                Launcher.mergeModel(model, r, a.toString());
+            } catch(Exception e) {
+                log.error("Failed to read nested model " + a, e);
+            } finally {
+                if(r != null) {
+                    r.close();
+                }
+                if(is != null) {
+                    is.close();
+                }
+            }
+        }
+    }
+    
+    @Override
+    protected void visitArtifact(Feature f, RunMode rm, ArtifactGroup g, Artifact a) throws Exception {
+        final String classifier = a.getClassifier();
+        if("slingstart".equals(classifier ) || "slingfeature".equals(classifier)) {
+            toMerge.add(a);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/crankstart/launcher/BasicLauncherIT.java b/src/test/java/org/apache/sling/crankstart/launcher/BasicLauncherIT.java
index 70ba369..5847aac 100644
--- a/src/test/java/org/apache/sling/crankstart/launcher/BasicLauncherIT.java
+++ b/src/test/java/org/apache/sling/crankstart/launcher/BasicLauncherIT.java
@@ -34,7 +34,7 @@
     public final RetryRule retryRule = new RetryRule();
     
     @BeforeClass
-    public static void setupClass() throws IOException {
+    public static void setupClass() throws Exception {
         C = new CrankstartSetup();
         C.setup();
         osgiConsole = new WebconsoleClient(C.getBaseUrl(), U.ADMIN, U.ADMIN);
diff --git a/src/test/java/org/apache/sling/crankstart/launcher/CrankstartSetup.java b/src/test/java/org/apache/sling/crankstart/launcher/CrankstartSetup.java
index 04bdb2d..4b8f3af 100644
--- a/src/test/java/org/apache/sling/crankstart/launcher/CrankstartSetup.java
+++ b/src/test/java/org/apache/sling/crankstart/launcher/CrankstartSetup.java
@@ -61,12 +61,13 @@
         return result;
     }
     
-    private static void mergeModelResource(Launcher launcher, String path) throws IOException {
+    private static void mergeModelResource(Launcher launcher, String path) throws Exception {
         final InputStream is = CrankstartSetup.class.getResourceAsStream(path);
         assertNotNull("Expecting test resource to be found:" + path, is);
         final Reader input = new InputStreamReader(is);
         try {
-            launcher.mergeModel(input, path);
+            Launcher.mergeModel(launcher.getModel(), input, path);
+            launcher.computeEffectiveModel();
         } finally {
             input.close();
         }
@@ -76,7 +77,7 @@
         return baseUrl;
     }
      
-    synchronized void setup() throws IOException {
+    synchronized void setup() throws Exception {
         if(crankstartThread != null) {
             return;
         }
diff --git a/src/test/java/org/apache/sling/crankstart/launcher/RunModeAIT.java b/src/test/java/org/apache/sling/crankstart/launcher/RunModeAIT.java
index 552756b..99ed68b 100644
--- a/src/test/java/org/apache/sling/crankstart/launcher/RunModeAIT.java
+++ b/src/test/java/org/apache/sling/crankstart/launcher/RunModeAIT.java
@@ -26,7 +26,7 @@
     public final RetryRule retryRule = new RetryRule();
     
     @BeforeClass
-    public static void setupClass() throws IOException {
+    public static void setupClass() throws Exception {
         System.setProperty(RunModeFilter.SLING_RUN_MODES, RUN_MODES);
         C.setup();
         osgiConsole = new WebconsoleClient(C.getBaseUrl(), U.ADMIN, U.ADMIN);
diff --git a/src/test/java/org/apache/sling/crankstart/launcher/RunModeBIT.java b/src/test/java/org/apache/sling/crankstart/launcher/RunModeBIT.java
index c00e8f3..bad8b2f 100644
--- a/src/test/java/org/apache/sling/crankstart/launcher/RunModeBIT.java
+++ b/src/test/java/org/apache/sling/crankstart/launcher/RunModeBIT.java
@@ -26,7 +26,7 @@
     public final RetryRule retryRule = new RetryRule();
     
     @BeforeClass
-    public static void setupClass() throws IOException {
+    public static void setupClass() throws Exception {
         System.setProperty(RunModeFilter.SLING_RUN_MODES, RUN_MODES);
         C.setup();
         osgiConsole = new WebconsoleClient(C.getBaseUrl(), U.ADMIN, U.ADMIN);
diff --git a/src/test/resources/launchpad-addons.txt b/src/test/resources/launchpad-addons.txt
deleted file mode 100644
index d787f38..0000000
--- a/src/test/resources/launchpad-addons.txt
+++ /dev/null
@@ -1,55 +0,0 @@
-#
-#  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.
-#
-
-# Additional items needed to be able to start the Sling Launchpad
-# using the :crankstart feature found in this folder + this file + 
-# the standard model from launchpad/builder so that it passes 
-# the Sling integration tests. As of June 11th, 2015 this is
-# not fully working, some integration tests fail. Probably due,
-# at least in part, to run modes not being implemented yet, which
-# means we possibly get conflicting bundles.
-#
-# Using this, currently 11 Sling tests from launchpad/integration-tests
-# fail, out of 528, when starting Sling with the following commands:
-# rm -rf CRANKSTART derby.log jackrabbit
-# java -Dcrankstart.model.http.port=8080 \
-# -Dsling.run.modes=:standalone,jackrabbit -jar \
-# target/org.apache.sling.crankstart.launcher-1.9.9-SNAPSHOT.jar \ 
-# src/test/resources/crankstart-model.txt \
-# src/test/resources/launchpad-addons.txt \
-# ../../..//launchpad/builder/src/main/provisioning   
-
-[feature name=crankstart.launchpad.addons]
-
-[artifacts]
-  org.apache.sling/org.apache.sling.launchpad.api/1.1.0
-  org.apache.sling/org.apache.sling.launchpad.karaf/0.1.1-SNAPSHOT
-  org.apache.sling/org.apache.sling.launchpad.test-services/2.0.9-SNAPSHOT
-  org.apache.sling/org.apache.sling.junit.core/1.0.10
-  org.apache.sling/org.apache.sling.junit.scriptable/1.0.10
-  org.apache.sling/org.apache.sling.launchpad.test-fragment/2.0.9-SNAPSHOT
-  org.apache.sling/org.apache.sling.servlets.compat/1.0.2
-
-[configurations]
-  integrationTestsConfig
-    message="This test config should be loaded at startup"
-
-  org.apache.sling.servlets.resolver.SlingServletResolver
-    # Set the servlet resolver's cache size to zero for testing
-    servletresolver.cacheSize=I"0"
diff --git a/src/test/resources/provisioning-model/crankstart-tests.txt b/src/test/resources/provisioning-model/crankstart-tests.txt
index b43499e..a0a0dbd 100644
--- a/src/test/resources/provisioning-model/crankstart-tests.txt
+++ b/src/test/resources/provisioning-model/crankstart-tests.txt
@@ -24,6 +24,9 @@
   org.apache.sling/org.apache.sling.junit.core/1.0.10
   org.apache.sling/org.apache.sling.commons.mime/2.1.8
   org.apache.sling/org.apache.sling.settings/1.3.6
+  
+  # TODO: Test a nested model file
+  # org.apache.sling/org.apache.sling.launchpad/8-SNAPSHOT/slingstart
 
 [artifacts runModes=A]
   org.apache.sling/org.apache.sling.api/2.9.0
diff --git a/src/test/resources/sling-launchpad.txt b/src/test/resources/sling-launchpad.txt
new file mode 100644
index 0000000..936e735
--- /dev/null
+++ b/src/test/resources/sling-launchpad.txt
@@ -0,0 +1,45 @@
+#
+#  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.
+#
+
+# Start the Sling Launchpad including whatever is required to
+# run the Sling integration tests.
+# This model is meant to be used in addition to the base 
+# crankstart-model
+[feature name=sling.launchpad]
+
+[settings]
+  org.apache.sling.commons.log.julenabled=true
+  
+[artifacts]
+  # karaf bunde provides Sling Launchpad startup services
+  org.apache.sling/org.apache.sling.launchpad.karaf/0.1.1-SNAPSHOT
+  
+  # The launchpad itself
+  org.apache.sling/org.apache.sling.launchpad/8-SNAPSHOT/txt/slingfeature
+  
+  # Sling integration tests support
+  org.apache.sling/org.apache.sling.launchpad.test-bundles/0.0.1-SNAPSHOT/txt/slingstart
+    
+[configurations]
+  integrationTestsConfig
+    message="This test config should be loaded at startup"
+
+  org.apache.sling.servlets.resolver.SlingServletResolver
+    # Set the servlet resolver's cache size to zero for testing
+    servletresolver.cacheSize=I"0"
\ No newline at end of file