Fixed FELIX-3476 Make package admin to use SimpleWebConsolePlugin
https://issues.apache.org/jira/browse/FELIX-3476

Fixed FELIX-3474 Make package admin work with J9 and other embedded VMs
https://issues.apache.org/jira/browse/FELIX-3474


git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1330237 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/webconsole-plugins/packageadmin/pom.xml b/webconsole-plugins/packageadmin/pom.xml
index 814fa1c..9ff5703 100644
--- a/webconsole-plugins/packageadmin/pom.xml
+++ b/webconsole-plugins/packageadmin/pom.xml
@@ -51,28 +51,48 @@
     </scm>
 
     <build>
+		<!-- add UTF-8-to-ISO translated resources -->
+		<resources>
+			<resource>
+				<directory>${basedir}/src/main/resources</directory>
+			</resource>
+			<resource>
+				<directory>target/classes</directory>
+				<includes>
+					<include>OSGI-INF/**</include>
+				</includes>
+				<filtering>false</filtering>
+			</resource>
+		</resources>
+
         <plugins>
+			<!-- translate UTF-8 encoded properties files to ISO-8859-1 -->
             <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-compiler-plugin</artifactId>
-                <configuration>
-                    <source>1.5</source>
-                    <target>1.5</target>
-                </configuration>
-             </plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>native2ascii-maven-plugin</artifactId>
+                <version>1.0-beta-1</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>native2ascii</goal>
+                        </goals>
+                        <configuration>
+                            <encoding>UTF-8</encoding>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+
              <plugin>
                 <groupId>org.apache.felix</groupId>
                 <artifactId>maven-bundle-plugin</artifactId>
-                <version>2.0.1</version>
+                <version>2.3.6</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>
@@ -82,6 +102,18 @@
                     </instructions>
                 </configuration>
             </plugin>
+			<plugin>
+                <groupId>org.apache.rat</groupId>
+                <artifactId>apache-rat-plugin</artifactId>
+                <configuration>
+                    <includes>
+                        <include>src/**</include>
+                    </includes>
+                    <excludes>
+                        <exclude>src/main/resources/res/plugin.html</exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
         </plugins>
     </build>
     <dependencies>
@@ -103,5 +135,23 @@
             <version>2.3</version>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>1.4</version>
+            <scope>provided</scope>
+        </dependency>
+		<dependency>
+			<groupId>org.apache.felix</groupId>
+			<artifactId>org.apache.felix.webconsole</artifactId>
+			<version>3.1.8</version>
+			<scope>provided</scope>
+		</dependency>
+        <dependency>
+            <groupId>org.json</groupId>
+            <artifactId>json</artifactId>
+            <version>20070829</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
index 01fc800..0f2cd85 100644
--- 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
@@ -21,64 +21,106 @@
 import java.util.Dictionary;
 import java.util.Hashtable;
 
-import org.osgi.framework.*;
+import org.apache.felix.webconsole.ConfigurationPrinter;
+import org.apache.felix.webconsole.SimpleWebConsolePlugin;
+import org.apache.felix.webconsole.WebConsoleConstants;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
 import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
 
-public class Activator implements BundleActivator
+/**
+ * This is the main starting class of the bundle.
+ */
+public class Activator implements BundleActivator, ServiceTrackerCustomizer
 {
 
     private ServiceTracker pkgAdminTracker;
 
-    private ServiceRegistration pkgAdminPlugin;
+    private BundleContext context;
+    private SimpleWebConsolePlugin plugin;
+    private ServiceRegistration printerReg;
 
-    private ServiceRegistration depFinderPlugin;
-
+    /**
+     * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
+     */
     public void start(final BundleContext context) throws Exception
     {
-        this.pkgAdminTracker = new ServiceTracker(context, "org.osgi.service.packageadmin.PackageAdmin", null);
+        this.context = context;
+        this.pkgAdminTracker = new ServiceTracker(context,
+            "org.osgi.service.packageadmin.PackageAdmin", this); //$NON-NLS-1$
         this.pkgAdminTracker.open();
 
-        registerPackageAdminPlugin(context);
-        registerDependencyFinderPlugin(context);
+        // register configuration printer
+        final Dictionary/*<String, Object>*/props = new Hashtable/*<String, Object>*/();
+        props.put(WebConsoleConstants.CONFIG_PRINTER_MODES, new String[] {
+                ConfigurationPrinter.MODE_ZIP, ConfigurationPrinter.MODE_TXT });
+        printerReg = context.registerService(
+            "org.apache.felix.webconsole.ConfigurationPrinter", //$NON-NLS-1$
+            new WebConsolePrinter(context, pkgAdminTracker), props);
     }
 
-    private void registerPackageAdminPlugin(final BundleContext context)
-    {
-        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);
-        props.put("felix.webconsole.configprinter.modes", new String[]
-            { "zip", "txt" });
-        this.pkgAdminPlugin = context.registerService("javax.servlet.Servlet", plugin, props);
-    }
-
-    private void registerDependencyFinderPlugin(final BundleContext context)
-    {
-        final DependencyFinderPlugin plugin = new DependencyFinderPlugin(context, pkgAdminTracker);
-        final Dictionary<String, Object> props = new Hashtable<String, Object>();
-        props.put("felix.webconsole.label", DependencyFinderPlugin.LABEL);
-        props.put("felix.webconsole.title", DependencyFinderPlugin.TITLE);
-        this.depFinderPlugin = context.registerService("javax.servlet.Servlet", plugin, props);
-    }
-
+    /**
+     * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
+     */
     public void stop(final BundleContext context) throws Exception
     {
-        if (this.pkgAdminPlugin != null)
+        if (printerReg != null)
         {
-            this.pkgAdminPlugin.unregister();
-            this.pkgAdminPlugin = null;
+            printerReg.unregister();
+            printerReg = null;
         }
-        if (this.depFinderPlugin != null)
-        {
-            this.depFinderPlugin.unregister();
-            this.depFinderPlugin = null;
-        }
+
         if (this.pkgAdminTracker != null)
         {
             this.pkgAdminTracker.close();
             this.pkgAdminTracker = null;
         }
+
+        this.context = null;
+    }
+
+    // - begin tracker
+    /**
+     * @see org.osgi.util.tracker.ServiceTrackerCustomizer#modifiedService(org.osgi.framework.ServiceReference,
+     *      java.lang.Object)
+     */
+    public final void modifiedService(ServiceReference reference, Object service)
+    {/* unused */
+    }
+
+    /**
+     * @see org.osgi.util.tracker.ServiceTrackerCustomizer#addingService(org.osgi.framework.ServiceReference)
+     */
+    public final Object addingService(ServiceReference reference)
+    {
+        SimpleWebConsolePlugin plugin = this.plugin;
+        Object ret = null;
+        if (plugin == null)
+        {
+            ret = context.getService(reference);
+            this.plugin = plugin = new WebConsolePlugin(context, ret).register(context);
+        }
+
+        return ret;
+    }
+
+    /**
+     * @see org.osgi.util.tracker.ServiceTrackerCustomizer#removedService(org.osgi.framework.ServiceReference,
+     *      java.lang.Object)
+     */
+    public final void removedService(ServiceReference reference, Object service)
+    {
+        SimpleWebConsolePlugin plugin = this.plugin;
+
+        if (pkgAdminTracker.size() <= 1 && plugin != null)
+        {
+            plugin.unregister();
+            this.plugin = null;
+        }
+
     }
 
 }
diff --git a/webconsole-plugins/packageadmin/src/main/java/org/apache/felix/webconsole/plugins/packageadmin/internal/DependencyFinderPlugin.java b/webconsole-plugins/packageadmin/src/main/java/org/apache/felix/webconsole/plugins/packageadmin/internal/DependencyFinderPlugin.java
deleted file mode 100644
index 3f2e614..0000000
--- a/webconsole-plugins/packageadmin/src/main/java/org/apache/felix/webconsole/plugins/packageadmin/internal/DependencyFinderPlugin.java
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
- * 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.InputStream;
-import java.io.PrintWriter;
-import java.net.URL;
-import java.util.Enumeration;
-import java.util.LinkedHashSet;
-import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
-
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-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;
-
-@SuppressWarnings("serial")
-public class DependencyFinderPlugin extends HttpServlet
-{
-
-    static final String LABEL = "depfinder";
-
-    static final String TITLE = "Dependency Finder";
-
-    private static final String PARAM_FIND = "plugin.find";
-
-    private static final String PARAM_SUBMIT = "plugin.submit";
-
-    private static final String INDENT = "    ";
-
-    private final BundleContext bundleContext;
-
-    private final ServiceTracker pkgAdminTracker;
-
-    DependencyFinderPlugin(final BundleContext bundleContext, final ServiceTracker pkgAdminTracker)
-    {
-        this.bundleContext = bundleContext;
-        this.pkgAdminTracker = pkgAdminTracker;
-    }
-
-    private void drawForm(final PrintWriter pw, String findField)
-    {
-        titleHtml(pw, "Dependency Finder", "Enter a list of package or class names");
-        pw.println("<tr>");
-        pw.println("<td>Packages/Classes</td>");
-        pw.print("<td colspan='2'>");
-        pw.print("<form method='GET'>");
-        pw.println("<textarea rows='10' cols='80' name='" + PARAM_FIND + "'>" + (findField != null ? findField : "")
-            + "</textarea>");
-        pw.println("&nbsp;&nbsp;<input type='submit' name='" + PARAM_SUBMIT + "' value='Find' class='submit'>");
-        pw.print("</form>");
-        pw.print("</td>");
-        pw.println("</tr>");
-    }
-
-    private void endTable(final PrintWriter pw)
-    {
-        pw.println("</table>");
-    }
-
-    private void startTable(final PrintWriter pw)
-    {
-        pw.println("<table class='nicetable'>");
-    }
-
-    private void titleHtml(PrintWriter pw, String title, String description)
-    {
-        pw.println("<tr>");
-        pw.println("<th colspan='3'>" + title + "</th>");
-        pw.println("</tr>");
-
-        if (description != null)
-        {
-            pw.println("<tr>");
-            pw.println("<td colspan='3'>" + description + "</th>");
-            pw.println("</tr>");
-        }
-    }
-
-    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);
-    }
-
-    @Override
-    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
-    {
-        final PrintWriter pw = resp.getWriter();
-
-        final PackageAdmin pa = (PackageAdmin) this.pkgAdminTracker.getService();
-        if (pa == null)
-        {
-            printStatLine(pw, "PackageAdmin Service not registered");
-            return;
-        }
-
-        printStatLine(pw, "Find exported packages and classes");
-
-        startTable(pw);
-        String findField = req.getParameter(PARAM_FIND);
-        if (findField != null)
-        {
-            SortedSet<String> packageNames = getPackageNames(findField);
-            if (!packageNames.isEmpty())
-            {
-                Set<Bundle> exportingBundles = new LinkedHashSet<Bundle>();
-
-                titleHtml(pw, "Packages", "Here are the packages you entered and by which bundle they are exported.");
-                for (String packageName : packageNames)
-                {
-                    pw.println("<tr>");
-                    pw.print("<td>");
-                    pw.print(packageName);
-                    pw.print("</td>");
-
-                    pw.print("<td colspan='2'>");
-                    ExportedPackage[] exports = pa.getExportedPackages(packageName);
-                    ExportedPackage export = pa.getExportedPackage(packageName);
-                    if (export == null)
-                    {
-                        pw.print("<span style='color: red;'>NOT EXPORTED</span>");
-                    }
-                    else
-                    {
-                        Bundle exportingBundle = export.getExportingBundle();
-                        pw.printf(
-                            "<a href='/system/console/bundles/%s' style='text-decoration: none; color: #555;'>%s (%s)</a>",
-                            exportingBundle.getBundleId(), exportingBundle.getSymbolicName(),
-                            exportingBundle.getBundleId());
-                        if (exports.length > 1)
-                        {
-                            pw.printf(" and %s others.", exports.length - 1);
-                        }
-                        exportingBundles.add(exportingBundle);
-
-                    }
-                    pw.print("</td>");
-
-                    pw.println("</tr>");
-                }
-
-                titleHtml(pw, "Maven Dependencies", "Here are the bundles listed above as Maven dependencies");
-                pw.println("<tr>");
-                pw.print("<td colspan='3'>");
-                pw.println("<pre>");
-                for (Bundle bundle : exportingBundles)
-                {
-                    Enumeration<?> entries = bundle.findEntries("META-INF/maven", "pom.properties", true);
-                    if (entries != null)
-                    {
-                        URL u = (URL) entries.nextElement();
-                        java.util.Properties props = new java.util.Properties();
-                        InputStream is = null;
-                        try
-                        {
-                            is = u.openStream();
-                            props.load(u.openStream());
-
-                            indent(pw, 2, "<dependency>");
-                            indent(pw, 3, String.format("<groupId>%s</groupId>", props.get("groupId")));
-                            indent(pw, 3, String.format("<artifactId>%s</artifactId>", props.get("artifactId")));
-                            indent(pw, 3, String.format("<version>%s</version>", props.get("version")));
-                            indent(pw, 3, "<scope>provided</scope>");
-                            indent(pw, 2, "</dependency>");
-                        }
-                        catch (IOException e)
-                        {
-                        }
-                        finally
-                        {
-                            if (is != null)
-                            {
-                                is.close();
-                            }
-                        }
-                    }
-                }
-                pw.print("</pre>");
-                pw.print("</td>");
-                pw.println("</tr>");
-            }
-        }
-        drawForm(pw, findField);
-        endTable(pw);
-    }
-
-    private void indent(PrintWriter pw, int count, String string)
-    {
-        for (int i = 0; i < count; i++)
-        {
-            pw.print(INDENT);
-        }
-        pw.println(string.replace("<", "&lt;").replace(">", "&gt;"));
-    }
-
-    static SortedSet<String> getPackageNames(String findField)
-    {
-        String[] parts = findField.split("\\s");
-        SortedSet<String> result = new TreeSet<String>();
-        for (String part : parts)
-        {
-            part = part.trim();
-            if (part.length() > 0)
-            {
-                int idx = part.lastIndexOf('.');
-                if (idx != -1)
-                {
-                    char firstCharAfterLastDot = part.charAt(idx + 1);
-                    if (Character.isUpperCase(firstCharAfterLastDot))
-                    {
-                        result.add(part.substring(0, idx));
-                    }
-                    else
-                    {
-                        result.add(part);
-                    }
-                }
-            }
-        }
-        return result;
-    }
-
-}
diff --git a/webconsole-plugins/packageadmin/src/main/java/org/apache/felix/webconsole/plugins/packageadmin/internal/ExportedPackageComparator.java b/webconsole-plugins/packageadmin/src/main/java/org/apache/felix/webconsole/plugins/packageadmin/internal/ExportedPackageComparator.java
new file mode 100644
index 0000000..1d63fc3
--- /dev/null
+++ b/webconsole-plugins/packageadmin/src/main/java/org/apache/felix/webconsole/plugins/packageadmin/internal/ExportedPackageComparator.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.Comparator;

+

+import org.osgi.service.packageadmin.ExportedPackage;

+

+final class ExportedPackageComparator implements Comparator/*<ExportedPackage>*/

+{

+

+    public int compare(Object _o1, Object _o2)

+    {

+        ExportedPackage o1 = (ExportedPackage) _o1;

+        ExportedPackage o2 = (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;

+        }

+

+        final long o1bid = o1.getExportingBundle().getBundleId();

+        final long o2bid = o2.getExportingBundle().getBundleId();

+        return (o1bid < o2bid ? -1 : (o1bid == o2bid ? 0 : 1));

+    }

+

+}
\ No newline at end of file
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
deleted file mode 100644
index df886f6..0000000
--- a/webconsole-plugins/packageadmin/src/main/java/org/apache/felix/webconsole/plugins/packageadmin/internal/PackageAdminPlugin.java
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
- * 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.*;
-import java.util.Map.Entry;
-
-import javax.servlet.*;
-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;
-
-@SuppressWarnings("serial")
-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 void dumpDuplicatesAsTxt(final PrintWriter pw, final Map<String, Set<ExportedPackage>> exports)
-    {
-        pw.println("Duplicate Exported Packages");
-        pw.println("---------------------------");
-        final List<String[]> lines = new ArrayList<String[]>();
-        lines.add(new String[]
-            { "Package", "Exports", "Imports" });
-
-        for (Entry<String, Set<ExportedPackage>> exportEntry : exports.entrySet())
-        {
-            final Set<ExportedPackage> exportSet = exportEntry.getValue();
-            if (exportSet.size() > 1)
-            {
-                String firstCol = exportEntry.getKey();
-                for (ExportedPackage exportedPackage : exportSet)
-                {
-                    final Bundle[] importers = exportedPackage.getImportingBundles();
-                    final String secondCol = "version=" + exportedPackage.getVersion() + ", Bundle "
-                        + exportedPackage.getExportingBundle();
-                    if (importers != null && importers.length > 0)
-                    {
-                        boolean first = true;
-                        for (Bundle bundle : importers)
-                        {
-                            if (first)
-                            {
-                                lines.add(new String[]
-                                    { firstCol, secondCol, bundle.toString() });
-                                first = false;
-                            }
-                            else
-                            {
-                                lines.add(new String[]
-                                    { "", "", bundle.toString() });
-
-                            }
-                        }
-                    }
-                    else
-                    {
-                        lines.add(new String[]
-                            { firstCol, secondCol, "" });
-                    }
-                    firstCol = "";
-                }
-            }
-        }
-        int maxFirst = 0, maxSecond = 0;
-        for (final String[] entry : lines)
-        {
-            if (entry[0].length() > maxFirst)
-            {
-                maxFirst = entry[0].length();
-            }
-            if (entry[1].length() > maxSecond)
-            {
-                maxSecond = entry[1].length();
-            }
-        }
-        maxFirst += 2;
-        maxSecond += 2;
-        for (final String[] entry : lines)
-        {
-            padText(pw, entry[0], maxFirst);
-            padText(pw, entry[1], maxSecond);
-            pw.println(entry[2]);
-        }
-    }
-
-    private void padText(final PrintWriter pw, final String text, final int length)
-    {
-        pw.print(text);
-        final int padLength = length - text.length();
-        for (int i = 0; i < padLength; i++)
-        {
-            pw.print(' ');
-        }
-    }
-
-    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());
-        }
-
-    }
-
-    /**
-     * Configuration printer
-     */
-    public void printConfiguration(final PrintWriter pw)
-    {
-        final PackageAdmin pa = (PackageAdmin) this.pkgAdminTracker.getService();
-        if (pa == null)
-        {
-            pw.println("PackageAdmin Service not registered");
-            return;
-        }
-
-        try
-        {
-            Map<String, Set<ExportedPackage>> exports = collectExportedPackages(pa);
-
-            pw.print("PackageAdmin service reports ");
-            pw.print(String.valueOf(exports.size()));
-            pw.println(" exported packages.");
-            pw.println();
-
-            dumpDuplicatesAsTxt(pw, exports);
-        }
-        catch (Exception e)
-        {
-            pw.println("failure ...." + e);
-        }
-    }
-}
diff --git a/webconsole-plugins/packageadmin/src/main/java/org/apache/felix/webconsole/plugins/packageadmin/internal/WebConsolePlugin.java b/webconsole-plugins/packageadmin/src/main/java/org/apache/felix/webconsole/plugins/packageadmin/internal/WebConsolePlugin.java
new file mode 100644
index 0000000..f85132c
--- /dev/null
+++ b/webconsole-plugins/packageadmin/src/main/java/org/apache/felix/webconsole/plugins/packageadmin/internal/WebConsolePlugin.java
@@ -0,0 +1,300 @@
+/*

+ * 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.InputStream;

+import java.net.URL;

+import java.util.Comparator;

+import java.util.Enumeration;

+import java.util.Iterator;

+import java.util.LinkedHashSet;

+import java.util.Map;

+import java.util.Map.Entry;

+import java.util.Set;

+import java.util.SortedSet;

+import java.util.StringTokenizer;

+import java.util.TreeMap;

+import java.util.TreeSet;

+

+import javax.servlet.ServletException;

+import javax.servlet.http.HttpServlet;

+import javax.servlet.http.HttpServletRequest;

+import javax.servlet.http.HttpServletResponse;

+

+import org.apache.commons.io.IOUtils;

+import org.apache.felix.webconsole.SimpleWebConsolePlugin;

+import org.apache.felix.webconsole.WebConsoleUtil;

+import org.json.JSONArray;

+import org.json.JSONException;

+import org.json.JSONObject;

+import org.osgi.framework.Bundle;

+import org.osgi.framework.BundleContext;

+import org.osgi.service.packageadmin.ExportedPackage;

+import org.osgi.service.packageadmin.PackageAdmin;

+

+/**

+ * Provides a Web bases interface to the Packages Admin service, allowing

+ * the user to find package / maven information and identify duplicate exports.

+ */

+class WebConsolePlugin extends SimpleWebConsolePlugin

+{

+

+    private static final String LABEL = "depfinder"; //$NON-NLS-1$

+    private static final String TITLE = "%pluginTitle"; //$NON-NLS-1$

+    private static final String CSS[] = { "/" + LABEL + "/res/plugin.css" }; //$NON-NLS-1$ //$NON-NLS-2$

+

+    private static final Comparator/*<ExportedPackage>*/EXPORT_PACKAGE_COMPARATOR = new ExportedPackageComparator();

+

+    private final PackageAdmin pa;

+    private final BundleContext bc;

+

+    // templates

+    private final String TEMPLATE;

+

+    WebConsolePlugin(BundleContext bc, Object pa)

+    {

+        super(LABEL, TITLE, CSS);

+

+        this.pa = (PackageAdmin) pa;

+        this.bc = bc;

+

+        // load templates

+        TEMPLATE = readTemplateFile("/res/plugin.html"); //$NON-NLS-1$

+    }

+

+    /**

+     * @see org.apache.felix.webconsole.AbstractWebConsolePlugin#renderContent(HttpServletRequest, HttpServletResponse)

+     */

+    protected final void renderContent(HttpServletRequest req,

+        HttpServletResponse response) throws ServletException, IOException

+    {

+        response.getWriter().print(TEMPLATE);

+    }

+

+    /**

+     * @see HttpServlet#doPost(HttpServletRequest, HttpServletResponse)

+     */

+    protected final void doPost(HttpServletRequest req, HttpServletResponse resp)

+        throws ServletException, IOException

+    {

+        final Object json;

+

+        try

+        {

+            String action = req.getParameter("action"); //$NON-NLS-1$

+            if ("deps".equals(action)) { //$NON-NLS-1$

+                json = doFindDependencies(req, pa);

+            }

+            else if ("dups".equals(action)) { //$NON-NLS-1$

+                Map/*<String, Set<ExportedPackage>>*/packages = collectExportedPackages(

+                    pa, bc);

+                json = doFindDuplicates(packages);

+            }

+            else

+            {

+                throw new ServletException("Invalid action: " + action);

+            }

+        }

+        catch (JSONException e)

+        {

+            throw new ServletException(e);

+        }

+

+        WebConsoleUtil.setNoCache(resp);

+        resp.setContentType("application/json; utf-8"); //$NON-NLS-1$

+        resp.getWriter().println(json);

+    }

+

+    static final Map/*<String, Set<ExportedPackage>>*/collectExportedPackages(

+        final PackageAdmin pa, final BundleContext bundleContext)

+    {

+        Map/*<String, Set<ExportedPackage>>*/exports = new TreeMap/*<String, Set<ExportedPackage>>*/();

+

+        Bundle[] bundles = bundleContext.getBundles();

+        for (int i = 0; bundles != null && i < bundles.length; i++)

+        {

+            final Bundle bundle = bundles[i];

+            final ExportedPackage[] bundleExports = pa.getExportedPackages(bundle);

+            for (int j = 0; bundleExports != null && j < bundleExports.length; j++)

+            {

+                final ExportedPackage exportedPackage = bundleExports[j];

+                Set/*<ExportedPackage>*/exportSet = (Set) 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 static final JSONObject doFindDependencies(HttpServletRequest req,

+        PackageAdmin pa) throws JSONException

+    {

+        final JSONObject json = new JSONObject();

+

+        final String findField = req.getParameter("plugin.find"); //$NON-NLS-1$

+        if (findField != null)

+        {

+            Set/*<String>*/packageNames = getPackageNames(findField);

+            Set/*<Bundle>*/exportingBundles = new LinkedHashSet/*<Bundle>*/();

+

+            for (Iterator/*<String>*/i = packageNames.iterator(); i.hasNext();)

+            {

+                String name = (String) i.next();

+                json.append("packages", getPackageInfo(name, pa, exportingBundles)); //$NON-NLS-1$

+            }

+

+            final JSONObject mavenJson = new JSONObject();

+            json.put("maven", mavenJson); //$NON-NLS-1$

+            for (Iterator/*<Bundle>*/i = exportingBundles.iterator(); i.hasNext();)

+            {

+                Bundle bundle = (Bundle) i.next();

+                mavenJson.putOpt(//

+                    String.valueOf(bundle.getBundleId()), //

+                    getMavenInfo(bundle));

+            }

+        }

+

+        return json;

+

+    }

+

+    private static final JSONArray doFindDuplicates(

+        final Map/*<String, Set<ExportedPackage>>*/exports) throws JSONException

+    {

+        final JSONArray ret = new JSONArray();

+        for (Iterator entryIter = exports.entrySet().iterator(); entryIter.hasNext();)

+        {

+            Entry/*<String, Set<ExportedPackage>>*/exportEntry = (Entry) entryIter.next();

+            Set/*<ExportedPackage>*/exportSet = (Set) exportEntry.getValue();

+            if (exportSet.size() > 1)

+            {

+                final JSONObject container = new JSONObject();

+                ret.put(container);

+                for (Iterator packageIter = exportSet.iterator(); packageIter.hasNext();)

+                {

+                    ExportedPackage exportedPackage = (ExportedPackage) packageIter.next();

+                    final JSONObject json = toJSON(exportedPackage);

+                    Bundle[] importers = exportedPackage.getImportingBundles();

+                    for (int j = 0; importers != null && j < importers.length; j++)

+                    {

+                        json.append("importers", toJSON(importers[j], new JSONObject())); //$NON-NLS-1$

+                    }

+                    container//

+                    .put("name", exportedPackage.getName()) //$NON-NLS-1$

+                    .append("entries", json); //$NON-NLS-1$

+                }

+            }

+        }

+        return ret;

+    }

+

+    private static final JSONObject toJSON(Bundle bundle, JSONObject json)

+        throws JSONException

+    {

+        json.put("bid", bundle.getBundleId()); //$NON-NLS-1$

+        json.putOpt("bsn", bundle.getSymbolicName()); //$NON-NLS-1$

+        return json;

+    }

+

+    private static final JSONObject toJSON(final ExportedPackage pkg)

+        throws JSONException

+    {

+        final JSONObject ret = new JSONObject();

+        ret.put("version", pkg.getVersion()); //$NON-NLS-1$

+        return toJSON(pkg.getExportingBundle(), ret);

+    }

+

+    private static final JSONObject getPackageInfo(String packageName, PackageAdmin pa,

+        Set/*<Bundle>*/exportingBundles) throws JSONException

+    {

+        final JSONObject ret = new JSONObject();

+        final ExportedPackage[] exports = pa.getExportedPackages(packageName);

+        for (int i = 0; exports != null && i < exports.length; i++)

+        {

+            final ExportedPackage x = exports[i];

+            ret.append("exporters", toJSON(x)); //$NON-NLS-1$

+            exportingBundles.add(x.getExportingBundle());

+        }

+        return ret.put("name", packageName); //$NON-NLS-1$

+    }

+

+    private static final JSONObject getMavenInfo(Bundle bundle)

+    {

+        JSONObject ret = null;

+

+        Enumeration entries = bundle.findEntries("META-INF/maven", "pom.properties", true); //$NON-NLS-1$ //$NON-NLS-2$

+        if (entries != null)

+        {

+            URL u = (URL) entries.nextElement();

+            java.util.Properties props = new java.util.Properties();

+            InputStream is = null;

+            try

+            {

+                is = u.openStream();

+                props.load(u.openStream());

+

+                ret = new JSONObject(props);

+            }

+            catch (IOException e)

+            {

+                // ignore

+            }

+            finally

+            {

+                IOUtils.closeQuietly(is);

+            }

+        }

+        return ret;

+    }

+

+    static final Set/*<String>*/getPackageNames(String findField)

+    {

+        StringTokenizer tok = new StringTokenizer(findField, " \t\n\f\r"); //$NON-NLS-1$

+        SortedSet/*<String>*/result = new TreeSet/*<String>*/();

+        while (tok.hasMoreTokens())

+        {

+            final String part = tok.nextToken().trim();

+            if (part.length() > 0)

+            {

+                int idx = part.lastIndexOf('.');

+                if (idx != -1)

+                {

+                    char firstCharAfterLastDot = part.charAt(idx + 1);

+                    if (Character.isUpperCase(firstCharAfterLastDot))

+                    {

+                        result.add(part.substring(0, idx));

+                    }

+                    else

+                    {

+                        result.add(part);

+                    }

+                }

+            }

+        }

+        return result;

+    }

+

+}

diff --git a/webconsole-plugins/packageadmin/src/main/java/org/apache/felix/webconsole/plugins/packageadmin/internal/WebConsolePrinter.java b/webconsole-plugins/packageadmin/src/main/java/org/apache/felix/webconsole/plugins/packageadmin/internal/WebConsolePrinter.java
new file mode 100644
index 0000000..aba1264
--- /dev/null
+++ b/webconsole-plugins/packageadmin/src/main/java/org/apache/felix/webconsole/plugins/packageadmin/internal/WebConsolePrinter.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.PrintWriter;

+import java.util.ArrayList;

+import java.util.Iterator;

+import java.util.List;

+import java.util.Map;

+import java.util.Set;

+import java.util.Map.Entry;

+

+import org.apache.felix.webconsole.ConfigurationPrinter;

+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;

+

+class WebConsolePrinter implements ConfigurationPrinter

+{

+

+    private final ServiceTracker tracker;

+    private final BundleContext bc;

+

+    WebConsolePrinter(BundleContext bc, ServiceTracker tracker)

+    {

+        this.bc = bc;

+        this.tracker = tracker;

+    }

+

+    /**

+     * @see org.apache.felix.webconsole.ConfigurationPrinter#printConfiguration(java.io.PrintWriter)

+     */

+    public void printConfiguration(PrintWriter pw)

+    {

+        final PackageAdmin pa = (PackageAdmin) tracker.getService();

+        if (pa == null)

+        {

+            pw.println("Status: PackageAdmin Service not registered");

+            return;

+        }

+

+        try

+        {

+            Map/*<String, Set<ExportedPackage>>*/exports = WebConsolePlugin.collectExportedPackages(

+                pa, bc);

+

+            pw.print("Status: PackageAdmin service reports ");

+            pw.print(String.valueOf(exports.size()));

+            pw.println(" exported packages.");

+            pw.println();

+

+            dumpDuplicatesAsTxt(pw, exports);

+        }

+        catch (Exception e)

+        {

+            pw.println("failure ...." + e);

+        }

+    }

+

+    private void dumpDuplicatesAsTxt(final PrintWriter pw,

+        final Map/*<String, Set<ExportedPackage>>*/exports)

+    {

+        pw.println("Duplicate Exported Packages");

+        pw.println("---------------------------");

+        final List/*<String[]>*/lines = new ArrayList/*<String[]>*/();

+        lines.add(new String[] { "Package", "Exports", "Imports" });

+

+        for (Iterator/*<Entry<String, Set<ExportedPackage>>>*/entriesIter = exports.entrySet().iterator(); entriesIter.hasNext();)

+        {

+            Entry/*<String, Set<ExportedPackage>>*/exportEntry = (Entry) entriesIter.next();

+

+            final Set/*<ExportedPackage>*/exportSet = (Set) exportEntry.getValue();

+            if (exportSet.size() > 1)

+            {

+                String firstCol = (String) exportEntry.getKey();

+                for (Iterator packageIter = exportSet.iterator(); packageIter.hasNext();)

+                {

+                    ExportedPackage exportedPackage = (ExportedPackage) packageIter.next();

+                    final Bundle[] importers = exportedPackage.getImportingBundles();

+                    final String secondCol = "version=" + exportedPackage.getVersion()

+                        + ", Bundle " + exportedPackage.getExportingBundle();

+                    if (importers != null && importers.length > 0)

+                    {

+                        boolean first = true;

+                        for (int j = 0; j < importers.length; j++)

+                        {

+                            final Bundle bundle = importers[j];

+                            if (first)

+                            {

+                                lines.add(new String[] { firstCol, secondCol,

+                                        bundle.toString() });

+                                first = false;

+                            }

+                            else

+                            {

+                                lines.add(new String[] { "", "", bundle.toString() });

+

+                            }

+                        }

+                    }

+                    else

+                    {

+                        lines.add(new String[] { firstCol, secondCol, "" });

+                    }

+                    firstCol = "";

+                }

+            }

+        }

+        int maxFirst = 0, maxSecond = 0;

+        for (int i = 0; i < lines.size(); i++)

+        {

+            final String[] entry = (String[]) lines.get(i);

+            if (entry[0].length() > maxFirst)

+            {

+                maxFirst = entry[0].length();

+            }

+            if (entry[1].length() > maxSecond)

+            {

+                maxSecond = entry[1].length();

+            }

+        }

+        maxFirst += 2;

+        maxSecond += 2;

+        for (int i = 0; i < lines.size(); i++)

+        {

+            final String[] entry = (String[]) lines.get(i);

+            padText(pw, entry[0], maxFirst);

+            padText(pw, entry[1], maxSecond);

+            pw.println(entry[2]);

+        }

+    }

+

+    private static final void padText(final PrintWriter pw, final String text,

+        final int length)

+    {

+        pw.print(text);

+        final int padLength = length - text.length();

+        for (int i = 0; i < padLength; i++)

+        {

+            pw.print(' ');

+        }

+    }

+

+    /**

+     * @see org.apache.felix.webconsole.ConfigurationPrinter#getTitle()

+     */

+    public String getTitle()

+    {

+        return "Duplicate Exports";

+    }

+

+}

diff --git a/webconsole-plugins/packageadmin/src/main/resources/OSGI-INF/l10n/bundle.properties b/webconsole-plugins/packageadmin/src/main/resources/OSGI-INF/l10n/bundle.properties
new file mode 100644
index 0000000..91c4163
--- /dev/null
+++ b/webconsole-plugins/packageadmin/src/main/resources/OSGI-INF/l10n/bundle.properties
@@ -0,0 +1,46 @@
+#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.
+
+#
+# Web Console strings for reference all strings here are commented.
+# This file may be used to produce a translation of the strings
+#
+# Note that properties files are ISO-8859-1 encoded. To provide translations
+# for languages requiring different character encodings, you may use the
+# native2ascii Maven Plugin from http://mojo.codehaus.org/native2ascii-maven-plugin/
+# to translate the natively encoded files to ISO-8859-1 during bundle build
+#
+# Translations requiring non-ISO-8859-1 encoding are placed in the
+# src/main/native2ascii/OSGI-INF/l10n folder and are converted using said
+# plugin while building the bundle
+#
+pluginTitle=Packages
+find.label=Packages/Classes: 
+find.btn=Find
+find.tip=Enter a list of package or class names
+
+header.package.name=Package
+header.package.ver=Version
+header.exporting.bundle=Exported by
+header.maven.deps=Maven Dependency
+no.exporters.found=No exporting bundle(s) found
+no.maven.found=No maven information available
+status.initial=Please enter a list of package or class names and click the find button
+status.find={0} result(s) found
+
+# duplicates
+find.dups=Find Duplicate Exports
+status.dups={0} duplicate package(s) found
+header.importing.bundle=Used by bundle(s)
diff --git a/webconsole-plugins/packageadmin/src/main/resources/OSGI-INF/l10n/bundle_bg.properties b/webconsole-plugins/packageadmin/src/main/resources/OSGI-INF/l10n/bundle_bg.properties
new file mode 100644
index 0000000..8b96e6a
--- /dev/null
+++ b/webconsole-plugins/packageadmin/src/main/resources/OSGI-INF/l10n/bundle_bg.properties
@@ -0,0 +1,46 @@
+#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.

+

+#

+# Web Console strings for reference all strings here are commented.

+# This file may be used to produce a translation of the strings

+#

+# Note that properties files are ISO-8859-1 encoded. To provide translations

+# for languages requiring different character encodings, you may use the

+# native2ascii Maven Plugin from http://mojo.codehaus.org/native2ascii-maven-plugin/

+# to translate the natively encoded files to ISO-8859-1 during bundle build

+#

+# Translations requiring non-ISO-8859-1 encoding are placed in the

+# src/main/native2ascii/OSGI-INF/l10n folder and are converted using said

+# plugin while building the bundle

+#

+pluginTitle=Java пакети

+find.label=Пакети/класове: 

+find.btn=Търсене

+find.tip=Въведете списък с пакети или имена на класове

+

+header.package.name=Пакет

+header.package.ver=Версия

+header.exporting.bundle=Предостъпен от

+header.maven.deps=Maven информация

+no.exporters.found=Няма бъндъли предотставящи този пакет

+no.maven.found=Няма достъпна Maven информация

+status.initial=Моля въведете списък с пакети или имена на класове и натиснете бутона "Търсене"

+status.find=Открити са {0} резултата

+

+# duplicates

+find.dups=Търсене на дублирани пакети

+status.dups=Открити са {0} дуплирани пакета

+header.importing.bundle=Използван от бъндъл(и)

diff --git a/webconsole-plugins/packageadmin/src/main/resources/res/plugin.css b/webconsole-plugins/packageadmin/src/main/resources/res/plugin.css
new file mode 100644
index 0000000..474a443
--- /dev/null
+++ b/webconsole-plugins/packageadmin/src/main/resources/res/plugin.css
@@ -0,0 +1,19 @@
+/*

+ * 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.

+ */

+

+td.mvn { white-space: pre-wrap }

+#findField { width: 50% }

diff --git a/webconsole-plugins/packageadmin/src/main/resources/res/plugin.html b/webconsole-plugins/packageadmin/src/main/resources/res/plugin.html
new file mode 100644
index 0000000..d4fbae3
--- /dev/null
+++ b/webconsole-plugins/packageadmin/src/main/resources/res/plugin.html
@@ -0,0 +1,63 @@
+<script type="text/javascript" src="${pluginRoot}/res/plugin.js"></script>
+<script type="text/javascript">
+// <![CDATA[
+var i18n = {
+	statusFind : '${status.find}',
+	statusDups : '${status.dups}'
+}
+// ]]>
+</script>
+
+<!-- status line -->
+<p class="statline">${status.initial}</p>
+
+<!-- table caption -->
+<form method="post" action="${pluginRoot}">
+	<div class="ui-widget-header ui-corner-top buttonGroup">
+		<span>${find.label}</span>
+		<input type="text" id="findField" title="${find.tip}" />
+		<button id="findButton">${find.btn}</button>
+		<button id="findDups">${find.dups}</button>
+	</div>
+</form>
+
+<!-- table find results -->
+<table class="tablesorter nicetable noauto" id="findTable">
+	<thead>
+		<tr>
+			<th>${header.package.name}</th>
+			<th>${header.package.ver}</th>
+			<th>${header.exporting.bundle}</th>
+			<th>${header.maven.deps}</th>
+		</tr>
+	</thead>
+	<tbody>
+		<tr>
+			<td class="pkg">&nbsp;</td>
+			<td class="ver">-</td>
+			<td class="bnd">${no.exporters.found}</td>
+			<td class="mvn">${no.maven.found}</td>
+		</tr>
+	</tbody>
+</table>
+
+<!-- duplicates -->
+<table class="nicetable noauto ui-helper-hidden" id="dupsTable">
+	<thead>
+		<tr>
+			<th>${header.package.name}</th>
+			<th>${header.package.ver}</th>
+			<th>${header.exporting.bundle}</th>
+			<th>${header.importing.bundle}</th>
+		</tr>
+	</thead>
+	<tbody>
+		<tr>
+			<td class="pkg">-</td>
+			<td class="ver">-</td>
+			<td class="exp">-</td>
+			<td class="imp">&nbsp;</td>
+		</tr>
+	</tbody>
+</table>
+
diff --git a/webconsole-plugins/packageadmin/src/main/resources/res/plugin.js b/webconsole-plugins/packageadmin/src/main/resources/res/plugin.js
new file mode 100644
index 0000000..8468103
--- /dev/null
+++ b/webconsole-plugins/packageadmin/src/main/resources/res/plugin.js
@@ -0,0 +1,120 @@
+/*

+ * 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.

+ */

+

+var findField = false;

+var findButton = false;

+var findTable = false;

+var findTableBody = false;

+var findTableTemplate = false;

+var dupsTable = false;

+var dupsTableBody = false;

+var dupsTableTemplate = false;

+var statline = false;

+

+function linkBundle(bnd) { return '<a href="{0}/bundles/{1}">{2} ({3})</a>'.msgFormat(appRoot, bnd.bid, bnd.bsn, bnd.bid) }

+

+$(function() {

+	findField = $('#findField');

+	statline = $('.statline');

+

+	findTable = $('#findTable').tablesorter({

+		headers:{

+			1:{sorter: false},

+			3:{sorter: false}

+		},

+		textExtraction:mixedLinksExtraction

+	});

+	findTableBody = findTable.find('tbody');

+	findTableTemplate = findTableBody.find('tr').clone();

+	findTableBody.empty();

+

+	dupsTable = $('#dupsTable');

+	dupsTableBody = dupsTable.find('tbody');

+	dupsTableTemplate = dupsTableBody.find('tr').clone();

+

+	$('#findButton').click(function() {

+		if(!findField.val()) {

+			findField.addClass('ui-state-error');

+		} else {

+			findField.removeClass('ui-state-error');

+

+			$.post(pluginRoot, { 'action': 'deps', 'plugin.find' : findField.val() }, function(response) {

+				dupsTable.addClass('ui-helper-hidden')

+				findTable.removeClass('ui-helper-hidden');

+				findTableBody.empty();

+				if (response.packages) for(var i in response.packages) {

+					var pkg = response.packages[i];

+					if (pkg.exporters) for(var i in pkg.exporters) {

+						var exp = pkg.exporters[i];

+						var tr = findTableTemplate.clone()

+							.find('td.pkg').text(pkg.name).end()

+							.find('td.ver').text(exp.version).end()

+							.find('td.bnd').html(linkBundle(exp)).end()

+							.appendTo(findTableBody);

+						if (response.maven && response.maven[exp.bid]) {

+							var mvn = response.maven[exp.bid];

+							mvn['scope'] = 'provided';

+							var txt = ''; for (var p in mvn) {

+								txt += '\t<' + p + '>' + mvn[p] + '</' + p + '>\n';

+							}

+							tr.find('td.mvn').text('<dependency>\n' + txt + '</dependency>');

+						}

+					} else {

+						var tr = findTableTemplate.clone()

+							.find('td.pkg').text(pkg.name).end()

+							.appendTo(findTableBody);

+					}

+				}

+				statline.text(i18n.statusFind.msgFormat(response.packages ? response.packages.length : 0));

+				findTable.trigger('update').trigger('applyWidgets')

+			}, 'json');

+		}

+		return false;

+	});

+

+	$('#findDups').click(function() {

+		$.post(pluginRoot, { 'action': 'dups' }, function(response) {

+			findTable.addClass('ui-helper-hidden');

+			dupsTable.removeClass('ui-helper-hidden')

+			dupsTableBody.empty();

+			if (response) for(var i in response) {

+				var pkg = response[i];

+				if (pkg.entries) for (var i in pkg.entries) {

+					var exp = pkg.entries[i];

+					var td = dupsTableTemplate.clone()

+						.find('td.pkg').text(pkg.name).end()

+						.find('td.ver').text(exp.version).end()

+						.find('td.exp').html(linkBundle(exp)).end();

+					if (exp.importers) {

+						var txt = ''; for(var j in exp.importers) txt += linkBundle(exp.importers[j]) + '<br/>';

+						td.find('td.imp').html( txt );

+					}

+					if (i==0) {

+						td.find('td.pkg').attr('rowspan', pkg.entries.length);

+					} else {

+						td.find('td.pkg').remove();

+					}

+					td.appendTo(dupsTableBody);

+				}

+			}

+			statline.text(i18n.statusDups.msgFormat(response ? response.length : 0));

+		}, 'json');

+

+		return false;

+	});

+

+})

diff --git a/webconsole-plugins/packageadmin/src/test/java/org/apache/felix/webconsole/plugins/packageadmin/internal/DependencyFinderPluginTest.java b/webconsole-plugins/packageadmin/src/test/java/org/apache/felix/webconsole/plugins/packageadmin/internal/DependencyFinderPluginTest.java
deleted file mode 100644
index 56531fa..0000000
--- a/webconsole-plugins/packageadmin/src/test/java/org/apache/felix/webconsole/plugins/packageadmin/internal/DependencyFinderPluginTest.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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.Iterator;
-import java.util.SortedSet;
-
-import static org.junit.Assert.*;
-import org.junit.Test;
-
-public class DependencyFinderPluginTest
-{
-
-    @Test
-    public void single()
-    {
-        SortedSet<String> r = DependencyFinderPlugin.getPackageNames("com.foo.bar");
-        assertEquals(1, r.size());
-        assertEquals("com.foo.bar", r.first());
-    }
-
-    @Test
-    public void singleClass()
-    {
-        SortedSet<String> r = DependencyFinderPlugin.getPackageNames("com.foo.bar.MyClass");
-        assertEquals(1, r.size());
-        assertEquals("com.foo.bar", r.first());
-    }
-
-    @Test
-    public void multiple()
-    {
-        SortedSet<String> r = DependencyFinderPlugin
-            .getPackageNames("com.foo.bar\ncom.foo.bar2\ncom.foo.bar3.MyClass\ncom.foo.bar.MyClass");
-        assertEquals(3, r.size());
-        Iterator<String> it = r.iterator();
-        assertEquals("com.foo.bar", it.next());
-        assertEquals("com.foo.bar2", it.next());
-        assertEquals("com.foo.bar3", it.next());
-    }
-
-}
\ No newline at end of file