You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by cz...@apache.org on 2022/01/06 13:04:57 UTC

[sling-org-apache-sling-feature-launcher] branch issues/SLING-11042 updated: Register feature as a service and update metadata

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

cziegeler pushed a commit to branch issues/SLING-11042
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature-launcher.git


The following commit(s) were added to refs/heads/issues/SLING-11042 by this push:
     new f091e29  Register feature as a service and update metadata
f091e29 is described below

commit f091e29a5294b745034680a3bedda6839fda2fab
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Thu Jan 6 14:04:50 2022 +0100

    Register feature as a service and update metadata
---
 .../feature/launcher/impl/FeatureProcessor.java    |  2 +
 .../launcher/impl/launchers/AbstractRunner.java    | 74 +++++++++++++++++++++-
 .../launcher/impl/launchers/FrameworkLauncher.java | 63 ++++++++++++++++++
 .../launcher/impl/launchers/FrameworkRunner.java   | 16 +++--
 4 files changed, 148 insertions(+), 7 deletions(-)

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 ac1815b..9b7ab0d 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
@@ -187,6 +187,8 @@ public class FeatureProcessor {
             for(final Artifact a : entry.getValue()) {
                 final URL artifactFile = ctx.getArtifactFile(a.getId());
 
+                // add URL to feature metadata
+                a.getMetadata().put(URL.class.getName(), artifactFile.toString());
                 config.getInstallation().addBundle(entry.getKey(), artifactFile);
             }
         }
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 d5f952b..0c56e27 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
@@ -18,6 +18,7 @@ package org.apache.sling.feature.launcher.impl.launchers;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.StringReader;
 import java.lang.reflect.Array;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
@@ -27,6 +28,7 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Dictionary;
+import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.List;
 import java.util.Map;
@@ -38,16 +40,20 @@ import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
+import java.util.function.BiConsumer;
+import java.util.function.Supplier;
 
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleException;
 import org.osgi.framework.Constants;
 import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.PrototypeServiceFactory;
 import org.osgi.framework.ServiceReference;
 import org.osgi.framework.ServiceRegistration;
 import org.osgi.framework.launch.Framework;
 import org.osgi.framework.startlevel.BundleStartLevel;
+import org.osgi.framework.wiring.BundleWiring;
 import org.osgi.util.tracker.ServiceTracker;
 import org.osgi.util.tracker.ServiceTrackerCustomizer;
 import org.slf4j.Logger;
@@ -81,12 +87,24 @@ public abstract class AbstractRunner implements Callable<Integer> {
 
     protected final Logger logger;
 
+    private Supplier<String> featureSupplier;
+
+    private BiConsumer<URL, Map<String, String>> bundleReporter;
+
     public AbstractRunner(final List<Object[]> configurations, final List<URL> installables) {
         this.configurations = new ArrayList<>(configurations);
         this.installables = installables;
         this.logger = LoggerFactory.getLogger("launcher");
     }
 
+    public void setFeatureSupplier(final Supplier<String> supplier) {
+        this.featureSupplier = supplier;
+    }
+
+    public void setBundleReporter(final BiConsumer<URL, Map<String, String>> reporter) {
+        this.bundleReporter = reporter;
+    }
+
     protected void setupFramework(final Framework framework, final Map<Integer, List<URL>> bundlesMap) throws BundleException {
         // check for Apache Felix CM persistence manager config
         final String pm = framework.getBundleContext().getProperty(CM_CONFIG_PM);
@@ -313,7 +331,7 @@ public abstract class AbstractRunner implements Callable<Integer> {
                 if (file.getProtocol().equals("file")) {
                     location = "reference:";
                 }
-                location = location + file.toString();
+                location = location.concat(file.toString());
 
                 final Bundle bundle = bc.installBundle(location, null);
 
@@ -324,10 +342,64 @@ public abstract class AbstractRunner implements Callable<Integer> {
                     }
                     bundle.start();
                 }
+
+                if ( this.bundleReporter != null ) {
+                    final Map<String, String> params = new HashMap<>();
+                    params.put(Constants.BUNDLE_SYMBOLICNAME, bundle.getSymbolicName());
+                    params.put(Constants.BUNDLE_VERSION, bundle.getVersion().toString());
+                    params.put("Bundle-Id", String.valueOf(bundle.getBundleId()));
+
+                    this.bundleReporter.accept(file, params);
+                }
+                    
             }
         }
     }
 
+    protected void finishStartup(final Framework framework) {
+        Bundle featureBundle = null;
+        for(final Bundle bundle : framework.getBundleContext().getBundles()) {
+            if ( featureSupplier != null && "org.apache.sling.feature".equals(bundle.getSymbolicName()) ) {
+                featureBundle = bundle;
+            }
+        }
+        if ( featureBundle != null ) {
+            final Bundle bundle = featureBundle;
+            // the feature is registered as a prototype to give each client a copy as feature models are mutable
+            final Dictionary<String, Object> properties = new Hashtable<>();
+            properties.put("name", "org.apache.sling.feature.launcher");
+            featureBundle.getBundleContext().registerService(new String[] {"org.apache.sling.feature.Feature"}, 
+                new PrototypeServiceFactory<Object>() {
+
+                    @Override
+                    public Object getService(final Bundle client, final ServiceRegistration<Object> registration) {
+                        final ClassLoader cl = bundle.adapt(BundleWiring.class).getClassLoader();
+                        final ClassLoader oldTCCL = Thread.currentThread().getContextClassLoader();
+                        Thread.currentThread().setContextClassLoader(cl);
+                        try {
+                            final Class<?> readerClass = cl.loadClass("org.apache.sling.feature.io.json.FeatureJSONReader");
+                            final Method readMethod = readerClass.getDeclaredMethod("read", java.io.Reader.class, String.class);
+                            try( final StringReader reader = new StringReader(featureSupplier.get())) {
+                                return readMethod.invoke(null, reader, null);
+                            }
+                        } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
+                            // ignore
+                        } finally {
+                            Thread.currentThread().setContextClassLoader(oldTCCL);
+                        }
+                        return null;
+                    }
+
+                    @Override
+                    public void ungetService(final Bundle client, final ServiceRegistration<Object> registration,
+                            final Object service) {
+                        // nothing to do
+                    }
+                    
+                }, properties);
+        }
+    }
+
     /**
      * Sort the start levels in the ascending order. The only exception is the start level
      * "0", which should be put at the position configured in {@code felix.startlevel.bundle}.
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 e88a566..11a3128 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
@@ -16,19 +16,29 @@
  */
 package org.apache.sling.feature.launcher.impl.launchers;
 
+import java.io.IOException;
+import java.io.StringWriter;
 import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.net.URL;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Callable;
+import java.util.function.BiConsumer;
+import java.util.function.Supplier;
 
+import org.apache.sling.feature.Artifact;
 import org.apache.sling.feature.ArtifactId;
 import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.io.json.FeatureJSONWriter;
 import org.apache.sling.feature.launcher.impl.VariableSubstitutor;
 import org.apache.sling.feature.launcher.spi.Launcher;
 import org.apache.sling.feature.launcher.spi.LauncherPrepareContext;
 import org.apache.sling.feature.launcher.spi.LauncherRunContext;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
 
 import aQute.bnd.annotation.spi.ServiceProvider;
 
@@ -38,12 +48,14 @@ import aQute.bnd.annotation.spi.ServiceProvider;
 @ServiceProvider(value = Launcher.class)
 public class FrameworkLauncher implements Launcher {
 
+    private Feature feature;
 
     @Override
     public void prepare(final LauncherPrepareContext context,
             final ArtifactId frameworkId,
             final Feature app) throws Exception {
         context.addAppJar(context.getArtifactFile(frameworkId));
+        this.feature = app;
     }
 
     /**
@@ -89,6 +101,37 @@ public class FrameworkLauncher implements Launcher {
         Callable<Integer> restart = (Callable<Integer>) constructor.newInstance(properties, context.getBundleMap(),
                 context.getConfigurations(), context.getInstallableArtifacts());
 
+        setOptionalSupplier(restart, "setFeatureSupplier", new Supplier<Object>() {
+
+            @Override
+            public Object get() {
+                try ( final StringWriter writer = new StringWriter()) {
+                    FeatureJSONWriter.write(writer, feature);
+                    writer.flush();
+                    return writer.toString();
+                } catch ( final IOException ignore) {
+                    // ignore
+                }
+                return null;
+            }
+            
+        });
+
+        setOptionalBiConsumer(restart, "setBundleReporter", new BiConsumer<URL, Map<String, String>>() {
+            @Override
+            public void accept(final URL url, final Map<String, String> values) {
+                final String urlString = url.toString();
+                for(final Artifact a : feature.getBundles()) {
+                    if ( urlString.equals(a.getMetadata().get(URL.class.getName()))) {
+                        for(final Map.Entry<String, String> entry : values.entrySet()) {
+                            a.getMetadata().put(entry.getKey(), entry.getValue());
+                        }
+                        break;
+                    }
+                }
+            }
+            
+        });
         return restart.call();
         // nothing else to do, constructor starts everything
     }
@@ -96,4 +139,24 @@ public class FrameworkLauncher implements Launcher {
     protected String getFrameworkRunnerClass() {
         return FrameworkRunner.class.getName();
     }
+
+    private void setOptionalSupplier(final Object restart, final String name, final Supplier<Object> supplier) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+        try {
+            final Method setSupplier = restart.getClass().getMethod(name, Supplier.class);
+            setSupplier.setAccessible(true);
+            setSupplier.invoke(restart, supplier);
+        } catch ( final NoSuchMethodException nsme) {
+            // ignore
+        }
+    }
+
+    private void setOptionalBiConsumer(final Object restart, final String name, final BiConsumer consumer) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+        try {
+            final Method setMethod = restart.getClass().getMethod(name, BiConsumer.class);
+            setMethod.setAccessible(true);
+            setMethod.invoke(restart, consumer);
+        } catch ( final NoSuchMethodException nsme) {
+            // ignore
+        }
+    }
 }
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 f31a8a2..fd77c01 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
@@ -39,12 +39,20 @@ public class FrameworkRunner extends AbstractRunner {
 
     private volatile int type = -1;
 
+    private final Map<String, String> frameworkProperties;
+
+    private final Map<Integer, List<URL>> bundlesMap;
+
     public FrameworkRunner(final Map<String, String> frameworkProperties,
             final Map<Integer, List<URL>> bundlesMap,
             final List<Object[]> configurations,
             final List<URL> installables) throws Exception {
         super(configurations, installables);
-
+        this.frameworkProperties = frameworkProperties;
+        this.bundlesMap = bundlesMap;
+    }
+    
+    public Integer call() throws Exception {
         final ServiceLoader<FrameworkFactory> loader = ServiceLoader.load(FrameworkFactory.class);
         FrameworkFactory factory = null;
         for(FrameworkFactory f : loader) {
@@ -81,7 +89,6 @@ public class FrameworkRunner extends AbstractRunner {
 
         this.setupFramework(framework, bundlesMap);
 
-
         long time = System.currentTimeMillis();
         long startTimeout = Long.parseLong(frameworkProperties.getOrDefault(START_TIMEOUT, String.valueOf(10 * 60)));
 
@@ -89,6 +96,7 @@ public class FrameworkRunner extends AbstractRunner {
         if (!this.startFramework(framework, startTimeout, TimeUnit.SECONDS)) {
             throw new TimeoutException("Waited for more than " + startTimeout + " seconds to startup framework.");
         }
+        this.finishStartup(framework);
         logger.info("Framework started");
 
         logger.debug("Startup took: " + (System.currentTimeMillis() - time));
@@ -101,10 +109,6 @@ public class FrameworkRunner extends AbstractRunner {
             }
             logger.debug("Restart took: " + (System.currentTimeMillis() - time));
         }
-    }
-
-    @Override
-    public Integer call() {
         return type;
     }
 }