You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by dk...@apache.org on 2021/12/16 00:34:33 UTC

[sling-org-apache-sling-feature-apiregions] 01/01: SLING-11006 - Adding a web console status printer for the API Regions

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

dklco pushed a commit to branch SLING-11006-regions-webconsole
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature-apiregions.git

commit 038e7d175b4158e0f54e9cfa11f187db996e245c
Author: Dan Klco <kl...@adobe.com>
AuthorDate: Wed Dec 15 19:34:21 2021 -0500

    SLING-11006 - Adding a web console status printer for the API Regions
---
 pom.xml                                            |  20 ++++
 .../sling/feature/apiregions/impl/Activator.java   |  32 +++++++
 .../feature/apiregions/impl/RegionPrinter.java     | 104 +++++++++++++++++++++
 .../feature/apiregions/impl/ActivatorTest.java     |  15 +++
 .../feature/apiregions/impl/RegionPrinterTest.java |  89 ++++++++++++++++++
 src/test/resources/printer/empty.txt               |  32 +++++++
 src/test/resources/printer/populated.txt           |  54 +++++++++++
 7 files changed, 346 insertions(+)

diff --git a/pom.xml b/pom.xml
index cdce5f3..937c3c9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -65,6 +65,7 @@
                     <excludes>
                         <exclude>*.md</exclude>
                         <exclude>src/test/resources/*</exclude>
+                        <exclude>src/test/resources/printer/*.txt</exclude>
                         <exclude>src/test/resources/props1/*</exclude>
                         <exclude>src/test/resources/props2/*</exclude>
                         <exclude>src/test/resources/props3/*</exclude>
@@ -72,6 +73,25 @@
                     </excludes>
                 </configuration>
             </plugin>
+            <plugin>
+                <groupId>org.jacoco</groupId>
+                <artifactId>jacoco-maven-plugin</artifactId>
+                <version>0.8.7</version>
+                <executions>
+                   <execution>
+                      <goals>
+                         <goal>prepare-agent</goal>
+                      </goals>
+                   </execution>
+                   <execution>
+                      <id>report</id>
+                      <phase>prepare-package</phase>
+                      <goals>
+                         <goal>report</goal>
+                      </goals>
+                   </execution>
+                </executions>
+             </plugin>
         </plugins>
     </build>
     <dependencies>
diff --git a/src/main/java/org/apache/sling/feature/apiregions/impl/Activator.java b/src/main/java/org/apache/sling/feature/apiregions/impl/Activator.java
index 5d0900f..1bd0aae 100644
--- a/src/main/java/org/apache/sling/feature/apiregions/impl/Activator.java
+++ b/src/main/java/org/apache/sling/feature/apiregions/impl/Activator.java
@@ -61,6 +61,7 @@ public class Activator implements BundleActivator, FrameworkListener {
 
     BundleContext bundleContext;
     ServiceRegistration<ResolverHookFactory> hookRegistration;
+    ServiceRegistration<RegionPrinter> webconsoleRegistration;
 
     RegionConfiguration configuration;
 
@@ -74,6 +75,7 @@ public class Activator implements BundleActivator, FrameworkListener {
 
         registerHook();
 
+        registerWebconsoleStatus();
         this.configAdminTracker = new ServiceTracker<>(context, CONFIG_ADMIN_CLASS_NAME, new ServiceTrackerCustomizer<Object, Object>() {
 
             @Override
@@ -100,8 +102,11 @@ public class Activator implements BundleActivator, FrameworkListener {
         this.configAdminTracker.open();
 
         context.addFrameworkListener(this);
+
+        
     }
 
+
     @Override
     public synchronized void stop(BundleContext context) throws Exception {
         // All services automatically get unregistered by the framework.
@@ -135,6 +140,23 @@ public class Activator implements BundleActivator, FrameworkListener {
         hookRegistration = bundleContext.registerService(ResolverHookFactory.class, enforcer, this.configuration.getRegistrationProperties());
     }
 
+    synchronized void registerWebconsoleStatus() {
+
+        if (webconsoleRegistration != null){
+            return; // There is already a hook, no need to re-register
+        }
+
+        LOG.info("Registering region printer");
+        RegionPrinter printer = new RegionPrinter(bundleContext,configuration);
+
+        final Dictionary<String, String> serviceProps = new Hashtable<>();
+        serviceProps.put("felix.webconsole.label", RegionPrinter.PATH);
+        serviceProps.put("felix.webconsole.title", RegionPrinter.HEADLINE);
+        serviceProps.put("felix.webconsole.configprinter.modes", "always");
+
+        webconsoleRegistration = bundleContext.registerService(RegionPrinter.class, printer, serviceProps);
+    }
+
     synchronized void unregisterHook() {
         if (hookRegistration != null) {
             hookRegistration.unregister();
@@ -142,6 +164,13 @@ public class Activator implements BundleActivator, FrameworkListener {
         }
     }
 
+    synchronized void unregisterWebconsoleStatus() {
+        if (webconsoleRegistration != null) {
+            webconsoleRegistration.unregister();
+            webconsoleRegistration = null;
+        }
+    }
+
     @Override
     public void frameworkEvent(FrameworkEvent event) {
         if (event.getType() == FrameworkEvent.STARTED) {
@@ -192,13 +221,16 @@ public class Activator implements BundleActivator, FrameworkListener {
                         Object arg = args[0];
                         if (arg == null) {
                             registerHook();
+                            registerWebconsoleStatus();
                         } else if (arg instanceof Dictionary) {
                             Dictionary<?,?> props = (Dictionary<?,?>) args[0];
                             Object disabled = props.get("disable");
                             if ("true".equals(disabled)) {
                                 unregisterHook();
+                                unregisterWebconsoleStatus();
                             } else {
                                 registerHook();
+                                registerWebconsoleStatus();
                             }
                         }
                     }
diff --git a/src/main/java/org/apache/sling/feature/apiregions/impl/RegionPrinter.java b/src/main/java/org/apache/sling/feature/apiregions/impl/RegionPrinter.java
new file mode 100644
index 0000000..9ebef16
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/apiregions/impl/RegionPrinter.java
@@ -0,0 +1,104 @@
+/*
+ * 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.apiregions.impl;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Version;
+
+@SuppressWarnings("java:S3457") // adding platform specific endings makes the process harder and \n should be
+                                // interpreted correctly across all
+public class RegionPrinter {
+
+    static final String HEADLINE = "Sling Feature API Regions";
+    static final String PATH = "apiregions";
+    private RegionConfiguration config;
+    private BundleContext context;
+
+    public RegionPrinter(BundleContext context, RegionConfiguration config) {
+        this.context = context;
+        this.config = config;
+    }
+
+    private void renderPackageMappings(PrintWriter pw) {
+        Map<String, Set<String>> regionPackageMap = config.getRegionPackageMap();
+        regionPackageMap.keySet().stream().sorted().forEach(region -> {
+            pw.println(String.format("\n%s:", region));
+            regionPackageMap.get(region).stream().sorted().forEach(pkg -> pw.println(" - " + pkg));
+        });
+    }
+
+    private void renderBundleMappings(PrintWriter pw) {
+        Map<String, List<String>> featureRegions = config.getFeatureRegionMap();
+        Map<String, Set<String>> bundlesToFeatures = config.getBundleFeatureMap();
+
+        Map<String, Entry<String, Version>> bundleLocations = config.getBundleLocationConfigMap();
+        bundlesToFeatures.keySet().stream().sorted().forEach(bundle -> {
+            Set<String> regions = new HashSet<>();
+            bundlesToFeatures.get(bundle).stream()
+                    .forEach(feature -> Optional.ofNullable(featureRegions.get(feature))
+                            .ifPresent(regions::addAll));
+            String location = Optional.ofNullable(bundleLocations.get(bundle))
+                    .map(loc -> loc.getKey() + "v" + loc.getValue().toString()).orElse("null");
+            pw.println(String.format(" - %s\n\t - features: %s\n\t - regions: %s\n\t - location: %s", bundle,
+                    bundlesToFeatures.get(bundle).stream().collect(Collectors.joining(",")),
+                    regions.stream().collect(Collectors.joining(",")), location));
+        });
+    }
+
+    private void renderHeader(PrintWriter pw, String header) {
+        pw.println("\n\n" + header + "\n-------------------\n");
+    }
+
+    private void renderProperties(PrintWriter pw) {
+        String[] properties = new String[] { Activator.REGIONS_PROPERTY_NAME, RegionConstants.APIREGIONS_JOINGLOBAL,
+                RegionConstants.DEFAULT_REGIONS, RegionConstants.PROPERTIES_FILE_LOCATION };
+        Arrays.stream(properties).forEach(p -> pw.println(String.format(" - %s=%s", p, context.getProperty(p))));
+    }
+
+    /**
+     * Print out the region information
+     * 
+     * @see org.apache.felix.webconsole.ConfigurationPrinter#printConfiguration(java.io.PrintWriter)
+     */
+    public void printConfiguration(PrintWriter pw) {
+        pw.println(HEADLINE + "\n===========================");
+
+        renderHeader(pw, "Default Regions");
+        config.getDefaultRegions().stream().forEach(r -> pw.println(" - " + r));
+        renderHeader(pw, "Region Order");
+        config.getGlobalRegionOrder().stream().forEach(r -> pw.println(" - " + r));
+        renderHeader(pw, "Properties");
+        renderProperties(pw);
+        renderHeader(pw, "Packages per Region");
+        renderPackageMappings(pw);
+        renderHeader(pw, "Bundle Mappings");
+        renderBundleMappings(pw);
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/feature/apiregions/impl/ActivatorTest.java b/src/test/java/org/apache/sling/feature/apiregions/impl/ActivatorTest.java
index bace3b3..4ff300c 100644
--- a/src/test/java/org/apache/sling/feature/apiregions/impl/ActivatorTest.java
+++ b/src/test/java/org/apache/sling/feature/apiregions/impl/ActivatorTest.java
@@ -111,6 +111,15 @@ public class ActivatorTest {
                 Mockito.eq(expectedProps));
 
         Mockito.verify(bc).addFrameworkListener(a);
+
+        Dictionary<String, Object> expectedPrinterProps = new Hashtable<>();
+        expectedPrinterProps.put("felix.webconsole.label", RegionPrinter.PATH);
+        expectedPrinterProps.put("felix.webconsole.title", RegionPrinter.HEADLINE);
+        expectedPrinterProps.put("felix.webconsole.configprinter.modes", "always");
+        Mockito.verify(bc, Mockito.times(1)).registerService(
+                Mockito.eq(RegionPrinter.class),
+                Mockito.isA(RegionPrinter.class),
+                Mockito.eq(expectedPrinterProps));
     }
 
     @Test
@@ -122,6 +131,7 @@ public class ActivatorTest {
         a.registerHook();
 
         assertNull(a.hookRegistration);
+        assertNull(a.webconsoleRegistration);
     }
 
     @SuppressWarnings("unchecked")
@@ -132,9 +142,12 @@ public class ActivatorTest {
         Activator a = new Activator();
         a.bundleContext = bc;
         a.hookRegistration = Mockito.mock(ServiceRegistration.class);
+        a.webconsoleRegistration = Mockito.mock(ServiceRegistration.class);
         a.registerHook();
+        a.registerWebconsoleStatus();
 
         assertNotNull(a.hookRegistration);
+        assertNotNull(a.webconsoleRegistration);
         Mockito.verifyZeroInteractions(bc);
     }
 
@@ -142,7 +155,9 @@ public class ActivatorTest {
     public void testUnregisterHook() {
         Activator a = new Activator();
         a.unregisterHook(); // Should not throw an exception
+        a.unregisterWebconsoleStatus();
         assertNull(a.hookRegistration);
+        assertNull(a.webconsoleRegistration);
     }
 
     @SuppressWarnings({ "unchecked", "rawtypes" })
diff --git a/src/test/java/org/apache/sling/feature/apiregions/impl/RegionPrinterTest.java b/src/test/java/org/apache/sling/feature/apiregions/impl/RegionPrinterTest.java
new file mode 100644
index 0000000..7e4b5b3
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/apiregions/impl/RegionPrinterTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.apiregions.impl;
+
+import static org.apache.sling.feature.apiregions.impl.RegionConstants.BUNDLE_FEATURE_FILENAME;
+import static org.apache.sling.feature.apiregions.impl.RegionConstants.FEATURE_REGION_FILENAME;
+import static org.apache.sling.feature.apiregions.impl.RegionConstants.IDBSNVER_FILENAME;
+import static org.apache.sling.feature.apiregions.impl.RegionConstants.PROPERTIES_RESOURCE_PREFIX;
+import static org.apache.sling.feature.apiregions.impl.RegionConstants.REGION_PACKAGE_FILENAME;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.URISyntaxException;
+import java.util.stream.Collectors;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.internal.util.io.IOUtil;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+
+public class RegionPrinterTest {
+
+    private RegionConfiguration regionConfiguration;
+    private BundleContext bundleContext;
+    private PrintWriter pw;
+    private StringWriter sw;
+
+    @Before
+    public void setup() {
+        regionConfiguration = mock(RegionConfiguration.class);
+        bundleContext = mock(BundleContext.class);
+        sw = new StringWriter();
+        pw = new PrintWriter(sw);
+    }
+
+    private String loadResource(String name) {
+        return IOUtil.readLines(RegionPrinterTest.class.getClassLoader().getResourceAsStream(name)).stream()
+                .collect(Collectors.joining(String.format("\n")));
+    }
+
+    @Test
+    public void testBasic() {
+        RegionPrinter printer = new RegionPrinter(bundleContext, regionConfiguration);
+        printer.printConfiguration(pw);
+        assertEquals(loadResource("printer/empty.txt"), sw.toString());
+    }
+
+    @Test
+    public void testWithData() throws URISyntaxException, IOException {
+
+        String e = getClass().getResource("/empty.properties").toURI().toString();
+        String b = getClass().getResource("/bundles1.properties").toURI().toString();
+        String f = getClass().getResource("/features1.properties").toURI().toString();
+        String r = getClass().getResource("/regions1.properties").toURI().toString();
+
+        when(bundleContext.getBundle()).thenReturn(mock(Bundle.class));
+        when(bundleContext.getProperty(PROPERTIES_RESOURCE_PREFIX + IDBSNVER_FILENAME)).thenReturn(e);
+        when(bundleContext.getProperty(PROPERTIES_RESOURCE_PREFIX + BUNDLE_FEATURE_FILENAME)).thenReturn(b);
+        when(bundleContext.getProperty(PROPERTIES_RESOURCE_PREFIX + FEATURE_REGION_FILENAME)).thenReturn(f);
+        when(bundleContext.getProperty(PROPERTIES_RESOURCE_PREFIX + REGION_PACKAGE_FILENAME)).thenReturn(r);
+
+        regionConfiguration = new RegionConfiguration(bundleContext);
+
+        RegionPrinter printer = new RegionPrinter(bundleContext, regionConfiguration);
+        printer.printConfiguration(pw);
+        assertEquals(loadResource("printer/populated.txt"), sw.toString());
+    }
+}
diff --git a/src/test/resources/printer/empty.txt b/src/test/resources/printer/empty.txt
new file mode 100644
index 0000000..f8f3e98
--- /dev/null
+++ b/src/test/resources/printer/empty.txt
@@ -0,0 +1,32 @@
+Sling Feature API Regions
+===========================
+
+
+Default Regions
+-------------------
+
+
+
+Region Order
+-------------------
+
+
+
+Properties
+-------------------
+
+ - org.apache.sling.feature.apiregions.regions=null
+ - sling.feature.apiregions.joinglobal=null
+ - sling.feature.apiregions.default=null
+ - sling.feature.apiregions.location=null
+
+
+Packages per Region
+-------------------
+
+
+
+Bundle Mappings
+-------------------
+
+
diff --git a/src/test/resources/printer/populated.txt b/src/test/resources/printer/populated.txt
new file mode 100644
index 0000000..20f89d2
--- /dev/null
+++ b/src/test/resources/printer/populated.txt
@@ -0,0 +1,54 @@
+Sling Feature API Regions
+===========================
+
+
+Default Regions
+-------------------
+
+
+
+Region Order
+-------------------
+
+ - global
+ - internal
+
+
+Properties
+-------------------
+
+ - org.apache.sling.feature.apiregions.regions=null
+ - sling.feature.apiregions.joinglobal=null
+ - sling.feature.apiregions.default=null
+ - sling.feature.apiregions.location=null
+
+
+Packages per Region
+-------------------
+
+
+global:
+ - a.b.c
+ - d.e.f
+ - test
+
+internal:
+ - xyz
+
+
+Bundle Mappings
+-------------------
+
+ - org.sling:b1:1
+	 - features: org.sling:something:1.2.3:slingosgifeature:myclassifier
+	 - regions: 
+	 - location: null
+ - org.sling:b2:1
+	 - features: org.sling:something:1.2.3:slingosgifeature:myclassifier
+	 - regions: 
+	 - location: null
+ - org.sling:b3:1
+	 - features: some.other:feature:123,org.sling:something:1.2.3:slingosgifeature:myclassifier
+	 - regions: 
+	 - location: null
+