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 <[email protected]> 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 +
