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

cziegeler pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/felix-dev.git


The following commit(s) were added to refs/heads/master by this push:
     new 0d0c5bc  FELIX-6464 add web console plugin for capabilities (#109)
0d0c5bc is described below

commit 0d0c5bc80482010c092169be15b2ecd6e949f72f
Author: Konrad Windszus <[email protected]>
AuthorDate: Tue Oct 19 10:25:35 2021 +0200

    FELIX-6464 add web console plugin for capabilities (#109)
    
    * FELIX-6464 add web console plugin for capabilities
    
    * add missing class
---
 .../webconsole/internal/core/BundlesServlet.java   |  18 ++-
 .../internal/core/CapabilitiesPrinter.java         | 140 +++++++++++++++++++++
 .../core/CapabilitiesProvidedInfoProvider.java     | 107 ++++++++++++++++
 .../core/CapabilitiesRequiredInfoProvider.java     |  76 +++++++++++
 .../webconsole/internal/servlet/OsgiManager.java   |   1 +
 .../main/resources/OSGI-INF/l10n/bundle.properties |   9 ++
 6 files changed, 350 insertions(+), 1 deletion(-)

diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/BundlesServlet.java
 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/BundlesServlet.java
index 723e731..69c62c1 100644
--- 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/BundlesServlet.java
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/BundlesServlet.java
@@ -125,6 +125,10 @@ public class BundlesServlet extends SimpleWebConsolePlugin 
implements OsgiManage
     // templates
     private final String TEMPLATE_MAIN;
 
+    private ServiceRegistration<BundleInfoProvider> bipCapabilitiesProvided;
+
+    private ServiceRegistration<BundleInfoProvider> bipCapabilitiesRequired;
+
     /** Default constructor */
     public BundlesServlet()
     {
@@ -166,6 +170,8 @@ public class BundlesServlet extends SimpleWebConsolePlugin 
implements OsgiManage
         props.put( WebConsoleConstants.CONFIG_PRINTER_MODES, new String[] { 
ConfigurationPrinter.MODE_TXT,
                 ConfigurationPrinter.MODE_ZIP } );
         configurationPrinter = bundleContext.registerService( 
ConfigurationPrinter.class, this, props );
+        bipCapabilitiesProvided = bundleContext.registerService( 
BundleInfoProvider.class, new CapabilitiesProvidedInfoProvider( 
bundleContext.getBundle() ), null );
+        bipCapabilitiesRequired = bundleContext.registerService( 
BundleInfoProvider.class, new CapabilitiesRequiredInfoProvider( 
bundleContext.getBundle() ), null );
     }
 
 
@@ -181,12 +187,22 @@ public class BundlesServlet extends 
SimpleWebConsolePlugin implements OsgiManage
             configurationPrinter = null;
         }
 
-        if ( bundleInfoTracker != null)
+        if ( bundleInfoTracker != null )
         {
             bundleInfoTracker.close();
             bundleInfoTracker = null;
         }
 
+        if ( bipCapabilitiesProvided != null )
+        {
+            bipCapabilitiesProvided.unregister();
+            bipCapabilitiesProvided = null;
+        }
+        if ( bipCapabilitiesRequired != null )
+        {
+            bipCapabilitiesRequired.unregister();
+            bipCapabilitiesRequired = null;
+        }
         super.deactivate();
     }
 
diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/CapabilitiesPrinter.java
 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/CapabilitiesPrinter.java
new file mode 100644
index 0000000..5c1cb34
--- /dev/null
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/CapabilitiesPrinter.java
@@ -0,0 +1,140 @@
+/*
+ * 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.felix.webconsole.internal.core;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Array;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import org.apache.felix.webconsole.internal.AbstractConfigurationPrinter;
+import org.apache.felix.webconsole.internal.misc.ConfigurationRender;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.namespace.HostNamespace;
+import org.osgi.framework.namespace.PackageNamespace;
+import org.osgi.framework.wiring.BundleCapability;
+import org.osgi.framework.wiring.BundleWiring;
+
+
+public class CapabilitiesPrinter extends AbstractConfigurationPrinter
+{
+
+    private static final String TITLE = "Capabilities";
+
+    static final List<String> EXCLUDED_NAMESPACES = Arrays.asList( 
PackageNamespace.PACKAGE_NAMESPACE, HostNamespace.HOST_NAMESPACE );
+    static final Predicate<String> EXCLUDED_NAMESPACES_PREDICATE = new 
ExcludedNamespacesPredicate();
+
+    public String getTitle()
+    {
+        return TITLE;
+    }
+
+    static final class ExcludedNamespacesPredicate implements Predicate<String>
+    {
+        @Override
+        public boolean test(String namespace) {
+            return !EXCLUDED_NAMESPACES.contains(namespace);
+        }
+    }
+
+    public void printConfiguration( PrintWriter printWriter )
+    {
+        for ( Bundle bundle : getBundleContext().getBundles() )
+        {
+            BundleWiring wiring = bundle.adapt( BundleWiring.class );
+            
+            List<BundleCapability> capabilities = wiring.getCapabilities( null 
).stream()
+                    .filter( c -> 
EXCLUDED_NAMESPACES_PREDICATE.test(c.getNamespace()) )
+                    .collect( Collectors.toList() );
+            if ( capabilities.isEmpty() )
+            {
+                // skip bundles not exporting capabilities
+                continue;
+            }
+            
+            ConfigurationRender.infoLine( printWriter, null,  "Bundle", 
bundle.getSymbolicName() + " (" + bundle.getBundleId() + ")" );
+            for ( BundleCapability capability : capabilities )
+            {
+                ConfigurationRender.infoLine( printWriter, "  ", "Capability 
namespace", capability.getNamespace() );
+                String attributes = dumpTypedAttributes( 
capability.getAttributes() );
+                if ( !attributes.isEmpty() ) 
+                {
+                    ConfigurationRender.infoLine( printWriter, "    ", 
"Attributes", attributes );
+                }
+                String directives = dumpDirectives( capability.getDirectives() 
);
+                if ( !directives.isEmpty() )
+                {
+                    ConfigurationRender.infoLine( printWriter, "    ", 
"Directives", directives );
+                }
+                String requirerBundles = wiring.getProvidedWires( 
capability.getNamespace() ).stream()
+                        .map( w -> w.getRequirer().getSymbolicName() + " (" + 
w.getRequirer().getBundle().getBundleId() + ") with directives: " + 
dumpDirectives( w.getRequirement().getDirectives() ) )
+                        .collect( Collectors.joining( ", ") );
+                if ( !requirerBundles.isEmpty() )
+                {
+                    ConfigurationRender.infoLine( printWriter, "    ", 
"Required By", requirerBundles );
+                }
+            }
+        }
+    }
+
+    static String dumpTypedAttributes( Map<String, Object> typedAttributes )
+    {
+        StringBuilder attributes = new StringBuilder();
+        boolean isFirst = true;
+        for ( Map.Entry<String, Object> entry : typedAttributes.entrySet() )
+        {
+            String value;
+            if ( entry.getValue().getClass().isArray() ) 
+            {
+                StringBuilder values = new StringBuilder( "[" );
+                for ( int i=0; i<Array.getLength(entry.getValue()); i++ ) 
+                {
+                    if ( i > 0 )
+                    {
+                        values.append( ", " );
+                    }
+                    values.append( Array.get( entry.getValue(), i ) );
+                }
+                value = values.append( "]" ).toString();
+            }
+            else 
+            {
+                value = entry.getValue().toString();
+            }
+            if ( isFirst )
+            {
+                isFirst = false;
+            }
+            else
+            {
+                attributes.append( ", " );
+            }
+            attributes.append( entry.getKey() ).append( "=" ).append( value );
+        }
+        return attributes.toString();
+    }
+
+    static String dumpDirectives( Map<String, String> directives )
+    {
+        return directives.entrySet().stream().map( e -> e.getKey() + "=" + 
e.getValue() ).collect( Collectors.joining( " ," ) );
+    }
+}
\ No newline at end of file
diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/CapabilitiesProvidedInfoProvider.java
 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/CapabilitiesProvidedInfoProvider.java
new file mode 100644
index 0000000..0ad62b2
--- /dev/null
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/CapabilitiesProvidedInfoProvider.java
@@ -0,0 +1,107 @@
+/*
+ * 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.felix.webconsole.internal.core;
+
+
+import java.text.MessageFormat;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import org.apache.felix.webconsole.bundleinfo.BundleInfo;
+import org.apache.felix.webconsole.bundleinfo.BundleInfoProvider;
+import org.apache.felix.webconsole.bundleinfo.BundleInfoType;
+import org.apache.felix.webconsole.i18n.LocalizationHelper;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.namespace.BundleNamespace;
+import org.osgi.framework.namespace.IdentityNamespace;
+import org.osgi.framework.wiring.BundleCapability;
+import org.osgi.framework.wiring.BundleWiring;
+
+
+public class CapabilitiesProvidedInfoProvider implements BundleInfoProvider
+{
+    private final LocalizationHelper localization;
+
+    // hide some namespace except if required by some other bundle
+    // TODO: add more namespaces from 
http://docs.osgi.org/specification/osgi.cmpn/7.0.0/service.namespaces.html
+    private static final List<String> STANDARD_NAMESPACES = Arrays.asList( 
BundleNamespace.BUNDLE_NAMESPACE, IdentityNamespace.IDENTITY_NAMESPACE );
+    
+    CapabilitiesProvidedInfoProvider( Bundle bundle )
+    {
+        localization = new LocalizationHelper( bundle );
+    }
+
+    @Override
+    public String getName( Locale locale ) 
+    {
+        return localization.getResourceBundle( locale ).getString( 
"capabilities.provided.info.name" ); //$NON-NLS-1$;
+    }
+
+    /**
+     * Hides those capabilities which are exposed in a dedicated section and 
all default bundle namespaces which are not required by at least one other 
bundle.
+     *
+     */
+    private static final class CapabilityFilter implements 
Predicate<BundleCapability>
+    {
+        private BundleWiring wiring;
+        
+        CapabilityFilter(BundleWiring wiring)
+        {
+            this.wiring = wiring;
+        }
+
+        @Override
+        public boolean test(BundleCapability t)
+        {
+            return 
(CapabilitiesPrinter.EXCLUDED_NAMESPACES_PREDICATE.test(t.getNamespace())) && 
+            !(STANDARD_NAMESPACES.contains(t.getNamespace()) && 
wiring.getProvidedWires(t.getNamespace()).isEmpty());
+        }
+    }
+
+    @Override
+    public BundleInfo[] getBundleInfo( Bundle bundle, String webConsoleRoot, 
Locale locale )
+    {
+        BundleWiring wiring = bundle.adapt( BundleWiring.class );
+        Predicate<BundleCapability> capabilityFilter = new CapabilityFilter( 
wiring );
+        // which capabilities to filter?
+        List<BundleCapability> capabilities = wiring.getCapabilities( null 
).stream()
+                .filter( capabilityFilter )
+                .collect( Collectors.toList() );
+        return capabilities.stream().map( c -> toInfo( c, wiring, 
webConsoleRoot, locale ) ).toArray( BundleInfo[]::new );
+    }
+
+    private BundleInfo toInfo( BundleCapability capability, BundleWiring 
wiring, String webConsoleRoot, Locale locale )
+    {
+        final String descr = localization.getResourceBundle( locale 
).getString( "capabilities.provided.info.descr" ); //$NON-NLS-1$;
+        String name = localization.getResourceBundle( locale ).getString( 
"capabilities.provided.info.key" ); //$NON-NLS-1$;
+        String requirerBundles = wiring.getProvidedWires( 
capability.getNamespace() ).stream()
+                .map( w -> w.getRequirer().getSymbolicName() + " (" + 
w.getRequirer().getBundle().getBundleId() + ")" )
+                .collect( Collectors.joining( ", ") );
+        name = MessageFormat.format( name, capability.getNamespace(), 
CapabilitiesPrinter.dumpTypedAttributes( capability.getAttributes() ) );
+        if ( !requirerBundles.isEmpty() )
+        {
+            name += MessageFormat.format( localization.getResourceBundle( 
locale ).getString( "capabilities.provided.info.key.addition" ), 
requirerBundles );
+        }
+        // use empty link type to prevent the pattern <name>=<value> being 
used for printing
+        return new BundleInfo( name, "/#", BundleInfoType.LINK, descr );
+    }
+}
\ No newline at end of file
diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/CapabilitiesRequiredInfoProvider.java
 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/CapabilitiesRequiredInfoProvider.java
new file mode 100644
index 0000000..2136948
--- /dev/null
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/CapabilitiesRequiredInfoProvider.java
@@ -0,0 +1,76 @@
+/*
+ * 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.felix.webconsole.internal.core;
+
+
+import java.text.MessageFormat;
+import java.util.Locale;
+import java.util.Optional;
+
+import org.apache.felix.webconsole.bundleinfo.BundleInfo;
+import org.apache.felix.webconsole.bundleinfo.BundleInfoProvider;
+import org.apache.felix.webconsole.bundleinfo.BundleInfoType;
+import org.apache.felix.webconsole.i18n.LocalizationHelper;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.wiring.BundleRequirement;
+import org.osgi.framework.wiring.BundleWiring;
+
+
+public class CapabilitiesRequiredInfoProvider implements BundleInfoProvider
+{
+    private final LocalizationHelper localization;
+
+    CapabilitiesRequiredInfoProvider( Bundle bundle )
+    {
+        localization = new LocalizationHelper( bundle );
+    }
+
+    @Override
+    public String getName( Locale locale )
+    {
+        return localization.getResourceBundle( locale ).getString( 
"capabilities.required.info.name" ); //$NON-NLS-1$;
+    }
+
+    @Override
+    public BundleInfo[] getBundleInfo( Bundle bundle, String webConsoleRoot, 
Locale locale )
+    {
+        BundleWiring wiring = bundle.adapt( BundleWiring.class );
+        return wiring.getRequirements( null ).stream()
+            .filter( t -> 
CapabilitiesPrinter.EXCLUDED_NAMESPACES_PREDICATE.test(t.getNamespace()))
+            .map( r -> toInfo( r, wiring, webConsoleRoot, locale ) ).toArray( 
BundleInfo[]::new );
+    }
+
+    private BundleInfo toInfo( BundleRequirement requirement, BundleWiring 
wiring, String webConsoleRoot, Locale locale )
+    {
+        final String descr = localization.getResourceBundle( locale 
).getString( "capabilities.required.info.descr" ); //$NON-NLS-1$;
+        Optional<Bundle> providerBundle = wiring.getRequiredWires( 
requirement.getNamespace() ).stream()
+                .map( w -> w.getProvider().getBundle() ).findFirst(); // only 
the first one is returned
+        String name = localization.getResourceBundle( locale ).getString( 
"capabilities.required.info.key" ); //$NON-NLS-1$;
+        name = MessageFormat.format( name, requirement.getNamespace(), 
CapabilitiesPrinter.dumpDirectives( requirement.getDirectives() ) );
+        String link = "/#"; // use empty link type to prevent the pattern 
<name>=<value> being used for printing
+        if ( providerBundle.isPresent() )
+        {
+           name += MessageFormat.format( localization.getResourceBundle( 
locale ).getString( "capabilities.required.info.key.addition" ), 
providerBundle.get().getSymbolicName(), providerBundle.get().getBundleId() );
+           if ( webConsoleRoot != null ) {
+               link = webConsoleRoot + "/bundles/" + 
providerBundle.get().getBundleId(); //$NON-NLS-1$
+           }
+        }
+        return new BundleInfo( name, link, BundleInfoType.LINK, descr );
+    }
+}
\ No newline at end of file
diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java
 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java
index 9261e8d..94c7bf4 100644
--- 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java
@@ -203,6 +203,7 @@ public class OsgiManager extends GenericServlet
             
"org.apache.felix.webconsole.internal.compendium.PreferencesConfigurationPrinter",
 //$NON-NLS-1$
             
"org.apache.felix.webconsole.internal.compendium.WireAdminConfigurationPrinter",
 //$NON-NLS-1$
             
"org.apache.felix.webconsole.internal.core.BundlesConfigurationPrinter", 
//$NON-NLS-1$
+            "org.apache.felix.webconsole.internal.core.CapabilitiesPrinter", 
//$NON-NLS-1$
             
"org.apache.felix.webconsole.internal.core.PermissionsConfigurationPrinter", 
//$NON-NLS-1$
             
"org.apache.felix.webconsole.internal.core.ServicesConfigurationPrinter", 
//$NON-NLS-1$
             
"org.apache.felix.webconsole.internal.misc.SystemPropertiesPrinter", 
//$NON-NLS-1$
diff --git a/webconsole/src/main/resources/OSGI-INF/l10n/bundle.properties 
b/webconsole/src/main/resources/OSGI-INF/l10n/bundle.properties
index 9cc31c6..7792a48 100644
--- a/webconsole/src/main/resources/OSGI-INF/l10n/bundle.properties
+++ b/webconsole/src/main/resources/OSGI-INF/l10n/bundle.properties
@@ -73,6 +73,15 @@ vmstat.mem.free=Free Memory
 vmstat.gc.title=Garbage Collection
 vmstat.gc.button=Run
 
+capabilities.provided.info.name=Provided Capabilities
+capabilities.provided.info.key=Capability "{0}" with attributes {1}
+capabilities.provided.info.key.addition=\ (required by {0})
+capabilities.provided.info.descr=Capability provided by this bundle
+capabilities.required.info.name=Required Capabilities
+capabilities.required.info.key=Capability "{0}" with directives {1}
+capabilities.required.info.key.addition=\ (provided by {0} ({1}))
+capabilities.required.info.descr=Capability required by this bundle
+
 # Services plugin
 services.pluginTitle=Services
 services.details.hide=Hide Details

Reply via email to