You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@sling.apache.org by GitBox <gi...@apache.org> on 2018/10/10 09:13:57 UTC

[GitHub] bosschaert closed pull request #3: SLING-7970 Add Feature Model introspection service

bosschaert closed pull request #3: SLING-7970 Add Feature Model introspection service
URL: https://github.com/apache/sling-org-apache-sling-feature-launcher/pull/3
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/pom.xml b/pom.xml
index 9debaf3..08bea14 100644
--- a/pom.xml
+++ b/pom.xml
@@ -99,6 +99,12 @@
             <version>7.0.0</version>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.inventory</artifactId>
+            <version>1.0.6</version>
+            <scope>provided</scope>
+        </dependency>
         <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
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 dd5c2bb..018f1ea 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,6 +19,7 @@
 import java.io.File;
 import java.io.FileReader;
 import java.io.IOException;
+import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -42,6 +43,7 @@
 import org.apache.sling.feature.io.file.ArtifactHandler;
 import org.apache.sling.feature.io.file.ArtifactManager;
 import org.apache.sling.feature.io.json.FeatureJSONReader;
+import org.apache.sling.feature.io.json.FeatureJSONWriter;
 import org.apache.sling.feature.launcher.spi.LauncherPrepareContext;
 import org.apache.sling.feature.launcher.spi.extensions.ExtensionHandler;
 
@@ -120,7 +122,7 @@ public static void prepareLauncher(final LauncherPrepareContext ctx, final Launc
             for(final Artifact a : entry.getValue()) {
                 final File artifactFile = ctx.getArtifactFile(a.getId());
 
-                config.getInstallation().addBundle(entry.getKey(), artifactFile);
+                config.getInstallation().addBundle(entry.getKey(), a.getId().toMvnId(), artifactFile);
             }
         }
 
@@ -138,6 +140,10 @@ public static void prepareLauncher(final LauncherPrepareContext ctx, final Launc
             }
         }
 
+        StringWriter featureStringWriter = new StringWriter();
+        FeatureJSONWriter.write(featureStringWriter, app);
+        config.getInstallation().setEffectiveFeature(featureStringWriter.toString());
+
         extensions: for(final Extension ext : app.getExtensions()) {
             for (ExtensionHandler handler : ServiceLoader.load(ExtensionHandler.class,  FeatureProcessor.class.getClassLoader()))
             {
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 3f08856..4b190ff 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
@@ -35,7 +35,7 @@
     private final Map<String, String> fwkProperties = new HashMap<>();
 
     /** Bundle map */
-    private final Map<Integer, List<File>> bundleMap = new HashMap<>();
+    private final Map<Integer, Map<String, File>> bundleMap = new HashMap<>();
 
     /** Artifacts to be installed */
     private final List<File> installables = new ArrayList<>();
@@ -46,6 +46,9 @@
     /** The list of app jars. */
     private final List<File> appJars = new ArrayList<>();
 
+    /** The effective, merged feature used to launch. */
+    private String effectiveFeature;
+
     /**
      * Add an application jar.
      * @param jar The application jar
@@ -67,13 +70,13 @@ public void addAppJar(final File jar) {
      * @param startLevel The start level
      * @param file The bundle file
      */
-    public void addBundle(final Integer startLevel, final File file) {
-        List<File> files = bundleMap.get(startLevel);
+    public void addBundle(final Integer startLevel, final String id, final File file) {
+        Map<String, File> files = bundleMap.get(startLevel);
         if ( files == null ) {
-            files = new ArrayList<>();
+            files = new HashMap<>();
             bundleMap.put(startLevel, files);
         }
-        files.add(file);
+        files.put(id, file);
     }
 
     /**
@@ -112,7 +115,7 @@ public void addFrameworkProperty(String key, String value)
      * @see org.apache.sling.feature.launcher.spi.LauncherRunContext#getBundleMap()
      */
     @Override
-    public Map<Integer, List<File>> getBundleMap() {
+    public Map<Integer, Map<String, File>> getBundleMap() {
         return this.bundleMap;
     }
 
@@ -132,6 +135,22 @@ public void addFrameworkProperty(String key, String value)
         return this.installables;
     }
 
+    /**
+     * @see org.apache.sling.feature.launcher.spi.LauncherRunContext#getEffectiveFeature()
+     */
+    @Override
+    public String getEffectiveFeature() {
+        return effectiveFeature;
+    }
+
+    /**
+     * Set the effective feature JSON text
+     * @param featureJsonText
+     */
+    public void setEffectiveFeature(String featureJsonText) {
+        effectiveFeature = featureJsonText;
+    }
+
     /**
      * Clear all in-memory objects
      */
@@ -140,5 +159,6 @@ public void clear() {
         this.fwkProperties.clear();
         this.bundleMap.clear();
         this.installables.clear();
+        this.effectiveFeature = null;
     }
 }
diff --git a/src/main/java/org/apache/sling/feature/launcher/impl/launchers/AbstractRunner.java b/src/main/java/org/apache/sling/feature/launcher/impl/launchers/AbstractRunner.java
index f532a2c..a430e60 100644
--- a/src/main/java/org/apache/sling/feature/launcher/impl/launchers/AbstractRunner.java
+++ b/src/main/java/org/apache/sling/feature/launcher/impl/launchers/AbstractRunner.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sling.feature.launcher.impl.launchers;
 
+import org.apache.felix.inventory.InventoryPrinter;
 import org.apache.sling.feature.launcher.impl.Main;
 import org.apache.sling.launchpad.api.LaunchpadContentProvider;
 import org.apache.sling.launchpad.api.StartupHandler;
@@ -27,6 +28,7 @@
 import org.osgi.framework.FrameworkEvent;
 import org.osgi.framework.FrameworkListener;
 import org.osgi.framework.ServiceReference;
+import org.osgi.framework.Version;
 import org.osgi.framework.launch.Framework;
 import org.osgi.framework.startlevel.BundleStartLevel;
 import org.osgi.framework.startlevel.FrameworkStartLevel;
@@ -37,17 +39,20 @@
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.PrintWriter;
 import java.lang.reflect.Array;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Method;
 import java.net.JarURLConnection;
 import java.net.URL;
 import java.net.URLConnection;
+import java.util.AbstractMap;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Dictionary;
 import java.util.Enumeration;
+import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.Iterator;
 import java.util.List;
@@ -67,6 +72,7 @@
  * Common functionality for the framework start.
  */
 public abstract class AbstractRunner implements Callable<Integer> {
+    private static final String BUNDLES_SERVICE = "org.apache.sling.feature.service.Bundles";
 
     private volatile ServiceTracker<Object, Object> configAdminTracker;
 
@@ -100,7 +106,8 @@ public AbstractRunner(final Map<String, String> frameworkProperties, final List<
         }
     }
 
-    protected void setupFramework(final Framework framework, final Map<Integer, List<File>> bundlesMap)
+    protected void setupFramework(final Framework framework,
+            final Map<Integer, Map<String, File>> bundlesMap, String effectiveFeature)
     throws BundleException {
         if ( !configurations.isEmpty() ) {
             this.configAdminTracker = new ServiceTracker<>(framework.getBundleContext(),
@@ -165,11 +172,13 @@ public void removedService(ServiceReference<Object> reference, Object service) {
             this.installerTracker.open();
         }
 
+        Map<Map.Entry<String, Version>, String> bm = null;
         try {
-            this.install(framework, bundlesMap);
+            bm = this.install(framework, bundlesMap);
         } catch ( final IOException ioe) {
             throw new BundleException("Unable to install bundles.", ioe);
         }
+        final Map<Map.Entry<String, Version>, String> bundleMapping = bm;
 
         // TODO: double check bundles and take installables into account
         install = !this.configurations.isEmpty() || !this.installables.isEmpty() || !bundlesMap.isEmpty();
@@ -271,6 +280,28 @@ public InputStream getResourceAsStream(String path) {
 
                 }
             }, null);
+
+            Dictionary<String, Object> props = new Hashtable<>();
+            props.put(InventoryPrinter.NAME, "launch.features");
+            props.put(InventoryPrinter.TITLE, "Launch Features");
+            props.put(InventoryPrinter.FORMAT, "JSON");
+            new RegisterServiceReflectively(framework.getBundleContext(), InventoryPrinter.SERVICE, props,
+                new BaseInvocationHandler<Void>("print", a -> {
+                    PrintWriter pw = (PrintWriter) a[0];
+                    pw.print(effectiveFeature);
+                    return null;
+                })).open();
+
+            new RegisterServiceReflectively(framework.getBundleContext(), BUNDLES_SERVICE, null,
+                new BaseInvocationHandler<String>("getBundleArtifact", a -> {
+                    if (a.length != 2)
+                        throw new IllegalArgumentException("Bundles.getBundleArtifact() has 2 arguments.");
+
+                    String bsn = a[0].toString();
+                    String ver = a[1].toString();
+
+                    return bundleMapping.get(new AbstractMap.SimpleEntry<String, Version>(bsn, Version.valueOf(ver)));
+                })).open();
         } catch (NoClassDefFoundError ex) {
             // Ignore, we don't have the launchpad.api
         }
@@ -384,22 +415,29 @@ private String getFragmentHostHeader(final Bundle b) {
     /**
      * Install the bundles
      * @param bundleMap The map with the bundles indexed by start level
+     * @return a map mapping from a Bundle Symbolic Name and Version to the associated Artifact ID
      * @throws IOException, BundleException If anything goes wrong.
      */
-    private void install(final Framework framework, final Map<Integer, List<File>> bundleMap)
+    private Map<Map.Entry<String, Version>, String> install(final Framework framework, final Map<Integer, Map<String, File>> bundleMap)
     throws IOException, BundleException {
+        Map<Map.Entry<String, Version>, String> mapping = new HashMap<>();
+
         final BundleContext bc = framework.getBundleContext();
         int defaultStartLevel = getProperty(bc, "felix.startlevel.bundle", 1);
         for(final Integer startLevel : sortStartLevels(bundleMap.keySet(), defaultStartLevel)) {
             Main.LOG().debug("Installing bundles with start level {}", startLevel);
 
-            for(final File file : bundleMap.get(startLevel)) {
+            for(final Map.Entry<String, File> entry : bundleMap.get(startLevel).entrySet()) {
+                File file = entry.getValue();
                 Main.LOG().debug("- {}", file.getName());
 
                 // use reference protocol. This avoids copying the binary to the cache directory
                 // of the framework
                 final Bundle bundle = bc.installBundle("reference:" + file.toURI().toURL(), null);
 
+                // Record the mapping of the feature model bundle artifact ID to the Bundle Symbolic Name and Version
+                mapping.put(new AbstractMap.SimpleEntry<String, Version>(bundle.getSymbolicName(), bundle.getVersion()), entry.getKey());
+
                 // fragment?
                 if ( !isSystemBundleFragment(bundle) && getFragmentHostHeader(bundle) == null ) {
                     if ( startLevel > 0 ) {
@@ -409,6 +447,8 @@ private void install(final Framework framework, final Map<Integer, List<File>> b
                 }
             }
         }
+
+        return mapping;
     }
 
     /**
diff --git a/src/main/java/org/apache/sling/feature/launcher/impl/launchers/BaseInvocationHandler.java b/src/main/java/org/apache/sling/feature/launcher/impl/launchers/BaseInvocationHandler.java
new file mode 100644
index 0000000..49f23fb
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/launcher/impl/launchers/BaseInvocationHandler.java
@@ -0,0 +1,42 @@
+/*
+ * 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.launchers;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.util.function.Function;
+
+class BaseInvocationHandler<T> implements InvocationHandler {
+    private final String methodName;
+    private final Function<Object[], T> function;
+
+    BaseInvocationHandler(String method, Function<Object[], T> fun) {
+        methodName = method;
+        function = fun;
+    }
+
+    @Override
+    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+        if (method.getName().equals(methodName)) {
+            return function.apply(args);
+        } else {
+            return method.invoke(this, args);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/feature/launcher/impl/launchers/FrameworkLauncher.java b/src/main/java/org/apache/sling/feature/launcher/impl/launchers/FrameworkLauncher.java
index fb2dac2..bebe8dd 100644
--- a/src/main/java/org/apache/sling/feature/launcher/impl/launchers/FrameworkLauncher.java
+++ b/src/main/java/org/apache/sling/feature/launcher/impl/launchers/FrameworkLauncher.java
@@ -91,7 +91,7 @@ public String lookup(String key) {
             Main.LOG().debug("Bundles:");
             for(final Integer key : context.getBundleMap().keySet()) {
                 Main.LOG().debug("-- Start Level {}", key);
-                for(final File f : context.getBundleMap().get(key)) {
+                for(final File f : context.getBundleMap().get(key).values()) {
                     Main.LOG().debug("  - {}", f.getName());
                 }
             }
@@ -116,7 +116,8 @@ public String lookup(String key) {
         Callable<Integer> restart = (Callable<Integer>) constructor.newInstance(properties,
                 context.getBundleMap(),
                 context.getConfigurations(),
-                context.getInstallableArtifacts());
+                context.getInstallableArtifacts(),
+                context.getEffectiveFeature());
 
         return restart.call();
         // nothing else to do, constructor starts everything
diff --git a/src/main/java/org/apache/sling/feature/launcher/impl/launchers/FrameworkRunner.java b/src/main/java/org/apache/sling/feature/launcher/impl/launchers/FrameworkRunner.java
index 25ce117..c8af44c 100644
--- a/src/main/java/org/apache/sling/feature/launcher/impl/launchers/FrameworkRunner.java
+++ b/src/main/java/org/apache/sling/feature/launcher/impl/launchers/FrameworkRunner.java
@@ -37,9 +37,10 @@
     private volatile int type = -1;
 
     public FrameworkRunner(final Map<String, String> frameworkProperties,
-            final Map<Integer, List<File>> bundlesMap,
+            final Map<Integer, Map<String, File>> bundlesMap,
             final List<Object[]> configurations,
-            final List<File> installables) throws Exception {
+            final List<File> installables,
+            final String effectiveFeature) throws Exception {
         super(frameworkProperties, configurations, installables);
 
         final ServiceLoader<FrameworkFactory> loader = ServiceLoader.load(FrameworkFactory.class);
@@ -78,7 +79,7 @@ public void run() {
             }
         });
 
-        this.setupFramework(framework, bundlesMap);
+        this.setupFramework(framework, bundlesMap, effectiveFeature);
 
 
         long time = System.currentTimeMillis();
diff --git a/src/main/java/org/apache/sling/feature/launcher/impl/launchers/RegisterServiceReflectively.java b/src/main/java/org/apache/sling/feature/launcher/impl/launchers/RegisterServiceReflectively.java
new file mode 100644
index 0000000..571355d
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/launcher/impl/launchers/RegisterServiceReflectively.java
@@ -0,0 +1,100 @@
+/*
+ * 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.launchers;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.framework.namespace.PackageNamespace;
+import org.osgi.framework.wiring.BundleCapability;
+import org.osgi.framework.wiring.BundleWiring;
+import org.osgi.util.tracker.BundleTracker;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Proxy;
+import java.util.Dictionary;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+class RegisterServiceReflectively {
+    private final BundleContext bundleContext;
+    private final String serviceClassName;
+    private final String servicePackage;
+    private final Dictionary<String, Object> serviceProperties;
+    private final InvocationHandler invocationHandler;
+    private final BundleTracker<Bundle> bundleTracker;
+    private final Map<Bundle, ServiceRegistration<?>> registrations = new ConcurrentHashMap<>();
+
+    RegisterServiceReflectively(BundleContext bc, String className, Dictionary<String, Object> props,
+            InvocationHandler handler) {
+        bundleContext = bc;
+        serviceClassName = className;
+        servicePackage = serviceClassName.substring(0, serviceClassName.lastIndexOf('.'));
+        serviceProperties = props;
+        invocationHandler = handler;
+
+        bundleTracker = new BundleTracker<Bundle>(bc,
+                Bundle.ACTIVE | Bundle.STOPPING | Bundle.UNINSTALLED, null) {
+            @Override
+            public Bundle addingBundle(Bundle bundle, BundleEvent event) {
+                BundleWiring bw = bundle.adapt(BundleWiring.class);
+                for (BundleCapability cap : bw.getCapabilities(PackageNamespace.PACKAGE_NAMESPACE)) {
+                    if (servicePackage.equals(cap.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE))) {
+                        try {
+                            registrations.put(bundle, registerService(bw));
+                        } catch (ClassNotFoundException e) {
+                            // Ignore
+                        }
+                    }
+                }
+
+                return bundle;
+            }
+
+            @Override
+            public void modifiedBundle(Bundle bundle, BundleEvent event, Bundle object) {
+                removedBundle(bundle, event, object);
+                addingBundle(bundle, event);
+            }
+
+            @Override
+            public void removedBundle(Bundle bundle, BundleEvent event, Bundle object) {
+                ServiceRegistration<?> reg = registrations.remove(bundle);
+                if (reg != null)
+                    reg.unregister();
+            }
+        };
+    }
+
+    protected ServiceRegistration<?> registerService(BundleWiring bw) throws ClassNotFoundException {
+        Class<?> serviceClass = bw.getClassLoader().loadClass(serviceClassName);
+
+        Object proxy = Proxy.newProxyInstance(bw.getClassLoader(), new Class [] {serviceClass}, invocationHandler);
+        return bundleContext.registerService(serviceClassName, proxy, serviceProperties);
+    }
+
+    void open() {
+        bundleTracker.open();
+    }
+
+    void close() {
+        bundleTracker.close();
+    }
+}
diff --git a/src/main/java/org/apache/sling/feature/launcher/spi/LauncherRunContext.java b/src/main/java/org/apache/sling/feature/launcher/spi/LauncherRunContext.java
index 20c95a9..481d94f 100644
--- a/src/main/java/org/apache/sling/feature/launcher/spi/LauncherRunContext.java
+++ b/src/main/java/org/apache/sling/feature/launcher/spi/LauncherRunContext.java
@@ -32,10 +32,11 @@
     Map<String, String> getFrameworkProperties();
 
     /**
-     * Bundle map, key is the start level, value is a list of files.
+     * Bundle map, key is the start level, value is a map of bundle artifact
+     * ID and the local file where the bundle can be found.
      * @return The bundle map, might be empty
      */
-    Map<Integer, List<File>> getBundleMap();
+    Map<Integer, Map<String, File>> getBundleMap();
 
     /**
      * List of configurations.
@@ -55,4 +56,10 @@
      * @return The list of files. The list might be empty.
      */
     List<File> getInstallableArtifacts();
+
+    /**
+     * Obtain the effective Feature JSON
+     * @return The effective Feature JSON as a String.
+     */
+    String getEffectiveFeature();
 }
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/ExtensionInstallationContext.java
index 5efd7b7..753831e 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/ExtensionInstallationContext.java
@@ -16,14 +16,14 @@
  */
 package org.apache.sling.feature.launcher.spi.extensions;
 
+import org.apache.sling.feature.launcher.spi.LauncherRunContext;
+
 import java.io.File;
 import java.util.Dictionary;
 
-import org.apache.sling.feature.launcher.spi.LauncherRunContext;
-
 public interface ExtensionInstallationContext extends LauncherRunContext
 {
-    public void addBundle(final Integer startLevel, final File file);
+    public void addBundle(final Integer startLevel, final String artifactId, final File file);
 
     /**
      * Add an artifact to be installed by the installer


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services