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(" ");
+ }
+ 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());
+ }
+
+ }
+}