FELIX-3007 Simple Package Admin Service plugin dumping duplicate package exports

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1137979 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/webconsole-plugins/packageadmin/pom.xml b/webconsole-plugins/packageadmin/pom.xml
new file mode 100644
index 0000000..8042f73
--- /dev/null
+++ b/webconsole-plugins/packageadmin/pom.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+    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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>felix-parent</artifactId>
+        <version>1.2.0</version>
+        <relativePath>../../../pom/pom.xml</relativePath>
+    </parent>
+
+    <artifactId>org.apache.felix.webconsole.plugins.packageadmin</artifactId>
+    <packaging>bundle</packaging>
+    <version>0.0.1-SNAPSHOT</version>
+
+    <name>Apache Felix Web Console Package Admin Service Plugin</name>
+    <description>
+        Plugin providing support to query the Package Admin service
+        for various details; e.g. finding duplicate package exports
+        or finding bundles exporting given packages
+    </description>
+
+    <scm>
+        <connection>
+            scm:svn:http://svn.apache.org/repos/asf/felix/trunk/webconsole-plugins/memoryusage
+        </connection>
+        <developerConnection>
+            scm:svn:https://svn.apache.org/repos/asf/felix/trunk/webconsole-plugins/memoryusage
+        </developerConnection>
+        <url>
+            http://svn.apache.org/viewvc/felix/trunk/webconsole-plugins/memoryusage
+        </url>
+    </scm>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>1.5</source>
+                    <target>1.5</target>
+                </configuration>
+             </plugin>
+             <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <version>2.0.1</version>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Bundle-SymbolicName>
+                            ${project.artifactId}
+                        </Bundle-SymbolicName>
+                        <Import-Package>
+                            *
+                        </Import-Package>
+                        <Private-Package>
+                            org.apache.felix.webconsole.plugins.packageadmin.*
+                        </Private-Package>
+                        <Bundle-Activator>
+                            org.apache.felix.webconsole.plugins.packageadmin.internal.Activator
+                        </Bundle-Activator>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+    <dependencies>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+            <version>4.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+            <version>4.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+            <version>2.3</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/webconsole-plugins/packageadmin/src/main/java/org/apache/felix/webconsole/plugins/packageadmin/internal/Activator.java b/webconsole-plugins/packageadmin/src/main/java/org/apache/felix/webconsole/plugins/packageadmin/internal/Activator.java
new file mode 100644
index 0000000..008d2b5
--- /dev/null
+++ b/webconsole-plugins/packageadmin/src/main/java/org/apache/felix/webconsole/plugins/packageadmin/internal/Activator.java
@@ -0,0 +1,54 @@
+/*
+ * 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.plugins.packageadmin.internal;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.util.tracker.ServiceTracker;
+
+public class Activator implements BundleActivator {
+
+    private ServiceTracker pkgAdminTracker;
+
+    private ServiceRegistration pkgAdminPlugin;
+
+    public void start(BundleContext context) throws Exception {
+        this.pkgAdminTracker = new ServiceTracker(context,
+            "org.osgi.service.packageadmin.PackageAdmin", null);
+        this.pkgAdminTracker.open();
+
+        final PackageAdminPlugin plugin = new PackageAdminPlugin(context,
+            pkgAdminTracker);
+        final Dictionary<String, Object> props = new Hashtable<String, Object>();
+        props.put("felix.webconsole.label", PackageAdminPlugin.LABEL);
+        props.put("felix.webconsole.title", PackageAdminPlugin.TITLE);
+        this.pkgAdminPlugin = context.registerService("javax.servlet.Servlet",
+            plugin, props);
+    }
+
+    public void stop(BundleContext context) throws Exception {
+        this.pkgAdminPlugin.unregister();
+        this.pkgAdminTracker.close();
+    }
+
+}
diff --git a/webconsole-plugins/packageadmin/src/main/java/org/apache/felix/webconsole/plugins/packageadmin/internal/PackageAdminPlugin.java b/webconsole-plugins/packageadmin/src/main/java/org/apache/felix/webconsole/plugins/packageadmin/internal/PackageAdminPlugin.java
new file mode 100644
index 0000000..3fe31fc
--- /dev/null
+++ b/webconsole-plugins/packageadmin/src/main/java/org/apache/felix/webconsole/plugins/packageadmin/internal/PackageAdminPlugin.java
@@ -0,0 +1,170 @@
+/*
+ * 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.plugins.packageadmin.internal;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Comparator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import javax.servlet.GenericServlet;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletResponse;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.packageadmin.ExportedPackage;
+import org.osgi.service.packageadmin.PackageAdmin;
+import org.osgi.util.tracker.ServiceTracker;
+
+public class PackageAdminPlugin extends GenericServlet {
+
+    static final String LABEL = "pkgadmin";
+
+    static final String TITLE = "Package Admin";
+
+    private static final Comparator<ExportedPackage> EXPORT_PACKAGE_COMPARATOR = new ExportedPackageComparator();
+
+    private final BundleContext bundleContext;
+
+    private final ServiceTracker pkgAdminTracker;
+
+    PackageAdminPlugin(final BundleContext bundleContext,
+            final ServiceTracker pkgAdminTracker) {
+        this.bundleContext = bundleContext;
+        this.pkgAdminTracker = pkgAdminTracker;
+    }
+
+    public void service(ServletRequest req, ServletResponse res)
+            throws IOException {
+
+        final PrintWriter pw = ((HttpServletResponse) res).getWriter();
+
+        final PackageAdmin pa = (PackageAdmin) this.pkgAdminTracker.getService();
+        if (pa == null) {
+            printStatLine(pw, "PackageAdmin Service not registered");
+            return;
+        }
+
+        try {
+            Map<String, Set<ExportedPackage>> exports = collectExportedPackages(pa);
+            printStatLine(pw,
+                "PackageAdmin service reports %d exported packages",
+                exports.size());
+            dumpDuplicates(pw, exports);
+        } catch (Exception e) {
+            pw.println("failure ...." + e);
+            e.printStackTrace(pw);
+        }
+    }
+
+    private Map<String, Set<ExportedPackage>> collectExportedPackages(
+            final PackageAdmin pa) {
+        Map<String, Set<ExportedPackage>> exports = new TreeMap<String, Set<ExportedPackage>>();
+
+        for (Bundle bundle : this.bundleContext.getBundles()) {
+            final ExportedPackage[] bundleExports = pa.getExportedPackages(bundle);
+            if (bundleExports != null) {
+                for (ExportedPackage exportedPackage : bundleExports) {
+                    Set<ExportedPackage> exportSet = exports.get(exportedPackage.getName());
+                    if (exportSet == null) {
+                        exportSet = new TreeSet<ExportedPackage>(
+                            EXPORT_PACKAGE_COMPARATOR);
+                        exports.put(exportedPackage.getName(), exportSet);
+                    }
+                    exportSet.add(exportedPackage);
+                }
+            }
+        }
+
+        return exports;
+    }
+
+    private void printStatLine(final PrintWriter pw, final String format,
+            final Object... args) {
+        final String message = String.format(format, args);
+        pw.printf("<p class=\"statline\">%s</p>%n", message);
+    }
+
+    private void printTitle(final PrintWriter pw, final String format,
+            final Object... args) {
+        pw.println("<div class=\"ui-widget-header ui-corner-top\">");
+        pw.printf(format, args);
+        pw.println("</div>");
+    }
+
+    private void dumpDuplicates(final PrintWriter pw,
+            final Map<String, Set<ExportedPackage>> exports) {
+        printTitle(pw, "Duplicate Exported Packages");
+        pw.println("<table class=\"nicetable\">");
+        pw.println("<tr><th>Package</th><th>Exports</th><th>Imports</th></tr>");
+        for (Entry<String, Set<ExportedPackage>> exportEntry : exports.entrySet()) {
+            Set<ExportedPackage> exportSet = exportEntry.getValue();
+            if (exportSet.size() > 1) {
+                String firstCol = String.format("<td rowspan=\"%s\">%s</td>",
+                    exportSet.size(), exportEntry.getKey());
+                for (ExportedPackage exportedPackage : exportSet) {
+                    Bundle[] importers = exportedPackage.getImportingBundles();
+                    pw.printf("<tr>%s<td>version=%s, Bundle %s</td><td>",
+                        firstCol, exportedPackage.getVersion(),
+                        exportedPackage.getExportingBundle());
+                    if (importers != null && importers.length > 0) {
+                        for (Bundle bundle : importers) {
+                            pw.printf("%s<br>", bundle);
+                        }
+                    } else {
+                        pw.print("&nbsp;");
+                    }
+                    pw.println("</td></tr>");
+                    firstCol = "";
+                }
+            }
+        }
+        pw.println("</table>");
+    }
+
+    private static class ExportedPackageComparator implements
+            Comparator<ExportedPackage> {
+
+        public int compare(ExportedPackage o1, ExportedPackage o2) {
+            if (o1 == o2) {
+                return 0;
+            }
+
+            int name = o1.getName().compareTo(o2.getName());
+            if (name != 0) {
+                return name;
+            }
+
+            int version = o1.getVersion().compareTo(o2.getVersion());
+            if (version != 0) {
+                return version;
+            }
+
+            return Long.valueOf(o1.getExportingBundle().getBundleId()).compareTo(
+                o2.getExportingBundle().getBundleId());
+        }
+
+    }
+}