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 2019/03/29 13:25:33 UTC

[sling-org-apache-sling-feature-launcher] branch master updated: SLING-8334 Get Launcher Extension API ready for 1.0 release

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-launcher.git


The following commit(s) were added to refs/heads/master by this push:
     new 4103794  SLING-8334 Get Launcher Extension API ready for 1.0 release
     new 9eaf372  Merge pull request #8 from bosschaert/SLING-8334
4103794 is described below

commit 4103794287784f51997866715d17466effe9e291
Author: David Bosschaert <bo...@adobe.com>
AuthorDate: Thu Mar 28 15:53:13 2019 +0000

    SLING-8334 Get Launcher Extension API ready for 1.0 release
    
    The extensions now get provided with a single ExtensionContext object
    that combines the functionality that was previously spread over the
    LauncherPrepareContext and ExtensionInstallationContext.
    Additionally, a lookup is provided for a Feature Model based on Artifact
    ID. This can be used by extension plugins to read feature models
    referenced in an extension.
---
 .../launcher/impl/ExtensionContextImpl.java        | 107 +++++++++++++++++++++
 .../feature/launcher/impl/FeatureProcessor.java    |  18 ++--
 .../sling/feature/launcher/impl/Installation.java  |   8 +-
 .../apache/sling/feature/launcher/impl/Main.java   |  14 ++-
 .../extensions/handlers/ContentPackageHandler.java |   7 +-
 .../impl/extensions/handlers/RepoInitHandler.java  |   7 +-
 ...tallationContext.java => ExtensionContext.java} |  39 ++++++--
 .../launcher/spi/extensions/ExtensionHandler.java  |   3 +-
 .../launcher/impl/ExtensionContextImplTest.java    |  52 ++++++++++
 src/test/resources/test-feature.json               |   3 +
 10 files changed, 223 insertions(+), 35 deletions(-)

diff --git a/src/main/java/org/apache/sling/feature/launcher/impl/ExtensionContextImpl.java b/src/main/java/org/apache/sling/feature/launcher/impl/ExtensionContextImpl.java
new file mode 100644
index 0000000..8e543b6
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/launcher/impl/ExtensionContextImpl.java
@@ -0,0 +1,107 @@
+/*
+ * 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.launcher.impl;
+
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.io.json.FeatureJSONReader;
+import org.apache.sling.feature.launcher.spi.LauncherPrepareContext;
+import org.apache.sling.feature.launcher.spi.extensions.ExtensionContext;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.Dictionary;
+import java.util.List;
+import java.util.Map;
+
+class ExtensionContextImpl implements ExtensionContext {
+    private final Installation installation;
+    private final LauncherPrepareContext prepareContext;
+    private final Map<ArtifactId, Feature> loadedFeatures;
+
+    ExtensionContextImpl(LauncherPrepareContext lpc, Installation inst, Map<ArtifactId, Feature> featureMap) {
+        prepareContext = lpc;
+        installation = inst;
+        loadedFeatures = featureMap;
+    }
+
+    @Override
+    public void addBundle(Integer startLevel, File file) {
+        installation.addBundle(startLevel, file);
+    }
+
+    @Override
+    public void addInstallableArtifact(File file) {
+        installation.addInstallableArtifact(file);
+    }
+
+    @Override
+    public void addConfiguration(String pid, String factoryPid, Dictionary<String, Object> properties) {
+        installation.addConfiguration(pid, factoryPid, properties);
+    }
+
+    @Override
+    public void addFrameworkProperty(String key, String value) {
+        installation.addFrameworkProperty(key, value);
+    }
+
+    @Override
+    public Map<String, String> getFrameworkProperties() {
+        return installation.getFrameworkProperties();
+    }
+
+    @Override
+    public Map<Integer, List<File>> getBundleMap() {
+        return installation.getBundleMap();
+    }
+
+    @Override
+    public List<Object[]> getConfigurations() {
+        return installation.getConfigurations();
+    }
+
+    @Override
+    public List<File> getInstallableArtifacts() {
+        return installation.getInstallableArtifacts();
+    }
+
+    @Override
+    public void addAppJar(File jar) {
+        prepareContext.addAppJar(jar);
+    }
+
+    @Override
+    public File getArtifactFile(ArtifactId artifact) throws IOException {
+        return prepareContext.getArtifactFile(artifact);
+    }
+
+    @Override
+    public Feature getFeature(ArtifactId artifact) throws IOException {
+        Feature f = loadedFeatures.get(artifact);
+        if (f != null)
+            return f;
+
+        File file = getArtifactFile(artifact);
+        if (file == null)
+            return null;
+
+        try (FileReader r = new FileReader(file)) {
+            return FeatureJSONReader.read(r, artifact.toMvnUrl());
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/feature/launcher/impl/FeatureProcessor.java b/src/main/java/org/apache/sling/feature/launcher/impl/FeatureProcessor.java
index e63abfa..3f3ad26 100644
--- a/src/main/java/org/apache/sling/feature/launcher/impl/FeatureProcessor.java
+++ b/src/main/java/org/apache/sling/feature/launcher/impl/FeatureProcessor.java
@@ -19,8 +19,6 @@ package org.apache.sling.feature.launcher.impl;
 import java.io.File;
 import java.io.FileReader;
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -52,11 +50,12 @@ public class FeatureProcessor {
      * Read the features and prepare the application
      * @param config The current configuration
      * @param artifactManager The artifact manager
+     * @param loadedFeatures This map will be populated with features that were loaded as part of this process
      * @return The merged feature representing the application
      * @throws IOException when an IO exception occurs during application creation
      */
     public static Feature createApplication(final LauncherConfig config,
-            final ArtifactManager artifactManager) throws IOException
+            final ArtifactManager artifactManager, final Map<ArtifactId, Feature> loadedFeatures) throws IOException
     {
         final BuilderContext builderContext = new BuilderContext(id -> {
             try {
@@ -89,21 +88,20 @@ public class FeatureProcessor {
             ServiceLoader.load(PostProcessHandler.class).iterator(), Spliterator.ORDERED), false)
                 .toArray(PostProcessHandler[]::new));
 
-        List<Feature> features = new ArrayList<>();
-
         for (final String initFile : config.getFeatureFiles()) {
         	Main.LOG().debug("Reading feature file {}", initFile);
             final ArtifactHandler featureArtifact = artifactManager.getArtifactHandler(initFile);
             try (final FileReader r = new FileReader(featureArtifact.getFile())) {
                 final Feature f = FeatureJSONReader.read(r, featureArtifact.getUrl());
-                features.add(f);
+                loadedFeatures.put(f.getId(), f);
             } catch (Exception ex) {
                 throw new IOException("Error reading feature: " + initFile, ex);
             }
         }
 
         // TODO make feature id configurable
-        final Feature app = FeatureBuilder.assemble(ArtifactId.fromMvnId("group:assembled:1.0.0"), builderContext, features.toArray(new Feature[0]));
+        final Feature app = FeatureBuilder.assemble(ArtifactId.fromMvnId("group:assembled:1.0.0"), builderContext, loadedFeatures.values().toArray(new Feature[0]));
+        loadedFeatures.put(app.getId(), app);
 
         // TODO: this sucks
         for (Artifact bundle : app.getBundles()) {
@@ -126,10 +124,12 @@ public class FeatureProcessor {
      * @param ctx The launcher prepare context
      * @param config The launcher configuration
      * @param app The merged feature to launch
+     * @param loadedFeatures The features previously loaded by the launcher, this includes features that
+     * were passed in via file:// URLs from the commandline
      * @throws Exception when something goes wrong
      */
     public static void prepareLauncher(final LauncherPrepareContext ctx, final LauncherConfig config,
-            final Feature app) throws Exception {
+            final Feature app, Map<ArtifactId, Feature> loadedFeatures) throws Exception {
         for(final Map.Entry<Integer, List<Artifact>> entry : app.getBundles().getBundlesByStartOrder().entrySet()) {
             for(final Artifact a : entry.getValue()) {
                 final File artifactFile = ctx.getArtifactFile(a.getId());
@@ -156,7 +156,7 @@ public class FeatureProcessor {
         extensions: for(final Extension ext : app.getExtensions()) {
             for (ExtensionHandler handler : ServiceLoader.load(ExtensionHandler.class,  FeatureProcessor.class.getClassLoader()))
             {
-                if (handler.handle(ext, ctx, config.getInstallation())) {
+                if (handler.handle(new ExtensionContextImpl(ctx, config.getInstallation(), loadedFeatures), ext)) {
                     continue extensions;
                 }
             }
diff --git a/src/main/java/org/apache/sling/feature/launcher/impl/Installation.java b/src/main/java/org/apache/sling/feature/launcher/impl/Installation.java
index dbc17a6..40c8851 100644
--- a/src/main/java/org/apache/sling/feature/launcher/impl/Installation.java
+++ b/src/main/java/org/apache/sling/feature/launcher/impl/Installation.java
@@ -16,6 +16,8 @@
  */
 package org.apache.sling.feature.launcher.impl;
 
+import org.apache.sling.feature.launcher.spi.LauncherRunContext;
+
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Dictionary;
@@ -23,13 +25,10 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-import org.apache.sling.feature.launcher.spi.LauncherRunContext;
-import org.apache.sling.feature.launcher.spi.extensions.ExtensionInstallationContext;
-
 /**
  * This class holds the configuration of the launcher.
  */
-public class Installation implements LauncherRunContext, ExtensionInstallationContext {
+public class Installation implements LauncherRunContext {
 
     /** The map with the framework properties. */
     private final Map<String, String> fwkProperties = new HashMap<>();
@@ -102,7 +101,6 @@ public class Installation implements LauncherRunContext, ExtensionInstallationCo
         return this.fwkProperties;
     }
 
-    @Override
     public void addFrameworkProperty(String key, String value)
     {
         this.fwkProperties.put(key, value);
diff --git a/src/main/java/org/apache/sling/feature/launcher/impl/Main.java b/src/main/java/org/apache/sling/feature/launcher/impl/Main.java
index aff2330..aad8b63 100644
--- a/src/main/java/org/apache/sling/feature/launcher/impl/Main.java
+++ b/src/main/java/org/apache/sling/feature/launcher/impl/Main.java
@@ -23,7 +23,9 @@ import java.net.URL;
 import java.net.URLClassLoader;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import org.apache.commons.cli.BasicParser;
 import org.apache.commons.cli.CommandLine;
@@ -224,7 +226,8 @@ public class Main {
             try {
                 boolean restart = launcherConfig.getFeatureFiles().length == 0;
 
-                final Feature app = assemble(launcherConfig, artifactManager);
+                Map<ArtifactId, Feature> loadedFeatures = new HashMap<>();
+                final Feature app = assemble(launcherConfig, artifactManager, loadedFeatures);
 
                 Main.LOG().info("");
                 Main.LOG().info("Assembling launcher...");
@@ -247,7 +250,7 @@ public class Main {
 
                 launcher.prepare(ctx, getFelixFrameworkId(m_frameworkVersion), app);
 
-                FeatureProcessor.prepareLauncher(ctx, launcherConfig, app);
+                FeatureProcessor.prepareLauncher(ctx, launcherConfig, app, loadedFeatures);
 
                 Main.LOG().info("Using {} local artifacts, {} cached artifacts, and {} downloaded artifacts",
                     launcherConfig.getLocalArtifacts(), launcherConfig.getCachedArtifacts(), launcherConfig.getDownloadedArtifacts());
@@ -275,7 +278,8 @@ public class Main {
         }
     }
 
-    private static Feature assemble(final LauncherConfig launcherConfig, final ArtifactManager artifactManager) throws IOException
+    private static Feature assemble(final LauncherConfig launcherConfig, final ArtifactManager artifactManager,
+            Map<ArtifactId, Feature> loadedFeatures) throws IOException
     {
         if (launcherConfig.getFeatureFiles().length == 0) {
             File application = getApplicationFeatureFile(launcherConfig);
@@ -285,11 +289,11 @@ public class Main {
             else {
                 throw new IllegalStateException("No feature(s) to launch found and none where specified");
             }
-            return FeatureProcessor.createApplication(launcherConfig, artifactManager);
+            return FeatureProcessor.createApplication(launcherConfig, artifactManager, loadedFeatures);
         }
         else
         {
-            final Feature app = FeatureProcessor.createApplication(launcherConfig, artifactManager);
+            final Feature app = FeatureProcessor.createApplication(launcherConfig, artifactManager, loadedFeatures);
 
             // write application back
             final File file = getApplicationFeatureFile(launcherConfig);
diff --git a/src/main/java/org/apache/sling/feature/launcher/impl/extensions/handlers/ContentPackageHandler.java b/src/main/java/org/apache/sling/feature/launcher/impl/extensions/handlers/ContentPackageHandler.java
index dfc0db4..a08061c 100644
--- a/src/main/java/org/apache/sling/feature/launcher/impl/extensions/handlers/ContentPackageHandler.java
+++ b/src/main/java/org/apache/sling/feature/launcher/impl/extensions/handlers/ContentPackageHandler.java
@@ -21,19 +21,18 @@ import java.io.IOException;
 import org.apache.sling.feature.Artifact;
 import org.apache.sling.feature.Extension;
 import org.apache.sling.feature.ExtensionType;
-import org.apache.sling.feature.launcher.spi.LauncherPrepareContext;
+import org.apache.sling.feature.launcher.spi.extensions.ExtensionContext;
 import org.apache.sling.feature.launcher.spi.extensions.ExtensionHandler;
-import org.apache.sling.feature.launcher.spi.extensions.ExtensionInstallationContext;
 
 public class ContentPackageHandler implements ExtensionHandler
 {
     @Override
-    public boolean handle(Extension extension, LauncherPrepareContext prepareContext, ExtensionInstallationContext installationContext) throws IOException
+    public boolean handle(ExtensionContext context, Extension extension) throws IOException
     {
         if (extension.getType() == ExtensionType.ARTIFACTS
                 && extension.getName().equals(Extension.EXTENSION_NAME_CONTENT_PACKAGES)) {
             for(final Artifact a : extension.getArtifacts() ) {
-                installationContext.addInstallableArtifact(prepareContext.getArtifactFile(a.getId()));
+                context.addInstallableArtifact(context.getArtifactFile(a.getId()));
             }
             return true;
         }
diff --git a/src/main/java/org/apache/sling/feature/launcher/impl/extensions/handlers/RepoInitHandler.java b/src/main/java/org/apache/sling/feature/launcher/impl/extensions/handlers/RepoInitHandler.java
index c639104..34ac652 100644
--- a/src/main/java/org/apache/sling/feature/launcher/impl/extensions/handlers/RepoInitHandler.java
+++ b/src/main/java/org/apache/sling/feature/launcher/impl/extensions/handlers/RepoInitHandler.java
@@ -21,16 +21,15 @@ import java.util.concurrent.atomic.AtomicInteger;
 import org.apache.sling.feature.Configuration;
 import org.apache.sling.feature.Extension;
 import org.apache.sling.feature.ExtensionType;
-import org.apache.sling.feature.launcher.spi.LauncherPrepareContext;
+import org.apache.sling.feature.launcher.spi.extensions.ExtensionContext;
 import org.apache.sling.feature.launcher.spi.extensions.ExtensionHandler;
-import org.apache.sling.feature.launcher.spi.extensions.ExtensionInstallationContext;
 
 public class RepoInitHandler implements ExtensionHandler
 {
     private static final AtomicInteger index = new AtomicInteger(1);
 
     @Override
-    public boolean handle(Extension extension, LauncherPrepareContext prepareContext, ExtensionInstallationContext installationContext) throws Exception
+    public boolean handle(ExtensionContext context, Extension extension) throws Exception
     {
         if (extension.getName().equals(Extension.EXTENSION_NAME_REPOINIT)) {
             if ( extension.getType() != ExtensionType.TEXT ) {
@@ -39,7 +38,7 @@ public class RepoInitHandler implements ExtensionHandler
             final Configuration cfg = new Configuration("org.apache.sling.jcr.repoinit.RepositoryInitializer~repoinit"
                     + String.valueOf(index.getAndIncrement()));
             cfg.getProperties().put("scripts", extension.getText());
-            installationContext.addConfiguration(Configuration.getName(cfg.getPid()),
+            context.addConfiguration(Configuration.getName(cfg.getPid()),
                     Configuration.getFactoryPid(cfg.getPid()), cfg.getConfigurationProperties());
             return true;
         }
diff --git a/src/main/java/org/apache/sling/feature/launcher/spi/extensions/ExtensionInstallationContext.java b/src/main/java/org/apache/sling/feature/launcher/spi/extensions/ExtensionContext.java
similarity index 52%
rename from src/main/java/org/apache/sling/feature/launcher/spi/extensions/ExtensionInstallationContext.java
rename to src/main/java/org/apache/sling/feature/launcher/spi/extensions/ExtensionContext.java
index 5efd7b7..21e38ca 100644
--- a/src/main/java/org/apache/sling/feature/launcher/spi/extensions/ExtensionInstallationContext.java
+++ b/src/main/java/org/apache/sling/feature/launcher/spi/extensions/ExtensionContext.java
@@ -16,28 +16,55 @@
  */
 package org.apache.sling.feature.launcher.spi.extensions;
 
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.launcher.spi.LauncherPrepareContext;
+import org.apache.sling.feature.launcher.spi.LauncherRunContext;
+
 import java.io.File;
+import java.io.IOException;
 import java.util.Dictionary;
 
-import org.apache.sling.feature.launcher.spi.LauncherRunContext;
-
-public interface ExtensionInstallationContext extends LauncherRunContext
-{
+/**
+ * This context object is provided to launcher extensions.
+ */
+public interface ExtensionContext extends LauncherPrepareContext, LauncherRunContext {
+    /**
+     * Add a bundle to be installed by the launcher.
+     * @param startLevel The start level for the bundle.
+     * @param file The file with the bundle.
+     */
     public void addBundle(final Integer startLevel, final File file);
 
     /**
-     * Add an artifact to be installed by the installer
+     * Add an artifact to be installed by the launcher
      * @param file The file
      */
     public void addInstallableArtifact(final File file);
 
     /**
-     * Add a configuration
+     * Add a configuration to be installed by the launcher
      * @param pid The pid
      * @param factoryPid The factory pid
      * @param properties The propertis
      */
     public void addConfiguration(final String pid, final String factoryPid, final Dictionary<String, Object> properties);
 
+    /**
+     * Add a framework property to be set by the launcher.
+     * @param key The key for the property.
+     * @param value The value for the property.
+     */
     public void addFrameworkProperty(final String key, final String value);
+
+    /**
+     * Return the feature object for a given Artifact ID. It looks for the requested feature
+     * in the list of features provided from the launcher commandline as well as in the configured
+     * repositories.
+     * @param artifact The artifact ID for the feature.
+     * @return The Feature Model or null if the artifact cannot be found.
+     * @throws IOException If the artifact can be found, but creating a Feature
+     * Model out of it causes an exception.
+     */
+    Feature getFeature(ArtifactId artifact) throws IOException;
 }
diff --git a/src/main/java/org/apache/sling/feature/launcher/spi/extensions/ExtensionHandler.java b/src/main/java/org/apache/sling/feature/launcher/spi/extensions/ExtensionHandler.java
index d09298e..acb8347 100644
--- a/src/main/java/org/apache/sling/feature/launcher/spi/extensions/ExtensionHandler.java
+++ b/src/main/java/org/apache/sling/feature/launcher/spi/extensions/ExtensionHandler.java
@@ -17,9 +17,8 @@
 package org.apache.sling.feature.launcher.spi.extensions;
 
 import org.apache.sling.feature.Extension;
-import org.apache.sling.feature.launcher.spi.LauncherPrepareContext;
 
 public interface ExtensionHandler
 {
-    public boolean handle(Extension extension, LauncherPrepareContext prepareContext, ExtensionInstallationContext installationContext) throws Exception;
+    public boolean handle(ExtensionContext context, Extension extension) throws Exception;
 }
diff --git a/src/test/java/org/apache/sling/feature/launcher/impl/ExtensionContextImplTest.java b/src/test/java/org/apache/sling/feature/launcher/impl/ExtensionContextImplTest.java
new file mode 100644
index 0000000..e03b871
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/launcher/impl/ExtensionContextImplTest.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.launcher.impl;
+
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.launcher.spi.LauncherPrepareContext;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+public class ExtensionContextImplTest {
+    @Test
+    public void testGetFeature() throws Exception {
+        File testFeatureFile = new File(getClass().getResource("/test-feature.json").getFile());
+
+        ArtifactId aid2 = ArtifactId.fromMvnId("g:a:2");
+        LauncherPrepareContext lpc = Mockito.mock(LauncherPrepareContext.class);
+        Mockito.when(lpc.getArtifactFile(aid2)).thenReturn(testFeatureFile);
+
+        ArtifactId aid1 = ArtifactId.fromMvnId("g:a:1");
+        Feature f1 = new Feature(aid1);
+        Map<ArtifactId, Feature> loaded = new HashMap<>();
+        loaded.put(aid1, f1);
+
+        ExtensionContextImpl c = new ExtensionContextImpl(lpc, null, loaded);
+        assertEquals(f1, c.getFeature(aid1));
+        assertNotNull(c.getFeature(aid2));
+        assertNull(c.getFeature(ArtifactId.fromMvnId("g:a:3")));
+    }
+}
diff --git a/src/test/resources/test-feature.json b/src/test/resources/test-feature.json
new file mode 100644
index 0000000..8bac79d
--- /dev/null
+++ b/src/test/resources/test-feature.json
@@ -0,0 +1,3 @@
+{
+    "id" : "org.apache.sling/a-test-feature/1"
+}
\ No newline at end of file