Merge pull request #16 from apache/SLING-11006-regions-webconsole
SLING-11006: Add API Regions Webconsole Status
diff --git a/pom.xml b/pom.xml
index cdce5f3..7b1f90c 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>
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 @@
BundleContext bundleContext;
ServiceRegistration<ResolverHookFactory> hookRegistration;
+ ServiceRegistration<RegionPrinter> webconsoleRegistration;
RegionConfiguration configuration;
@@ -74,6 +75,7 @@
registerHook();
+ registerWebconsoleStatus();
this.configAdminTracker = new ServiceTracker<>(context, CONFIG_ADMIN_CLASS_NAME, new ServiceTrackerCustomizer<Object, Object>() {
@Override
@@ -100,8 +102,11 @@
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 @@
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 @@
}
}
+ 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 @@
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..cdedcb8
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/apiregions/impl/RegionPrinter.java
@@ -0,0 +1,113 @@
+/*
+ * 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.Collection;
+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 = "feature_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))));
+ }
+
+ private void printAll(Collection<String> op, PrintWriter pw) {
+ Optional.ofNullable(op).ifPresent(values -> values.forEach(v -> pw.println(" - " + v)));
+ }
+
+ /**
+ * Print out the region information
+ *
+ * @see org.apache.felix.webconsole.ConfigurationPrinter#printConfiguration(java.io.PrintWriter)
+ */
+ public void printConfiguration(PrintWriter pw) {
+ pw.println(HEADLINE + "\n===========================");
+
+ if (config != null) {
+ renderHeader(pw, "Default Regions");
+ printAll(config.getDefaultRegions(), pw);
+ renderHeader(pw, "Region Order");
+ printAll(config.getGlobalRegionOrder(), pw);
+ renderHeader(pw, "Properties");
+ renderProperties(pw);
+ renderHeader(pw, "Packages per Region");
+ renderPackageMappings(pw);
+ renderHeader(pw, "Bundle Mappings");
+ renderBundleMappings(pw);
+ } else {
+ pw.println("\n\nConfiguration not available");
+ }
+ }
+
+}
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 @@
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 @@
a.registerHook();
assertNull(a.hookRegistration);
+ assertNull(a.webconsoleRegistration);
}
@SuppressWarnings("unchecked")
@@ -132,9 +142,12 @@
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 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..7a78a38
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/apiregions/impl/RegionPrinterTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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 testNoConfiguration() {
+ RegionPrinter printer = new RegionPrinter(bundleContext, null);
+ printer.printConfiguration(pw);
+ assertEquals(loadResource("printer/noconfig.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..e406330
--- /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/noconfig.txt b/src/test/resources/printer/noconfig.txt
new file mode 100644
index 0000000..9e12113
--- /dev/null
+++ b/src/test/resources/printer/noconfig.txt
@@ -0,0 +1,6 @@
+Sling Feature - API Regions
+===========================
+
+
+Configuration not available
+
diff --git a/src/test/resources/printer/populated.txt b/src/test/resources/printer/populated.txt
new file mode 100644
index 0000000..7e1fcc7
--- /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
+