FELIX-574 Start replacing Action and Render interfaces by Servlet interface:
   * Add new AbstractWebConsolePlugin which implementations may extend from
   * Add RenderBridge to register old-style Render as Servlet
FELIX-566 Convert Bundle handling to REST-style

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@662438 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/webconsole/pom.xml b/webconsole/pom.xml
index 1dc1e1b..f20dff6 100644
--- a/webconsole/pom.xml
+++ b/webconsole/pom.xml
@@ -1,20 +1,20 @@
 <!--
- 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
-
+    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.
+    
+    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">
 
@@ -36,8 +36,12 @@
     </description>
 
     <scm>
-        <connection>scm:svn:http://svn.apache.org/repos/asf/felix/trunk/webconsole</connection>
-        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/felix/trunk/webconsole</developerConnection>
+        <connection>
+            scm:svn:http://svn.apache.org/repos/asf/felix/trunk/webconsole
+        </connection>
+        <developerConnection>
+            scm:svn:https://svn.apache.org/repos/asf/felix/trunk/webconsole
+        </developerConnection>
         <url>http://svn.apache.org/viewvc/felix/trunk/webconsole</url>
     </scm>
 
@@ -66,12 +70,18 @@
                         <Bundle-SymbolicName>
                             ${artifactId}
                         </Bundle-SymbolicName>
-                        <Bundle-Vendor>The Apache Software Foundation</Bundle-Vendor>
+                        <Bundle-Vendor>
+                            The Apache Software Foundation
+                        </Bundle-Vendor>
+                        <Bundle-DocURL>
+                            http://felix.apache.org
+                        </Bundle-DocURL>
                         <Bundle-Activator>
                             org.apache.felix.webconsole.internal.OsgiManagerActivator
                         </Bundle-Activator>
                         <Export-Package>
                             org.apache.felix.webconsole,
+                            org.osgi.service.obr
                         </Export-Package>
                         <Private-Package>
                             !org.apache.felix.webconsole,
@@ -85,18 +95,21 @@
                             <!-- Required by FileUpload and Util -->
                             org.apache.commons.io,
                             org.apache.commons.io.filefilter,
-                            org.apache.commons.io.output,
-
-                            <!-- Required for JSON data transfer -->
-                            org.json,
-
-                            <!-- Import/Export-Package parsing -->
-                            org.apache.felix.bundlerepository
+                            org.apache.commons.io.output
                         </Private-Package>
                         <Import-Package>
-                            org.apache.felix.*;
-                            org.osgi.service.obr;resolution:=optional,*
+                            org.apache.felix.scr;
+                            org.apache.felix.shell;
+                            org.osgi.service.*;resolution:=optional,*
                         </Import-Package>
+                        
+                        <Embed-Dependency>
+                            <!-- Import/Export-Package parsing, OBR -->
+                            org.apache.felix.bundlerepository,
+                            
+                            <!-- Required for JSON data transfer -->
+                            json
+                        </Embed-Dependency>
                     </instructions>
                 </configuration>
             </plugin>
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/AbstractWebConsolePlugin.java b/webconsole/src/main/java/org/apache/felix/webconsole/AbstractWebConsolePlugin.java
new file mode 100644
index 0000000..972375f
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/AbstractWebConsolePlugin.java
@@ -0,0 +1,309 @@
+/*
+ * 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;
+
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.text.MessageFormat;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.FileUploadException;
+import org.apache.commons.fileupload.disk.DiskFileItemFactory;
+import org.apache.commons.fileupload.servlet.ServletFileUpload;
+import org.apache.commons.fileupload.servlet.ServletRequestContext;
+import org.apache.felix.webconsole.internal.servlet.OsgiManager;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+
+
+public abstract class AbstractWebConsolePlugin extends HttpServlet
+{
+
+    /** Pseudo class version ID to keep the IDE quite. */
+    private static final long serialVersionUID = 1L;
+
+    /** The name of the request attribute containig the map of FileItems from the POST request */
+    public static final String ATTR_FILEUPLOAD = "org.apache.felix.webconsole.fileupload";
+
+    private static final String HEADER = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">"
+        + "<html>"
+        + "<head>"
+        + "<meta http-equiv=\"Content-Type\" content=\"text/html; utf-8\">"
+        + "<link rel=\"icon\" href=\"res/imgs/favicon.ico\">"
+        + "<title>{0} - {12}</title>"
+        + "<script src=\"{15}/res/ui/admin.js\" language=\"JavaScript\"></script>"
+        + "<script language=\"JavaScript\">"
+        + "ABOUT_VERSION=''{1}'';"
+        + "ABOUT_JVERSION=''{2}'';"
+        + "ABOUT_JRT=''{3} (build {2})'';"
+        + "ABOUT_JVM=''{4} (build {5}, {6})'';"
+        + "ABOUT_MEM=\"{7} KB\";"
+        + "ABOUT_USED=\"{8} KB\";"
+        + "ABOUT_FREE=\"{9} KB\";"
+        + "</script>"
+        + "<link href=\"{15}/res/ui/admin.css\" rel=\"stylesheet\" type=\"text/css\">"
+        + "</head>"
+        + "<body>"
+        + "<div id=\"main\">"
+        + "<div id=\"lead\">"
+        + "<h1>"
+        + "{0}<br>{12}"
+        + "</h1>"
+        + "<p>"
+        + "<a target=\"_blank\" href=\"{13}\" title=\"{11}\"><img src=\"{15}/res/imgs/logo.png\" width=\"165\" height=\"63\" border=\"0\"></a>"
+        + "</p>" + "</div>";
+
+    private BundleContext bundleContext;
+
+    private String adminTitle;
+    private String adminVersion;
+    private String productName;
+    private String productWeb;
+    private String vendorName;
+    private String vendorWeb;
+
+
+    //---------- HttpServlet Overwrites ----------------------------------------
+
+    /**
+     * Returns the title for this plugin as returned by {@link #getTitle()}
+     */
+    public String getServletName()
+    {
+        return getTitle();
+    }
+
+
+    /**
+     * Renders the web console page for the request. This consist of the following
+     * four parts called in order:
+     * <ol>
+     * <li>{@link #startResponse(HttpServletRequest, HttpServletResponse)}</li>
+     * <li>{@link #renderTopNavigation(HttpServletRequest, PrintWriter)}</li>
+     * <li>{@link #renderContent(HttpServletRequest, HttpServletResponse)}</li>
+     * <li>{@link #endResponse(PrintWriter)}</li>
+     * </ol>
+     */
+    protected void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException,
+        IOException
+    {
+        PrintWriter pw = startResponse( request, response );
+        renderTopNavigation( request, pw );
+        renderContent( request, response );
+        endResponse( pw );
+    }
+
+
+    //---------- AbstractWebConsolePlugin API ----------------------------------
+
+    public void activate( BundleContext bundleContext )
+    {
+        this.bundleContext = bundleContext;
+
+        Dictionary headers = bundleContext.getBundle().getHeaders();
+
+        adminTitle = ( String ) headers.get( Constants.BUNDLE_NAME ); // "OSGi Management Console";
+        adminVersion = ( String ) headers.get( Constants.BUNDLE_NAME ); // "1.0.0-SNAPSHOT";
+        productName = "Apache Felix";
+        productWeb = ( String ) headers.get( Constants.BUNDLE_DOCURL );
+        vendorName = ( String ) headers.get( Constants.BUNDLE_VENDOR );
+        vendorWeb = "http://www.apache.org";
+    }
+
+
+    public void deactivate()
+    {
+        this.bundleContext = null;
+    }
+
+
+    public abstract String getTitle();
+
+
+    public abstract String getLabel();
+
+
+    protected abstract void renderContent( HttpServletRequest req, HttpServletResponse res ) throws ServletException,
+        IOException;
+
+
+    protected BundleContext getBundleContext()
+    {
+        return bundleContext;
+    }
+
+
+    protected PrintWriter startResponse( HttpServletRequest request, HttpServletResponse response ) throws IOException
+    {
+        response.setCharacterEncoding( "utf-8" );
+        response.setContentType( "text/html" );
+
+        PrintWriter pw = response.getWriter();
+
+        long freeMem = Runtime.getRuntime().freeMemory() / 1024;
+        long totalMem = Runtime.getRuntime().totalMemory() / 1024;
+        long usedMem = totalMem - freeMem;
+
+        String appRoot = request.getContextPath() + request.getServletPath();
+
+        String header = MessageFormat.format( HEADER, new Object[]
+            { adminTitle, adminVersion, System.getProperty( "java.runtime.version" ),
+                System.getProperty( "java.runtime.name" ), System.getProperty( "java.vm.name" ),
+                System.getProperty( "java.vm.version" ), System.getProperty( "java.vm.info" ), new Long( totalMem ),
+                new Long( usedMem ), new Long( freeMem ), vendorWeb, productName, getTitle(), productWeb, vendorName,
+                appRoot } );
+        pw.println( header );
+        return pw;
+    }
+
+
+    protected void renderTopNavigation( HttpServletRequest request, PrintWriter pw )
+    {
+        // assume pathInfo to not be null, else this would not be called
+        String current = request.getPathInfo();
+        int slash = current.indexOf( "/", 1 );
+        if ( slash > 1 )
+        {
+            current = current.substring( 1, slash );
+        }
+
+        boolean disabled = false;
+
+        Map labelMap = ( Map ) request.getAttribute( OsgiManager.ATTR_LABEL_MAP );
+        if ( labelMap != null )
+        {
+            pw.println( "<p id='technav'>" );
+
+            SortedMap map = new TreeMap();
+            for ( Iterator ri = labelMap.entrySet().iterator(); ri.hasNext(); )
+            {
+                Map.Entry labelMapEntry = ( Map.Entry ) ri.next();
+                if ( labelMapEntry.getKey() == null )
+                {
+                    // ignore renders without a label
+                }
+                else if ( disabled || current.equals( labelMapEntry.getKey() ) )
+                {
+                    map.put( labelMapEntry.getValue(), "<span class='technavat'>" + labelMapEntry.getValue()
+                        + "</span>" );
+                }
+                else
+                {
+                    map.put( labelMapEntry.getValue(), "<a href='" + labelMapEntry.getKey() + "'>"
+                        + labelMapEntry.getValue() + "</a></li>" );
+                }
+            }
+
+            for ( Iterator li = map.values().iterator(); li.hasNext(); )
+            {
+                pw.println( li.next() );
+            }
+
+            pw.println( "</p>" );
+        }
+    }
+
+
+    protected void endResponse( PrintWriter pw )
+    {
+        pw.println( "</body>" );
+        pw.println( "</html>" );
+    }
+
+
+    public static String getParameter( HttpServletRequest request, String name )
+    {
+        // just get the parameter if not a multipart/form-data POST
+        if ( !ServletFileUpload.isMultipartContent( new ServletRequestContext( request ) ) )
+        {
+            return request.getParameter( name );
+        }
+
+        // check, whether we alread have the parameters
+        Map params = ( Map ) request.getAttribute( ATTR_FILEUPLOAD );
+        if ( params == null )
+        {
+            // parameters not read yet, read now
+            // Create a factory for disk-based file items
+            DiskFileItemFactory factory = new DiskFileItemFactory();
+            factory.setSizeThreshold( 256000 );
+
+            // Create a new file upload handler
+            ServletFileUpload upload = new ServletFileUpload( factory );
+            upload.setSizeMax( -1 );
+
+            // Parse the request
+            params = new HashMap();
+            try
+            {
+                List items = upload.parseRequest( request );
+                for ( Iterator fiter = items.iterator(); fiter.hasNext(); )
+                {
+                    FileItem fi = ( FileItem ) fiter.next();
+                    FileItem[] current = ( FileItem[] ) params.get( fi.getFieldName() );
+                    if ( current == null )
+                    {
+                        current = new FileItem[]
+                            { fi };
+                    }
+                    else
+                    {
+                        FileItem[] newCurrent = new FileItem[current.length + 1];
+                        System.arraycopy( current, 0, newCurrent, 0, current.length );
+                        newCurrent[current.length] = fi;
+                        current = newCurrent;
+                    }
+                    params.put( fi.getFieldName(), current );
+                }
+            }
+            catch ( FileUploadException fue )
+            {
+                // TODO: log
+            }
+            request.setAttribute( ATTR_FILEUPLOAD, params );
+        }
+
+        FileItem[] param = ( FileItem[] ) params.get( name );
+        if ( param != null )
+        {
+            for ( int i = 0; i < param.length; i++ )
+            {
+                if ( param[i].isFormField() )
+                {
+                    return param[i].getString();
+                }
+            }
+        }
+
+        // no valid string parameter, fail
+        return null;
+    }
+
+}
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleConstants.java b/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleConstants.java
new file mode 100644
index 0000000..2bc1e08
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleConstants.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+
+public interface WebConsoleConstants
+{
+
+    /**
+     * The name of the service to register as to be used as a "plugin" for
+     * the OSGi Manager (value is "javax.servlet.Servlet").
+     */
+    public static final String SERVICE_NAME = "javax.servlet.Servlet";
+
+    /**
+     * The URI address label under which the OSGi Manager plugin is called by
+     * the OSGi Manager (value is "felix.webconsole.label").
+     * <p>
+     * Only {@link #SERVICE_NAME} services with this service registration
+     * property set to a non-empty String values are accepted by the OSGi
+     * Manager as a plugin.
+     */
+    public static final String PLUGIN_LABEL = "felix.webconsole.label";
+
+}
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/BaseManagementPlugin.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/BaseManagementPlugin.java
index 83ac401..d38b03c 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/BaseManagementPlugin.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/BaseManagementPlugin.java
@@ -19,14 +19,13 @@
 package org.apache.felix.webconsole.internal;
 
 
-import org.apache.felix.webconsole.internal.servlet.Logger;
 import org.osgi.framework.BundleContext;
 import org.osgi.service.packageadmin.PackageAdmin;
 import org.osgi.service.startlevel.StartLevel;
 import org.osgi.util.tracker.ServiceTracker;
 
 
-public class BaseManagementPlugin
+public class BaseManagementPlugin implements OsgiManagerPlugin
 {
 
     private BundleContext bundleContext;
@@ -42,15 +41,31 @@
     }
 
 
-    public void setBundleContext( BundleContext bundleContext )
+    public void activate( BundleContext bundleContext )
     {
         this.bundleContext = bundleContext;
+        this.log = new Logger( bundleContext );
     }
 
 
-    public void setLogger( Logger log )
+    public void deactivate()
     {
-        this.log = log;
+        if ( log != null )
+        {
+            log.dispose();
+        }
+
+        if ( startLevelService != null )
+        {
+            startLevelService.close();
+            startLevelService = null;
+        }
+
+        if ( packageAdmin != null )
+        {
+            packageAdmin.close();
+            packageAdmin = null;
+        }
     }
 
 
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/BaseWebConsolePlugin.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/BaseWebConsolePlugin.java
new file mode 100644
index 0000000..5490538
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/BaseWebConsolePlugin.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.webconsole.internal;
+
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.felix.webconsole.AbstractWebConsolePlugin;
+import org.osgi.service.packageadmin.PackageAdmin;
+import org.osgi.service.startlevel.StartLevel;
+import org.osgi.util.tracker.ServiceTracker;
+
+
+public abstract class BaseWebConsolePlugin extends AbstractWebConsolePlugin implements OsgiManagerPlugin
+{
+
+    private static String PACKAGE_ADMIN_NAME = PackageAdmin.class.getName();
+    private static String START_LEVEL_NAME = StartLevel.class.getName();
+
+    private Logger log;
+
+    private Map services = new HashMap();
+
+
+    public void deactivate()
+    {
+        for ( Iterator ti = services.values().iterator(); ti.hasNext(); )
+        {
+            ServiceTracker tracker = ( ServiceTracker ) ti.next();
+            tracker.close();
+            ti.remove();
+        }
+
+        if ( log != null )
+        {
+            log.dispose();
+            log = null;
+        }
+    }
+
+
+    protected Logger getLog()
+    {
+        if ( log == null )
+        {
+            log = new Logger( getBundleContext() );
+        }
+
+        return log;
+    }
+
+
+    protected StartLevel getStartLevel()
+    {
+        return ( StartLevel ) getService( START_LEVEL_NAME );
+    }
+
+
+    protected PackageAdmin getPackageAdmin()
+    {
+        return ( PackageAdmin ) getService( PACKAGE_ADMIN_NAME );
+    }
+
+
+    protected Object getService( String serviceName )
+    {
+        ServiceTracker serviceTracker = ( ServiceTracker ) services.get( serviceName );
+        if ( serviceTracker == null )
+        {
+            serviceTracker = new ServiceTracker( getBundleContext(), serviceName, null );
+            serviceTracker.open();
+
+            services.put( serviceName, serviceTracker );
+        }
+
+        return serviceTracker.getService();
+    }
+}
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/Logger.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/Logger.java
similarity index 95%
rename from webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/Logger.java
rename to webconsole/src/main/java/org/apache/felix/webconsole/internal/Logger.java
index 7ff6f10..2d8e4b4 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/Logger.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/Logger.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.felix.webconsole.internal.servlet;
+package org.apache.felix.webconsole.internal;
 
 
 import org.osgi.framework.BundleContext;
@@ -30,14 +30,14 @@
     private ServiceTracker logTracker;
 
 
-    Logger( BundleContext bundleContext )
+    public Logger( BundleContext bundleContext )
     {
         logTracker = new ServiceTracker( bundleContext, LogService.class.getName(), null );
         logTracker.open();
     }
 
 
-    void dispose()
+    public void dispose()
     {
         if ( logTracker != null )
         {
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/OsgiManagerPlugin.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/OsgiManagerPlugin.java
new file mode 100644
index 0000000..404a9e3
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/OsgiManagerPlugin.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.webconsole.internal;
+
+
+import org.apache.felix.webconsole.AbstractWebConsolePlugin;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.packageadmin.PackageAdmin;
+import org.osgi.service.startlevel.StartLevel;
+import org.osgi.util.tracker.ServiceTracker;
+
+
+public interface OsgiManagerPlugin
+{
+
+    void activate( BundleContext bundleContext );
+
+
+    void deactivate();
+
+}
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/Util.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/Util.java
index 9d4212e..67972dc 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/Util.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/Util.java
@@ -20,16 +20,11 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintWriter;
-import java.text.MessageFormat;
 import java.util.Collection;
-import java.util.Iterator;
-import java.util.SortedMap;
-import java.util.TreeMap;
 
 import javax.servlet.http.HttpServletResponse;
 
 import org.apache.commons.io.IOUtils;
-import org.apache.felix.webconsole.Render;
 
 
 /**
@@ -59,104 +54,6 @@
     /** Parameter value */
     public static final String VALUE_SHUTDOWN = "shutdown";
 
-    private static final String HEADER = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">"
-        + "<html>"
-        + "<head>"
-        + "<meta http-equiv=\"Content-Type\" content=\"text/html; utf-8\">"
-        + "<link rel=\"icon\" href=\"res/imgs/favicon.ico\">"
-        + "<title>{0} - {12}</title>"
-        + "<script src=\"res/ui/admin.js\" language=\"JavaScript\"></script>"
-        + "<script language=\"JavaScript\">"
-        + "ABOUT_VERSION=''{1}'';"
-        + "ABOUT_JVERSION=''{2}'';"
-        + "ABOUT_JRT=''{3} (build {2})'';"
-        + "ABOUT_JVM=''{4} (build {5}, {6})'';"
-        + "ABOUT_MEM=\"{7} KB\";"
-        + "ABOUT_USED=\"{8} KB\";"
-        + "ABOUT_FREE=\"{9} KB\";"
-        + "</script>"
-        + "<link href=\"res/ui/admin.css\" rel=\"stylesheet\" type=\"text/css\">"
-        + "</head>"
-        + "<body>"
-        + "<div id=\"main\">"
-        + "<div id=\"lead\">"
-        + "<h1>"
-        + "{0}<br>{12}"
-        + "</h1>"
-        + "<p>"
-        + "<a target=\"_blank\" href=\"{13}\" title=\"{11}\"><img src=\"res/imgs/logo.png\" width=\"165\" height=\"63\" border=\"0\"></a>"
-        + "</p>" + "</div>";
-
-    /** The name of the request attribute containig the map of FileItems from the POST request */
-    public static final String ATTR_FILEUPLOAD = "org.apache.felix.webconsole.fileupload";
-
-
-    public static PrintWriter startHtml( HttpServletResponse resp, String pageTitle ) throws IOException
-    {
-        resp.setContentType( "text/html; utf-8" );
-
-        PrintWriter pw = resp.getWriter();
-
-        String adminTitle = "OSGi Management Console"; // ServletEngine.VERSION.getFullProductName();
-        String productName = "Felix"; // ServletEngine.VERSION.getShortProductName();
-        String productWeb = "http://felix.apache.org";
-        String vendorName = "http://www.apache.org"; // ServletEngine.VERSION.getVendorWeb();
-        String vendorWeb = "http://www.apache.org"; // ServletEngine.VERSION.getVendorWeb();
-
-        long freeMem = Runtime.getRuntime().freeMemory() / 1024;
-        long totalMem = Runtime.getRuntime().totalMemory() / 1024;
-        long usedMem = totalMem - freeMem;
-
-        String header = MessageFormat.format( HEADER, new Object[]
-            {
-                adminTitle,
-                "1.0.0-SNAPSHOT", // ServletEngine.VERSION.getFullVersion(),
-                System.getProperty( "java.runtime.version" ), System.getProperty( "java.runtime.name" ),
-                System.getProperty( "java.vm.name" ), System.getProperty( "java.vm.version" ),
-                System.getProperty( "java.vm.info" ), new Long( totalMem ), new Long( usedMem ), new Long( freeMem ),
-                vendorWeb, productName, pageTitle, productWeb, vendorName } );
-        pw.println( header );
-        return pw;
-    }
-
-
-    public static void navigation( PrintWriter pw, Collection renders, String current, boolean disabled )
-    {
-        pw.println( "<p id='technav'>" );
-
-        SortedMap map = new TreeMap();
-        for ( Iterator ri = renders.iterator(); ri.hasNext(); )
-        {
-            Render render = ( Render ) ri.next();
-            if ( render.getLabel() == null )
-            {
-                // ignore renders without a label
-            }
-            else if ( disabled || current.equals( render.getName() ) )
-            {
-                map.put( render.getLabel(), "<span class='technavat'>" + render.getLabel() + "</span>" );
-            }
-            else
-            {
-                map.put( render.getLabel(), "<a href='" + render.getName() + "'>" + render.getLabel() + "</a></li>" );
-            }
-        }
-
-        for ( Iterator li = map.values().iterator(); li.hasNext(); )
-        {
-            pw.println( li.next() );
-        }
-
-        pw.println( "</p>" );
-    }
-
-
-    public static void endHhtml( PrintWriter pw )
-    {
-        pw.println( "</body>" );
-        pw.println( "</html>" );
-    }
-
 
     public static void startScript( PrintWriter pw )
     {
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ComponentConfigurationPrinter.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ComponentConfigurationPrinter.java
index 76a6980..76f3ef6 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ComponentConfigurationPrinter.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ComponentConfigurationPrinter.java
@@ -44,9 +44,9 @@
     private ServiceRegistration registration;
 
 
-    public void setBundleContext( BundleContext bundleContext )
+    public void activate( BundleContext bundleContext )
     {
-        super.setBundleContext( bundleContext );
+        super.activate( bundleContext );
 
         registration = bundleContext.registerService( ConfigurationPrinter.SERVICE, this, null );
     }
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ConfigManagerBase.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ConfigManagerBase.java
index 5a5de80..225268b 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ConfigManagerBase.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ConfigManagerBase.java
@@ -41,15 +41,18 @@
  */
 abstract class ConfigManagerBase extends BaseManagementPlugin
 {
+    private static final String CONFIGURATION_ADMIN_NAME = ConfigurationAdmin.class.getName();
+    
+    private static final String META_TYPE_NAME = MetaTypeService.class.getName();
 
     private ServiceTracker configurationAdmin;
 
     private ServiceTracker metaTypeService;
 
 
-    public void setBundleContext( BundleContext bundleContext )
+    public void activate( BundleContext bundleContext )
     {
-        super.setBundleContext( bundleContext );
+        super.activate( bundleContext );
 
         configurationAdmin = new ServiceTracker( bundleContext, ConfigurationAdmin.class.getName(), null );
         configurationAdmin.open();
@@ -73,12 +76,14 @@
 
     protected ConfigurationAdmin getConfigurationAdmin()
     {
+        //TODO: getService(CONFIGURATION_ADMIN_NAME)
         return ( ConfigurationAdmin ) configurationAdmin.getService();
     }
 
 
     protected MetaTypeService getMetaTypeService()
     {
+        //TODO: getService(META_TYPE_NAME)
         return ( MetaTypeService ) metaTypeService.getService();
     }
 
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/AjaxBundleDetailsAction.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/AjaxBundleDetailsAction.java
index 6fdba1a..b3a312d 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/AjaxBundleDetailsAction.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/AjaxBundleDetailsAction.java
@@ -69,9 +69,9 @@
     private boolean[] bootPkgWildcards;
 
 
-    public void setBundleContext( BundleContext bundleContext )
+    public void activate( BundleContext bundleContext )
     {
-        super.setBundleContext( bundleContext );
+        super.activate( bundleContext );
 
         // bootdelegation property parsing from Apache Felix R4SearchPolicyCore
         String bootDelegation = bundleContext.getProperty( Constants.FRAMEWORK_BOOTDELEGATION );
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/BundleListRender.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/BundleListRender.java
index 7498d7f..5418352 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/BundleListRender.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/BundleListRender.java
@@ -19,123 +19,343 @@
 
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
+import java.util.Date;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.TreeMap;
 
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.apache.felix.webconsole.Render;
-import org.apache.felix.webconsole.internal.BaseManagementPlugin;
+import org.apache.felix.bundlerepository.R4Attribute;
+import org.apache.felix.bundlerepository.R4Export;
+import org.apache.felix.bundlerepository.R4Import;
+import org.apache.felix.bundlerepository.R4Package;
+import org.apache.felix.webconsole.internal.BaseWebConsolePlugin;
 import org.apache.felix.webconsole.internal.Util;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONWriter;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
 import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
 import org.osgi.framework.Version;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.component.ComponentConstants;
+import org.osgi.service.obr.Repository;
+import org.osgi.service.obr.RepositoryAdmin;
+import org.osgi.service.obr.Resource;
+import org.osgi.service.packageadmin.ExportedPackage;
+import org.osgi.service.packageadmin.PackageAdmin;
+import org.osgi.service.startlevel.StartLevel;
 import org.osgi.util.tracker.ServiceTracker;
 
 
 /**
  * The <code>BundleListRender</code> TODO
  */
-public class BundleListRender extends BaseManagementPlugin implements Render
+public class BundleListRender extends BaseWebConsolePlugin
 {
 
-    public static final String NAME = "list";
+    public static final String NAME = "bundles";
 
     public static final String LABEL = "Bundles";
 
     public static final String BUNDLE_ID = "bundleId";
 
-    private static final String INSTALLER_SERVICE_NAME = "org.apache.sling.osgi.assembly.installer.InstallerService";
+    private static final String REPOSITORY_ADMIN_NAME = RepositoryAdmin.class.getName();
 
-    // track the optional installer service manually
-    private ServiceTracker installerService;
+    // bootdelegation property entries. wildcards are converted to package
+    // name prefixes. whether an entry is a wildcard or not is set as a flag
+    // in the bootPkgWildcards array.
+    // see #activate and #isBootDelegated
+    private String[] bootPkgs;
+
+    // a flag for each entry in bootPkgs indicating whether the respective
+    // entry was declared as a wildcard or not
+    // see #activate and #isBootDelegated
+    private boolean[] bootPkgWildcards;
 
 
-    public void setBundleContext( BundleContext bundleContext )
+    public void activate( BundleContext bundleContext )
     {
-        super.setBundleContext( bundleContext );
+        super.activate( bundleContext );
 
-        installerService = new ServiceTracker( bundleContext, INSTALLER_SERVICE_NAME, null );
-        installerService.open();
-    }
-
-
-    // protected void deactivate(ComponentContext context) {
-    // if (installerService != null) {
-    // installerService.close();
-    // installerService = null;
-    // }
-    // }
-
-    public String getName()
-    {
-        return NAME;
+        // bootdelegation property parsing from Apache Felix R4SearchPolicyCore
+        String bootDelegation = bundleContext.getProperty( Constants.FRAMEWORK_BOOTDELEGATION );
+        bootDelegation = ( bootDelegation == null ) ? "java.*" : bootDelegation + ",java.*";
+        StringTokenizer st = new StringTokenizer( bootDelegation, " ," );
+        bootPkgs = new String[st.countTokens()];
+        bootPkgWildcards = new boolean[bootPkgs.length];
+        for ( int i = 0; i < bootPkgs.length; i++ )
+        {
+            bootDelegation = st.nextToken();
+            if ( bootDelegation.endsWith( "*" ) )
+            {
+                bootPkgWildcards[i] = true;
+                bootDelegation = bootDelegation.substring( 0, bootDelegation.length() - 1 );
+            }
+            bootPkgs[i] = bootDelegation;
+        }
     }
 
 
     public String getLabel()
     {
+        return NAME;
+    }
+
+
+    public String getTitle()
+    {
         return LABEL;
     }
 
 
-    public void render( HttpServletRequest request, HttpServletResponse response ) throws IOException
+    protected void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException,
+        IOException
+    {
+
+        String info = request.getPathInfo();
+        if ( info.endsWith( ".json" ) )
+        {
+            info = info.substring( 0, info.length() - 5 );
+            if ( getLabel().equals( info.substring( 1 ) ) )
+            {
+                // should return info on all bundles
+            }
+            else
+            {
+                Bundle bundle = getBundle( info );
+                if ( bundle != null )
+                {
+                    // bundle properties
+
+                    response.setContentType( "text/javascript" );
+                    response.setCharacterEncoding( "UTF-8" );
+
+                    PrintWriter pw = response.getWriter();
+                    JSONWriter jw = new JSONWriter( pw );
+                    try
+                    {
+                        performAction( jw, bundle );
+                    }
+                    catch ( JSONException je )
+                    {
+                        throw new IOException( je.toString() );
+                    }
+                }
+            }
+
+            // nothing more to do
+            return;
+        }
+
+        super.doGet( request, response );
+    }
+
+
+    protected void doPost( HttpServletRequest req, HttpServletResponse resp ) throws ServletException, IOException
+    {
+        boolean success = false;
+        Bundle bundle = getBundle( req.getPathInfo() );
+        long bundleId = bundle.getBundleId();
+        if ( bundle != null )
+        {
+            String action = req.getParameter( "action" );
+            if ( "start".equals( action ) )
+            {
+                // start bundle
+                success = true;
+                try
+                {
+                    bundle.start();
+                }
+                catch ( BundleException be )
+                {
+                    // log
+                }
+            }
+            else if ( "stop".equals( action ) )
+            {
+                // stop bundle
+                success = true;
+                try
+                {
+                    bundle.stop();
+                }
+                catch ( BundleException be )
+                {
+                    // log
+                }
+            }
+            else if ( "update".equals( action ) )
+            {
+                // update bundle
+                success = true;
+            }
+            else if ( "uninstall".equals( action ) )
+            {
+                // uninstall bundle
+                success = true;
+                try
+                {
+                    bundle.uninstall();
+                    bundle = null; // bundle has gone !
+                }
+                catch ( BundleException be )
+                {
+                    // log
+                }
+            }
+        }
+
+        if ( success )
+        {
+            // redirect or 200
+            resp.setStatus( HttpServletResponse.SC_OK );
+            JSONWriter jw = new JSONWriter( resp.getWriter() );
+            try
+            {
+                if ( bundle != null )
+                {
+                    bundleInfo( jw, bundle, true );
+                }
+                else
+                {
+                    jw.object();
+                    jw.key( "bundleId" );
+                    jw.value( bundleId );
+                    jw.endObject();
+                }
+            }
+            catch ( JSONException je )
+            {
+                throw new IOException( je.toString() );
+            }
+        }
+        else
+        {
+            super.doPost( req, resp );
+        }
+    }
+
+
+    private Bundle getBundle( String pathInfo )
+    {
+        // only use last part of the pathInfo
+        pathInfo = pathInfo.substring( pathInfo.lastIndexOf( '/' ) + 1 );
+
+        // assume bundle Id
+        long bundleId;
+        try
+        {
+            bundleId = Long.parseLong( pathInfo );
+        }
+        catch ( NumberFormatException nfe )
+        {
+            bundleId = -1;
+        }
+
+        if ( bundleId >= 0 )
+        {
+            return getBundleContext().getBundle( bundleId );
+        }
+
+        return null;
+    }
+
+
+    protected void renderContent( HttpServletRequest request, HttpServletResponse response ) throws ServletException,
+        IOException
     {
 
         PrintWriter pw = response.getWriter();
 
-        this.header( pw );
+        String appRoot = request.getContextPath() + request.getServletPath();
+        pw.println( "<script src='" + appRoot + "/res/ui/bundles.js' language='JavaScript'></script>" );
 
-        this.installForm( pw );
-        pw.println( "<tr class='content'>" );
-        pw.println( "<td colspan='7' class='content'>&nbsp;</th>" );
-        pw.println( "</tr>" );
-
-        this.tableHeader( pw );
-
-        Bundle[] bundles = this.getBundles();
-        if ( bundles == null || bundles.length == 0 )
+        Util.startScript( pw );
+        pw.println( "var bundleListData = " );
+        JSONWriter jw = new JSONWriter( pw );
+        try
         {
-            pw.println( "<tr class='content'>" );
-            pw.println( "<td class='content' colspan='6'>No " + this.getLabel() + " installed currently</td>" );
-            pw.println( "</tr>" );
-        }
-        else
-        {
+            jw.object();
+            jw.key( "startlevel" );
+            jw.value( getStartLevel().getInitialBundleStartLevel() );
 
-            sort( bundles );
+            Bundle bundle = getBundle( request.getPathInfo() );
+            Bundle[] bundles = ( bundle != null ) ? new Bundle[]
+                { bundle } : this.getBundles();
+            boolean details = ( bundle != null );
 
-            long previousBundle = -1;
-            for ( int i = 0; i < bundles.length; i++ )
+            if ( bundles != null && bundles.length > 0 )
             {
+                sort( bundles );
 
-                if ( previousBundle >= 0 )
+                jw.key( "bundles" );
+
+                jw.array();
+
+                for ( int i = 0; i < bundles.length; i++ )
                 {
-                    // prepare for injected table information row
-                    pw.println( "<tr id='bundle" + previousBundle + "'></tr>" );
+                    bundleInfo( jw, bundles[i], details );
                 }
 
-                this.bundle( pw, bundles[i] );
+                jw.endArray();
 
-                previousBundle = bundles[i].getBundleId();
             }
 
-            if ( previousBundle >= 0 )
-            {
-                // prepare for injected table information row
-                pw.println( "<tr id='bundle" + previousBundle + "'></tr>" );
-            }
+            jw.endObject();
+
+        }
+        catch ( JSONException je )
+        {
+            throw new IOException( je.toString() );
         }
 
-        pw.println( "<tr class='content'>" );
-        pw.println( "<td colspan='7' class='content'>&nbsp;</th>" );
-        pw.println( "</tr>" );
+        pw.println( ";" );
+        pw.println( "render(bundleListData.startlevel, bundleListData.bundles);" );
+        Util.endScript( pw );
+    }
 
-        this.installForm( pw );
 
-        this.footer( pw );
+    private void bundleInfo( JSONWriter jw, Bundle bundle, boolean details ) throws JSONException
+    {
+        jw.object();
+        jw.key( "bundleId" );
+        jw.value( bundle.getBundleId() );
+        jw.key( "name" );
+        jw.value( getName( bundle ) );
+        jw.key( "state" );
+        jw.value( toStateString( bundle.getState() ) );
+        jw.key( "hasStart" );
+        jw.value( hasStart( bundle ) );
+        jw.key( "hasStop" );
+        jw.value( hasStop( bundle ) );
+        jw.key( "hasUpdate" );
+        jw.value( hasUpdate( bundle ) );
+        jw.key( "hasUninstall" );
+        jw.value( hasUninstall( bundle ) );
+
+        if ( details )
+        {
+            bundleDetails( jw, bundle );
+        }
+
+        jw.endObject();
     }
 
 
@@ -145,140 +365,6 @@
     }
 
 
-    private void header( PrintWriter pw )
-    {
-        Util.startScript( pw );
-        pw.println( "function showDetails(bundleId) {" );
-        pw.println( "    var span = document.getElementById('bundle' + bundleId);" );
-        pw.println( "    if (!span) {" );
-        pw.println( "        return;" );
-        pw.println( "    }" );
-        pw.println( "    if (span.innerHTML) {" );
-        pw.println( "        span.innerHTML = '';" );
-        pw.println( "        return;" );
-        pw.println( "    }" );
-        pw.println( "    var parm = '?" + Util.PARAM_ACTION + "=" + AjaxBundleDetailsAction.NAME + "&" + BUNDLE_ID
-            + "=' + bundleId;" );
-        pw.println( "    sendRequest('GET', parm, displayBundleDetails);" );
-        pw.println( "}" );
-        pw.println( "function displayBundleDetails(obj) {" );
-        pw.println( "    var span = document.getElementById('bundle' + obj." + BUNDLE_ID + ");" );
-        pw.println( "    if (!span) {" );
-        pw.println( "        return;" );
-        pw.println( "    }" );
-        pw
-            .println( "    var innerHtml = '<td class=\"content\">&nbsp;</td><td class=\"content\" colspan=\"6\"><table broder=\"0\">';" );
-        pw.println( "    var props = obj.props;" );
-        pw.println( "    for (var i=0; i < props.length; i++) {" );
-        pw
-            .println( "        innerHtml += '<tr><td valign=\"top\" noWrap>' + props[i].key + '</td><td valign=\"top\">' + props[i].value + '</td></tr>';" );
-        pw.println( "    }" );
-        pw.println( "    innerHtml += '</table></td>';" );
-        pw.println( "    span.innerHTML = innerHtml;" );
-        pw.println( "}" );
-        Util.endScript( pw );
-
-        pw.println( "<table class='content' cellpadding='0' cellspacing='0' width='100%'>" );
-    }
-
-
-    private void tableHeader( PrintWriter pw )
-    {
-        // pw.println("<tr class='content'>");
-        // pw.println("<th class='content container' colspan='7'>Installed " +
-        // getLabel() + "</th>");
-        // pw.println("</tr>");
-
-        pw.println( "<tr class='content'>" );
-        pw.println( "<th class='content'>ID</th>" );
-        pw.println( "<th class='content' width='100%'>Name</th>" );
-        pw.println( "<th class='content'>Status</th>" );
-        pw.println( "<th class='content' colspan='4'>Actions</th>" );
-        pw.println( "</tr>" );
-    }
-
-
-    private void footer( PrintWriter pw )
-    {
-        pw.println( "</table>" );
-    }
-
-
-    private void bundle( PrintWriter pw, Bundle bundle )
-    {
-        String name = getName( bundle );
-
-        pw.println( "<tr>" );
-        pw.println( "<td class='content right'>" + bundle.getBundleId() + "</td>" );
-        pw.println( "<td class='content'><a href='javascript:showDetails(" + bundle.getBundleId() + ")'>" + name
-            + "</a></td>" );
-        pw.println( "<td class='content center'>" + this.toStateString( bundle.getState() ) + "</td>" );
-
-        // no buttons for system bundle
-        if ( bundle.getBundleId() == 0 )
-        {
-            pw.println( "<td class='content' colspan='4'>&nbsp;</td>" );
-        }
-        else
-        {
-            boolean enabled = bundle.getState() == Bundle.INSTALLED || bundle.getState() == Bundle.RESOLVED;
-            this.actionForm( pw, enabled, bundle.getBundleId(), StartAction.NAME, StartAction.LABEL );
-
-            enabled = bundle.getState() == Bundle.ACTIVE;
-            this.actionForm( pw, enabled, bundle.getBundleId(), StopAction.NAME, StopAction.LABEL );
-
-            enabled = bundle.getState() != Bundle.UNINSTALLED && this.hasUpdates( bundle );
-            this.actionForm( pw, enabled, bundle.getBundleId(), UpdateAction.NAME, UpdateAction.LABEL );
-
-            enabled = bundle.getState() == Bundle.INSTALLED || bundle.getState() == Bundle.RESOLVED
-                || bundle.getState() == Bundle.ACTIVE;
-            this.actionForm( pw, enabled, bundle.getBundleId(), UninstallAction.NAME, UninstallAction.LABEL );
-        }
-
-        pw.println( "</tr>" );
-    }
-
-
-    private void actionForm( PrintWriter pw, boolean enabled, long bundleId, String action, String actionLabel )
-    {
-        pw.println( "<form name='form" + bundleId + "' method='post'>" );
-        pw.println( "<td class='content' align='right'>" );
-        pw.println( "<input type='hidden' name='" + Util.PARAM_ACTION + "' value='" + action + "' />" );
-        pw.println( "<input type='hidden' name='" + BUNDLE_ID + "' value='" + bundleId + "' />" );
-        pw.println( "<input class='submit' type='submit' value='" + actionLabel + "'" + ( enabled ? "" : "disabled" )
-            + " />" );
-        pw.println( "</td>" );
-        pw.println( "</form>" );
-    }
-
-
-    private void installForm( PrintWriter pw )
-    {
-        int startLevel = getStartLevel().getInitialBundleStartLevel();
-
-        pw.println( "<form method='post' enctype='multipart/form-data'>" );
-        pw.println( "<tr class='content'>" );
-        pw.println( "<td class='content'>&nbsp;</td>" );
-        pw.println( "<td class='content'>" );
-        pw.println( "<input type='hidden' name='" + Util.PARAM_ACTION + "' value='" + InstallAction.NAME + "' />" );
-        pw.println( "<input class='input' type='file' name='" + InstallAction.FIELD_BUNDLEFILE + "'>" );
-        pw.println( " - Start <input class='checkradio' type='checkbox' name='" + InstallAction.FIELD_START
-            + "' value='start'>" );
-        pw.println( " - Start Level <input class='input' type='input' name='" + InstallAction.FIELD_STARTLEVEL
-            + "' value='" + startLevel + "' width='4'>" );
-        pw.println( "</td>" );
-        pw.println( "<td class='content' align='right' colspan='5' noWrap>" );
-        pw.println( "<input class='submit' style='width:auto' type='submit' value='" + InstallAction.LABEL + "'>" );
-        pw.println( "&nbsp;" );
-        pw.println( "<input class='submit' style='width:auto' type='submit' value='" + RefreshPackagesAction.LABEL
-            + "' onClick='this.form[\"" + Util.PARAM_ACTION + "\"].value=\"" + RefreshPackagesAction.NAME
-            + "\"; return true;'>" );
-        pw.println( "</td>" );
-        pw.println( "</tr>" );
-        pw.println( "</form>" );
-    }
-
-
     private String toStateString( int bundleState )
     {
         switch ( bundleState )
@@ -301,11 +387,24 @@
     }
 
 
-    private boolean hasUpdates( Bundle bundle )
+    private boolean hasStart( Bundle bundle )
     {
+        return bundle.getState() == Bundle.INSTALLED || bundle.getState() == Bundle.RESOLVED;
+    }
+
+
+    private boolean hasStop( Bundle bundle )
+    {
+        return bundle.getState() == Bundle.ACTIVE;
+    }
+
+
+    private boolean hasUpdate( Bundle bundle )
+    {
+        //        enabled = bundle.getState() != Bundle.UNINSTALLED && this.hasUpdates( bundle );
 
         // no updates if there is no installer service
-        Object isObject = installerService.getService();
+        Object isObject = getService( REPOSITORY_ADMIN_NAME );
         if ( isObject == null )
         {
             return false;
@@ -316,25 +415,39 @@
         {
             return false;
         }
-        /*
-                Version bundleVersion = Version.parseVersion((String) bundle.getHeaders().get(
-                    Constants.BUNDLE_VERSION));
 
-                BundleRepositoryAdmin repoAdmin = ((InstallerService) isObject).getBundleRepositoryAdmin();
-                for (Iterator<Resource> ri = repoAdmin.getResources(); ri.hasNext();) {
-                    Resource res = ri.next();
-                    if (bundle.getSymbolicName().equals(res.getSymbolicName())) {
-                        if (res.getVersion().compareTo(bundleVersion) > 0) {
-                            return true;
-                        }
+        Version bundleVersion = Version.parseVersion( ( String ) bundle.getHeaders().get( Constants.BUNDLE_VERSION ) );
+
+        RepositoryAdmin repoAdmin = ( RepositoryAdmin ) isObject;
+        Repository[] repositories = repoAdmin.listRepositories();
+        for ( int i = 0; i < repositories.length; i++ )
+        {
+            Resource[] resources = repositories[i].getResources();
+            for ( int j = 0; j < resources.length; j++ )
+            {
+                Resource res = resources[j];
+                if ( bundle.getSymbolicName().equals( res.getSymbolicName() ) )
+                {
+                    if ( res.getVersion().compareTo( bundleVersion ) > 0 )
+                    {
+                        return true;
                     }
                 }
-        */
+            }
+        }
 
         return false;
     }
 
 
+    private boolean hasUninstall( Bundle bundle )
+    {
+        return bundle.getState() == Bundle.INSTALLED || bundle.getState() == Bundle.RESOLVED
+            || bundle.getState() == Bundle.ACTIVE;
+
+    }
+
+
     private void sort( Bundle[] bundles )
     {
         Arrays.sort( bundles, BUNDLE_NAME_COMPARATOR );
@@ -359,6 +472,476 @@
         return name;
     }
 
+
+    public void performAction( JSONWriter jw, Bundle bundle ) throws JSONException
+    {
+        jw.object();
+        jw.key( BUNDLE_ID );
+        jw.value( bundle.getBundleId() );
+
+        bundleDetails( jw, bundle );
+
+        jw.endObject();
+    }
+
+
+    public void bundleDetails( JSONWriter jw, Bundle bundle ) throws JSONException
+    {
+        Dictionary headers = bundle.getHeaders();
+
+        jw.key( "props" );
+        jw.array();
+        keyVal( jw, "Symbolic Name", bundle.getSymbolicName() );
+        keyVal( jw, "Version", headers.get( Constants.BUNDLE_VERSION ) );
+        keyVal( jw, "Location", bundle.getLocation() );
+        keyVal( jw, "Last Modification", new Date( bundle.getLastModified() ) );
+
+        keyVal( jw, "Vendor", headers.get( Constants.BUNDLE_VENDOR ) );
+        keyVal( jw, "Copyright", headers.get( Constants.BUNDLE_COPYRIGHT ) );
+        keyVal( jw, "Description", headers.get( Constants.BUNDLE_DESCRIPTION ) );
+
+        keyVal( jw, "Start Level", getStartLevel( bundle ) );
+
+        if ( bundle.getState() == Bundle.INSTALLED )
+        {
+            listImportExportsUnresolved( jw, bundle );
+        }
+        else
+        {
+            listImportExport( jw, bundle );
+        }
+
+        listServices( jw, bundle );
+
+        jw.endArray();
+    }
+
+
+    private Integer getStartLevel( Bundle bundle )
+    {
+        StartLevel sl = getStartLevel();
+        return ( sl != null ) ? new Integer( sl.getBundleStartLevel( bundle ) ) : null;
+    }
+
+
+    private void listImportExport( JSONWriter jw, Bundle bundle ) throws JSONException
+    {
+        PackageAdmin packageAdmin = getPackageAdmin();
+        if ( packageAdmin == null )
+        {
+            return;
+        }
+
+        Map usingBundles = new TreeMap();
+
+        ExportedPackage[] exports = packageAdmin.getExportedPackages( bundle );
+        if ( exports != null && exports.length > 0 )
+        {
+            // do alphabetical sort
+            Arrays.sort( exports, new Comparator()
+            {
+                public int compare( ExportedPackage p1, ExportedPackage p2 )
+                {
+                    return p1.getName().compareTo( p2.getName() );
+                }
+
+
+                public int compare( Object o1, Object o2 )
+                {
+                    return compare( ( ExportedPackage ) o1, ( ExportedPackage ) o2 );
+                }
+            } );
+
+            StringBuffer val = new StringBuffer();
+            for ( int j = 0; j < exports.length; j++ )
+            {
+                ExportedPackage export = exports[j];
+                printExport( val, export.getName(), export.getVersion() );
+                Bundle[] ubList = export.getImportingBundles();
+                if ( ubList != null )
+                {
+                    for ( int i = 0; i < ubList.length; i++ )
+                    {
+                        Bundle ub = ubList[i];
+                        usingBundles.put( ub.getSymbolicName(), ub );
+                    }
+                }
+            }
+            keyVal( jw, "Exported Packages", val.toString() );
+        }
+        else
+        {
+            keyVal( jw, "Exported Packages", "None" );
+        }
+
+        exports = packageAdmin.getExportedPackages( ( Bundle ) null );
+        if ( exports != null && exports.length > 0 )
+        {
+            // collect import packages first
+            final List imports = new ArrayList();
+            for ( int i = 0; i < exports.length; i++ )
+            {
+                final ExportedPackage ep = exports[i];
+                final Bundle[] importers = ep.getImportingBundles();
+                for ( int j = 0; importers != null && j < importers.length; j++ )
+                {
+                    if ( importers[j].getBundleId() == bundle.getBundleId() )
+                    {
+                        imports.add( ep );
+
+                        break;
+                    }
+                }
+            }
+            // now sort
+            StringBuffer val = new StringBuffer();
+            if ( imports.size() > 0 )
+            {
+                final ExportedPackage[] packages = ( ExportedPackage[] ) imports.toArray( new ExportedPackage[imports
+                    .size()] );
+                Arrays.sort( packages, new Comparator()
+                {
+                    public int compare( ExportedPackage p1, ExportedPackage p2 )
+                    {
+                        return p1.getName().compareTo( p2.getName() );
+                    }
+
+
+                    public int compare( Object o1, Object o2 )
+                    {
+                        return compare( ( ExportedPackage ) o1, ( ExportedPackage ) o2 );
+                    }
+                } );
+                // and finally print out
+                for ( int i = 0; i < packages.length; i++ )
+                {
+                    ExportedPackage ep = packages[i];
+                    printImport( val, ep.getName(), ep.getVersion(), ep );
+                }
+            }
+            else
+            {
+                // add description if there are no imports
+                val.append( "None" );
+            }
+
+            keyVal( jw, "Imported Packages", val.toString() );
+        }
+
+        if ( !usingBundles.isEmpty() )
+        {
+            StringBuffer val = new StringBuffer();
+            for ( Iterator ui = usingBundles.values().iterator(); ui.hasNext(); )
+            {
+                Bundle usingBundle = ( Bundle ) ui.next();
+                val.append( getBundleDescriptor( usingBundle ) );
+                val.append( "<br />" );
+            }
+            keyVal( jw, "Importing Bundles", val.toString() );
+        }
+    }
+
+
+    private void listImportExportsUnresolved( JSONWriter jw, Bundle bundle ) throws JSONException
+    {
+        Dictionary dict = bundle.getHeaders();
+
+        String target = ( String ) dict.get( Constants.EXPORT_PACKAGE );
+        if ( target != null )
+        {
+            R4Package[] pkgs = R4Package.parseImportOrExportHeader( target );
+            if ( pkgs != null && pkgs.length > 0 )
+            {
+                // do alphabetical sort
+                Arrays.sort( pkgs, new Comparator()
+                {
+                    public int compare( R4Package p1, R4Package p2 )
+                    {
+                        return p1.getName().compareTo( p2.getName() );
+                    }
+
+
+                    public int compare( Object o1, Object o2 )
+                    {
+                        return compare( ( R4Package ) o1, ( R4Package ) o2 );
+                    }
+                } );
+
+                StringBuffer val = new StringBuffer();
+                for ( int i = 0; i < pkgs.length; i++ )
+                {
+                    R4Export export = new R4Export( pkgs[i] );
+                    printExport( val, export.getName(), export.getVersion() );
+                }
+                keyVal( jw, "Exported Packages", val.toString() );
+            }
+            else
+            {
+                keyVal( jw, "Exported Packages", "None" );
+            }
+        }
+
+        target = ( String ) dict.get( Constants.IMPORT_PACKAGE );
+        if ( target != null )
+        {
+            R4Package[] pkgs = R4Package.parseImportOrExportHeader( target );
+            if ( pkgs != null && pkgs.length > 0 )
+            {
+                Map imports = new TreeMap();
+                for ( int i = 0; i < pkgs.length; i++ )
+                {
+                    R4Package pkg = pkgs[i];
+                    imports.put( pkg.getName(), new R4Import( pkg ) );
+                }
+
+                // collect import packages first
+                final Map candidates = new HashMap();
+                PackageAdmin packageAdmin = getPackageAdmin();
+                if ( packageAdmin != null )
+                {
+                    ExportedPackage[] exports = packageAdmin.getExportedPackages( ( Bundle ) null );
+                    if ( exports != null && exports.length > 0 )
+                    {
+
+                        for ( int i = 0; i < exports.length; i++ )
+                        {
+                            final ExportedPackage ep = exports[i];
+
+                            R4Import imp = ( R4Import ) imports.get( ep.getName() );
+                            if ( imp != null && imp.isSatisfied( toR4Export( ep ) ) )
+                            {
+                                candidates.put( ep.getName(), ep );
+                            }
+                        }
+                    }
+                }
+
+                // now sort
+                StringBuffer val = new StringBuffer();
+                if ( imports.size() > 0 )
+                {
+                    for ( Iterator ii = imports.values().iterator(); ii.hasNext(); )
+                    {
+                        R4Import r4Import = ( R4Import ) ii.next();
+                        ExportedPackage ep = ( ExportedPackage ) candidates.get( r4Import.getName() );
+
+                        // if there is no matching export, check whether this
+                        // bundle has the package, ignore the entry in this case
+                        if ( ep == null )
+                        {
+                            String path = r4Import.getName().replace( '.', '/' );
+                            if ( bundle.getResource( path ) != null )
+                            {
+                                continue;
+                            }
+                        }
+
+                        printImport( val, r4Import.getName(), r4Import.getVersion(), ep );
+                    }
+                }
+                else
+                {
+                    // add description if there are no imports
+                    val.append( "None" );
+                }
+
+                keyVal( jw, "Imported Packages", val.toString() );
+            }
+        }
+    }
+
+
+    private void listServices( JSONWriter jw, Bundle bundle ) throws JSONException
+    {
+        ServiceReference[] refs = bundle.getRegisteredServices();
+        if ( refs == null || refs.length == 0 )
+        {
+            return;
+        }
+
+        for ( int i = 0; i < refs.length; i++ )
+        {
+            String key = "Service ID " + refs[i].getProperty( Constants.SERVICE_ID );
+
+            StringBuffer val = new StringBuffer();
+
+            appendProperty( val, refs[i], Constants.OBJECTCLASS, "Types" );
+            appendProperty( val, refs[i], Constants.SERVICE_PID, "PID" );
+            appendProperty( val, refs[i], ConfigurationAdmin.SERVICE_FACTORYPID, "Factory PID" );
+            appendProperty( val, refs[i], ComponentConstants.COMPONENT_NAME, "Component Name" );
+            appendProperty( val, refs[i], ComponentConstants.COMPONENT_ID, "Component ID" );
+            appendProperty( val, refs[i], ComponentConstants.COMPONENT_FACTORY, "Component Factory" );
+            appendProperty( val, refs[i], Constants.SERVICE_DESCRIPTION, "Description" );
+            appendProperty( val, refs[i], Constants.SERVICE_VENDOR, "Vendor" );
+
+            keyVal( jw, key, val.toString() );
+        }
+    }
+
+
+    private void appendProperty( StringBuffer dest, ServiceReference ref, String name, String label )
+    {
+        Object value = ref.getProperty( name );
+        if ( value instanceof Object[] )
+        {
+            Object[] values = ( Object[] ) value;
+            dest.append( label ).append( ": " );
+            for ( int j = 0; j < values.length; j++ )
+            {
+                if ( j > 0 )
+                    dest.append( ", " );
+                dest.append( values[j] );
+            }
+            dest.append( "<br />" ); // assume HTML use of result
+        }
+        else if ( value != null )
+        {
+            dest.append( label ).append( ": " ).append( value ).append( "<br />" );
+        }
+    }
+
+
+    private void keyVal( JSONWriter jw, String key, Object value ) throws JSONException
+    {
+        if ( key != null && value != null )
+        {
+            jw.object();
+            jw.key( "key" );
+            jw.value( key );
+            jw.key( "value" );
+            jw.value( value );
+            jw.endObject();
+        }
+    }
+
+
+    private void printExport( StringBuffer val, String name, Version version )
+    {
+        boolean bootDel = isBootDelegated( name );
+        if ( bootDel )
+        {
+            val.append( "<span style=\"color: red\">!! " );
+        }
+
+        val.append( name );
+        val.append( ",version=" );
+        val.append( version );
+
+        if ( bootDel )
+        {
+            val.append( " -- Overwritten by Boot Delegation</span>" );
+        }
+
+        val.append( "<br />" );
+    }
+
+
+    private void printImport( StringBuffer val, String name, Version version, ExportedPackage export )
+    {
+        boolean bootDel = isBootDelegated( name );
+        if ( bootDel || export == null )
+        {
+            val.append( "<span style=\"color: red\">!! " );
+        }
+
+        val.append( name );
+        val.append( ",version=" ).append( version );
+        val.append( " from " );
+
+        if ( export != null )
+        {
+            val.append( getBundleDescriptor( export.getExportingBundle() ) );
+
+            if ( bootDel )
+            {
+                val.append( " -- Overwritten by Boot Delegation</span>" );
+            }
+        }
+        else
+        {
+            val.append( " -- Cannot be resolved" );
+            if ( bootDel )
+            {
+                val.append( " and overwritten by Boot Delegation" );
+            }
+            val.append( "</span>" );
+        }
+
+        val.append( "<br />" );
+    }
+
+
+    // returns true if the package is listed in the bootdelegation property
+    private boolean isBootDelegated( String pkgName )
+    {
+
+        // bootdelegation analysis from Apache Felix R4SearchPolicyCore
+
+        // Only consider delegation if we have a package name, since
+        // we don't want to promote the default package. The spec does
+        // not take a stand on this issue.
+        if ( pkgName.length() > 0 )
+        {
+
+            // Delegate any packages listed in the boot delegation
+            // property to the parent class loader.
+            for ( int i = 0; i < bootPkgs.length; i++ )
+            {
+
+                // A wildcarded boot delegation package will be in the form of
+                // "foo.", so if the package is wildcarded do a startsWith() or
+                // a regionMatches() to ignore the trailing "." to determine if
+                // the request should be delegated to the parent class loader.
+                // If the package is not wildcarded, then simply do an equals()
+                // test to see if the request should be delegated to the parent
+                // class loader.
+                if ( ( bootPkgWildcards[i] && ( pkgName.startsWith( bootPkgs[i] ) || bootPkgs[i].regionMatches( 0,
+                    pkgName, 0, pkgName.length() ) ) )
+                    || ( !bootPkgWildcards[i] && bootPkgs[i].equals( pkgName ) ) )
+                {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+
+    private R4Export toR4Export( ExportedPackage export )
+    {
+        R4Attribute version = new R4Attribute( Constants.VERSION_ATTRIBUTE, export.getVersion().toString(), false );
+        return new R4Export( export.getName(), null, new R4Attribute[]
+            { version } );
+    }
+
+
+    private String getBundleDescriptor( Bundle bundle )
+    {
+        StringBuffer val = new StringBuffer();
+        if ( bundle.getSymbolicName() != null )
+        {
+            // list the bundle name if not null
+            val.append( bundle.getSymbolicName() );
+            val.append( " (" ).append( bundle.getBundleId() );
+            val.append( ")" );
+        }
+        else if ( bundle.getLocation() != null )
+        {
+            // otherwise try the location
+            val.append( bundle.getLocation() );
+            val.append( " (" ).append( bundle.getBundleId() );
+            val.append( ")" );
+        }
+        else
+        {
+            // fallback to just the bundle id
+            // only append the bundle
+            val.append( bundle.getBundleId() );
+        }
+        return val.toString();
+    }
+
     // ---------- inner classes ------------------------------------------------
 
     private static final Comparator BUNDLE_NAME_COMPARATOR = new Comparator()
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/InstallAction.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/InstallAction.java
index b839947..6b0c658 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/InstallAction.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/InstallAction.java
@@ -29,7 +29,7 @@
 import javax.servlet.http.HttpServletResponse;
 
 import org.apache.commons.fileupload.FileItem;
-import org.apache.felix.webconsole.internal.Util;
+import org.apache.felix.webconsole.AbstractWebConsolePlugin;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleException;
 import org.osgi.framework.Constants;
@@ -74,7 +74,7 @@
     {
 
         // get the uploaded data
-        Map params = ( Map ) request.getAttribute( Util.ATTR_FILEUPLOAD );
+        Map params = ( Map ) request.getAttribute( AbstractWebConsolePlugin.ATTR_FILEUPLOAD );
         if ( params == null )
         {
             return true;
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/UpdateAction.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/UpdateAction.java
index 58e1284..96ec646 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/UpdateAction.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/UpdateAction.java
@@ -42,9 +42,9 @@
     private ServiceTracker installerService;
 
 
-    public void setBundleContext( BundleContext bundleContext )
+    public void activate( BundleContext bundleContext )
     {
-        super.setBundleContext( bundleContext );
+        super.activate( bundleContext );
 
         installerService = new ServiceTracker( bundleContext, INSTALLER_SERVICE_NAME, null );
         installerService.open();
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/obr/AbstractObrPlugin.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/obr/AbstractObrPlugin.java
index 0252f02..450d848 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/obr/AbstractObrPlugin.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/obr/AbstractObrPlugin.java
@@ -20,7 +20,7 @@
 
 
 import org.apache.felix.webconsole.internal.BaseManagementPlugin;
-import org.osgi.framework.BundleContext;
+import org.osgi.service.obr.RepositoryAdmin;
 import org.osgi.util.tracker.ServiceTracker;
 
 
@@ -28,33 +28,27 @@
 {
 
     // track the optional installer service manually
-    private ServiceTracker installerService;
+    private ServiceTracker repositoryAdmin;
 
 
-    public void setBundleContext( BundleContext bundleContext )
+    protected RepositoryAdmin getRepositoryAdmin()
     {
-        super.setBundleContext( bundleContext );
-
-    }
-    /*
-        protected InstallerService getInstallerService() {
-            if (installerService == null) {
-                try {
-                    installerService = new ServiceTracker(getBundleContext(),
-                        InstallerService.class.getName(), null);
-                    installerService.open();
-                } catch (Throwable t) {
-                    // missing InstallerService class ??
-                    return null;
-                }
-
+        if ( repositoryAdmin == null )
+        {
+            try
+            {
+                repositoryAdmin = new ServiceTracker( getBundleContext(), RepositoryAdmin.class.getName(), null );
+                repositoryAdmin.open();
+            }
+            catch ( Throwable t )
+            {
+                // missing InstallerService class ??
+                return null;
             }
 
-            return (InstallerService) installerService.getService();
         }
 
-        protected BundleRepositoryAdmin getBundleRepositoryAdmin() {
-            InstallerService is = getInstallerService();
-            return (is != null) ? is.getBundleRepositoryAdmin() : null;
-        }*/
+        return ( RepositoryAdmin ) repositoryAdmin.getService();
+    }
+
 }
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/obr/BundleRepositoryRender.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/obr/BundleRepositoryRender.java
index cdd97ab..222c368 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/obr/BundleRepositoryRender.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/obr/BundleRepositoryRender.java
@@ -17,12 +17,23 @@
 package org.apache.felix.webconsole.internal.obr;
 
 
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Date;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
 import java.util.StringTokenizer;
+import java.util.TreeSet;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 
 import org.apache.felix.webconsole.Render;
 import org.apache.felix.webconsole.internal.Util;
@@ -30,9 +41,12 @@
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
 import org.osgi.framework.Version;
+import org.osgi.service.obr.Repository;
+import org.osgi.service.obr.RepositoryAdmin;
+import org.osgi.service.obr.Resource;
 
 
-public abstract class BundleRepositoryRender extends AbstractObrPlugin implements Render
+public class BundleRepositoryRender extends AbstractObrPlugin implements Render
 {
 
     public static final String NAME = "bundlerepo";
@@ -48,9 +62,9 @@
     private String[] repoURLs;
 
 
-    public void setBundleContext( BundleContext bundleContext )
+    public void activate( BundleContext bundleContext )
     {
-        super.setBundleContext( bundleContext );
+        super.activate( bundleContext );
 
         String urlStr = bundleContext.getProperty( REPOSITORY_PROPERTY );
         List urlList = new ArrayList();
@@ -80,77 +94,83 @@
     }
 
 
-    /*
-        public void render(HttpServletRequest request, HttpServletResponse response)
-                throws IOException {
+    public void render( HttpServletRequest request, HttpServletResponse response ) throws IOException
+    {
 
-            PrintWriter pw = response.getWriter();
-            this.header(pw);
+        PrintWriter pw = response.getWriter();
+        this.header( pw );
 
-            Iterator<?> repos;
-            BundleRepositoryAdmin repoAdmin = getBundleRepositoryAdmin();
-            if (repoAdmin != null) {
-                repos = repoAdmin.getRepositories();
-            } else {
-                repos = Collections.emptyList().iterator();
-            }
-
-            Set<String> activeURLs = new HashSet<String>();
-            if (!repos.hasNext()) {
-                pw.println("<tr class='content'>");
-                pw.println("<td class='content' colspan='4'>No Active Repositories</td>");
-                pw.println("</tr>");
-            } else {
-                while (repos.hasNext()) {
-                    Repository repo = (Repository) repos.next();
-
-                    activeURLs.add(repo.getURL().toString());
-
-                    pw.println("<tr class='content'>");
-                    pw.println("<td class='content'>" + repo.getName() + "</td>");
-                    pw.println("<td class='content'>" + repo.getURL() + "</td>");
-                    pw.println("<td class='content'>"
-                        + new Date(repo.getLastModified()) + "</td>");
-                    pw.println("<td class='content'>");
-                    pw.println("<form>");
-                    pw.println("<input type='hidden' name='" + Util.PARAM_ACTION
-                        + "' value='" + RefreshRepoAction.NAME + "'>");
-                    pw.println("<input type='hidden' name='"
-                        + RefreshRepoAction.PARAM_REPO + "' value='"
-                        + repo.getURL() + "'>");
-                    pw.println("<input class='submit' type='submit' value='Refresh'>");
-                    pw.println("</form>");
-                    pw.println("</td>");
-                    pw.println("</tr>");
-                }
-            }
-
-            // list any repositories configured but not active
-            for (int i = 0; i < this.repoURLs.length; i++) {
-                if (!activeURLs.contains(this.repoURLs[i])) {
-                    pw.println("<tr class='content'>");
-                    pw.println("<td class='content'>-</td>");
-                    pw.println("<td class='content'>" + this.repoURLs[i] + "</td>");
-                    pw.println("<td class='content'>[inactive, click Refresh to activate]</td>");
-                    pw.println("<td class='content'>");
-                    pw.println("<form>");
-                    pw.println("<input type='hidden' name='" + Util.PARAM_ACTION
-                        + "' value='" + RefreshRepoAction.NAME + "'>");
-                    pw.println("<input type='hidden' name='"
-                        + RefreshRepoAction.PARAM_REPO + "' value='"
-                        + this.repoURLs[i] + "'>");
-                    pw.println("<input class='submit' type='submit' value='Refresh'>");
-                    pw.println("</form>");
-                    pw.println("</td>");
-                    pw.println("</tr>");
-                }
-            }
-
-            this.footer(pw);
-
-            this.listResources(pw);
+        RepositoryAdmin repoAdmin = getRepositoryAdmin();
+        Repository[] repos;
+        if ( repoAdmin != null )
+        {
+            repos = repoAdmin.listRepositories();
         }
-    */
+        else
+        {
+            repos = null;
+        }
+
+        Set activeURLs = new HashSet();
+        if ( repos == null || repos.length == 0 )
+        {
+            pw.println( "<tr class='content'>" );
+            pw.println( "<td class='content' colspan='4'>No Active Repositories</td>" );
+            pw.println( "</tr>" );
+        }
+        else
+        {
+            for ( int i = 0; i < repos.length; i++ )
+            {
+                Repository repo = repos[i];
+
+                activeURLs.add( repo.getURL().toString() );
+
+                pw.println( "<tr class='content'>" );
+                pw.println( "<td class='content'>" + repo.getName() + "</td>" );
+                pw.println( "<td class='content'>" + repo.getURL() + "</td>" );
+                pw.println( "<td class='content'>" + new Date( repo.getLastModified() ) + "</td>" );
+                pw.println( "<td class='content'>" );
+                pw.println( "<form>" );
+                pw.println( "<input type='hidden' name='" + Util.PARAM_ACTION + "' value='" + RefreshRepoAction.NAME
+                    + "'>" );
+                pw.println( "<input type='hidden' name='" + RefreshRepoAction.PARAM_REPO + "' value='" + repo.getURL()
+                    + "'>" );
+                pw.println( "<input class='submit' type='submit' value='Refresh'>" );
+                pw.println( "</form>" );
+                pw.println( "</td>" );
+                pw.println( "</tr>" );
+            }
+        }
+
+        // list any repositories configured but not active
+        for ( int i = 0; i < this.repoURLs.length; i++ )
+        {
+            if ( !activeURLs.contains( this.repoURLs[i] ) )
+            {
+                pw.println( "<tr class='content'>" );
+                pw.println( "<td class='content'>-</td>" );
+                pw.println( "<td class='content'>" + this.repoURLs[i] + "</td>" );
+                pw.println( "<td class='content'>[inactive, click Refresh to activate]</td>" );
+                pw.println( "<td class='content'>" );
+                pw.println( "<form>" );
+                pw.println( "<input type='hidden' name='" + Util.PARAM_ACTION + "' value='" + RefreshRepoAction.NAME
+                    + "'>" );
+                pw.println( "<input type='hidden' name='" + RefreshRepoAction.PARAM_REPO + "' value='"
+                    + this.repoURLs[i] + "'>" );
+                pw.println( "<input class='submit' type='submit' value='Refresh'>" );
+                pw.println( "</form>" );
+                pw.println( "</td>" );
+                pw.println( "</tr>" );
+            }
+        }
+
+        this.footer( pw );
+
+        this.listResources( pw, repos );
+    }
+
+
     private void header( PrintWriter pw )
     {
         pw.println( "<table class='content' cellpadding='0' cellspacing='0' width='100%'>" );
@@ -194,72 +214,86 @@
     }
 
 
-    /*
-        private void listResources(PrintWriter pw) {
-            InstallerService is = getInstallerService();
-            if (is == null) {
-                return;
+    private void listResources( PrintWriter pw, Repository[] repos )
+    {
+
+        Map bundles = this.getBundles();
+
+        SortedSet resSet = new TreeSet( new Comparator()
+        {
+            public int compare( Object arg0, Object arg1 )
+            {
+                return compare( ( Resource ) arg0, ( Resource ) arg1 );
             }
 
-            Map<String, Version> bundles = this.getBundles();
 
-            Iterator<?> resources = is.getBundleRepositoryAdmin().getResources();
-            SortedSet<Resource> resSet = new TreeSet<Resource>(
-                new Comparator<Resource>() {
-                    public int compare(Resource o1, Resource o2) {
-                        if (o1 == o2 || o1.equals(o2)) {
-                            return 0;
-                        }
+            public int compare( Resource o1, Resource o2 )
+            {
+                if ( o1 == o2 || o1.equals( o2 ) )
+                {
+                    return 0;
+                }
 
-                        if (o1.getPresentationName().equals(
-                            o2.getPresentationName())) {
-                            return o1.getVersion().compareTo(o2.getVersion());
-                        }
+                if ( o1.getPresentationName().equals( o2.getPresentationName() ) )
+                {
+                    return o1.getVersion().compareTo( o2.getVersion() );
+                }
 
-                        return o1.getPresentationName().compareTo(
-                            o2.getPresentationName());
-                    }
-                });
+                return o1.getPresentationName().compareTo( o2.getPresentationName() );
+            }
+        } );
 
-            while (resources.hasNext()) {
-                Resource res = (Resource) resources.next();
-                Version ver = bundles.get(res.getSymbolicName());
-                if (ver == null || ver.compareTo(res.getVersion()) < 0) {
-                    resSet.add(res);
+        for ( int i = 0; i < repos.length; i++ )
+        {
+            Resource[] resources = repos[i].getResources();
+            for ( int j = 0; j < resources.length; j++ )
+            {
+                Resource res = resources[j];
+                Version ver = ( Version ) bundles.get( res.getSymbolicName() );
+                if ( ver == null || ver.compareTo( res.getVersion() ) < 0 )
+                {
+                    resSet.add( res );
                 }
             }
-
-            this.resourcesHeader(pw, !resSet.isEmpty());
-
-            for (Resource resource : resSet) {
-                this.printResource(pw, resource);
-            }
-
-            this.resourcesFooter(pw, !resSet.isEmpty());
         }
 
-        private void printResource(PrintWriter pw, Resource res) {
-            pw.println("<tr class='content'>");
-            pw.println("<td class='content' valign='top' align='center'><input class='checkradio' type='checkbox' name='bundle' value='"
-                + res.getSymbolicName() + "," + res.getVersion() + "'></td>");
+        this.resourcesHeader( pw, !resSet.isEmpty() );
 
-            // check whether the resource is an assembly (category name)
-            String style = "";
-            String[] cat = res.getCategories();
-            for (int i = 0; cat != null && i < cat.length; i++) {
-                if ("assembly".equals(cat[i])) {
-                    style = "style='font-weight:bold'";
-                }
-            }
-            pw.println("<td class='content' " + style + ">"
-                + res.getPresentationName() + " (" + res.getSymbolicName()
-                + ")</td>");
-            pw.println("<td class='content' " + style + " valign='top'>"
-                + res.getVersion() + "</td>");
-
-            pw.println("</tr>");
+        for ( Iterator ri = resSet.iterator(); ri.hasNext(); )
+        {
+            Resource resource = ( Resource ) ri.next();
+            this.printResource( pw, resource );
         }
-    */
+
+        this.resourcesFooter( pw, !resSet.isEmpty() );
+    }
+
+
+    private void printResource( PrintWriter pw, Resource res )
+    {
+        pw.println( "<tr class='content'>" );
+        pw
+            .println( "<td class='content' valign='top' align='center'><input class='checkradio' type='checkbox' name='bundle' value='"
+                + res.getSymbolicName() + "," + res.getVersion() + "'></td>" );
+
+        // check whether the resource is an assembly (category name)
+        String style = "";
+        String[] cat = res.getCategories();
+        for ( int i = 0; cat != null && i < cat.length; i++ )
+        {
+            if ( "assembly".equals( cat[i] ) )
+            {
+                style = "style='font-weight:bold'";
+            }
+        }
+        pw.println( "<td class='content' " + style + ">" + res.getPresentationName() + " (" + res.getSymbolicName()
+            + ")</td>" );
+        pw.println( "<td class='content' " + style + " valign='top'>" + res.getVersion() + "</td>" );
+
+        pw.println( "</tr>" );
+    }
+
+
     private void resourcesButtons( PrintWriter pw )
     {
         pw.println( "<tr class='content'>" );
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java
index f5b5c6e..4b5f4e1 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java
@@ -18,47 +18,38 @@
 
 
 import java.io.IOException;
-import java.io.PrintWriter;
 import java.util.Dictionary;
 import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.Iterator;
-import java.util.List;
 import java.util.Map;
-import java.util.SortedMap;
-import java.util.TreeMap;
 
 import javax.servlet.GenericServlet;
+import javax.servlet.Servlet;
 import javax.servlet.ServletException;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.apache.commons.fileupload.FileItem;
-import org.apache.commons.fileupload.FileUploadException;
-import org.apache.commons.fileupload.disk.DiskFileItemFactory;
-import org.apache.commons.fileupload.servlet.ServletFileUpload;
-import org.apache.commons.fileupload.servlet.ServletRequestContext;
+import org.apache.felix.webconsole.AbstractWebConsolePlugin;
 import org.apache.felix.webconsole.Action;
 import org.apache.felix.webconsole.Render;
-import org.apache.felix.webconsole.internal.BaseManagementPlugin;
+import org.apache.felix.webconsole.WebConsoleConstants;
+import org.apache.felix.webconsole.internal.Logger;
+import org.apache.felix.webconsole.internal.OsgiManagerPlugin;
 import org.apache.felix.webconsole.internal.Util;
 import org.apache.felix.webconsole.internal.compendium.AjaxConfigManagerAction;
 import org.apache.felix.webconsole.internal.compendium.ComponentConfigurationPrinter;
 import org.apache.felix.webconsole.internal.compendium.ComponentRenderAction;
 import org.apache.felix.webconsole.internal.compendium.ConfigManager;
-import org.apache.felix.webconsole.internal.core.AjaxBundleDetailsAction;
 import org.apache.felix.webconsole.internal.core.BundleListRender;
 import org.apache.felix.webconsole.internal.core.InstallAction;
 import org.apache.felix.webconsole.internal.core.RefreshPackagesAction;
 import org.apache.felix.webconsole.internal.core.SetStartLevelAction;
-import org.apache.felix.webconsole.internal.core.StartAction;
-import org.apache.felix.webconsole.internal.core.StopAction;
-import org.apache.felix.webconsole.internal.core.UninstallAction;
-import org.apache.felix.webconsole.internal.core.UpdateAction;
 import org.apache.felix.webconsole.internal.misc.ConfigurationRender;
+import org.apache.felix.webconsole.internal.obr.BundleRepositoryRender;
 import org.apache.felix.webconsole.internal.system.GCAction;
 import org.apache.felix.webconsole.internal.system.ShutdownAction;
 import org.apache.felix.webconsole.internal.system.ShutdownRender;
@@ -84,6 +75,8 @@
     /** Pseudo class version ID to keep the IDE quite. */
     private static final long serialVersionUID = 1L;
 
+    public static final String ATTR_LABEL_MAP = OsgiManager.class.getName() + ".labelMap";
+
     /**
      * The name and value of a parameter which will prevent redirection to a
      * render after the action has been executed (value is "_noredir_"). This
@@ -122,12 +115,17 @@
      */
     private static final String DEFAULT_MANAGER_ROOT = "/system/console";
 
+    //    private static final Class[] PLUGIN_CLASSES =
+    //        { AjaxConfigManagerAction.class, ComponentConfigurationPrinter.class, ComponentRenderAction.class,
+    //        ConfigManager.class, AjaxBundleDetailsAction.class, BundleListRender.class, InstallAction.class,
+    //        RefreshPackagesAction.class, SetStartLevelAction.class, StartAction.class, StopAction.class,
+    //        UninstallAction.class, UpdateAction.class, ConfigurationRender.class, GCAction.class, ShutdownAction.class,
+    //        ShutdownRender.class, VMStatRender.class };
     private static final Class[] PLUGIN_CLASSES =
         { AjaxConfigManagerAction.class, ComponentConfigurationPrinter.class, ComponentRenderAction.class,
-            ConfigManager.class, AjaxBundleDetailsAction.class, BundleListRender.class, InstallAction.class,
-            RefreshPackagesAction.class, SetStartLevelAction.class, StartAction.class, StopAction.class,
-            UninstallAction.class, UpdateAction.class, ConfigurationRender.class, GCAction.class, ShutdownAction.class,
-            ShutdownRender.class, VMStatRender.class };
+            ConfigManager.class, BundleListRender.class, InstallAction.class, RefreshPackagesAction.class,
+            SetStartLevelAction.class, ConfigurationRender.class, GCAction.class, ShutdownAction.class,
+            ShutdownRender.class, VMStatRender.class, BundleRepositoryRender.class };
 
     private BundleContext bundleContext;
 
@@ -141,13 +139,17 @@
 
     private ServiceTracker rendersTracker;
 
+    private ServiceTracker pluginsTracker;
+
     private ServiceRegistration configurationListener;
 
+    private Map plugins = new HashMap();
+
+    private Map labelMap = new HashMap();
+
     private Map operations = new HashMap();
 
-    private SortedMap renders = new TreeMap();
-
-    private Render defaultRender;
+    private Servlet defaultRender;
 
     private String defaultRenderName;
 
@@ -178,6 +180,8 @@
         operationsTracker.open();
         rendersTracker = new RenderServiceTracker( this );
         rendersTracker.open();
+        pluginsTracker = new PluginServiceTracker( this );
+        pluginsTracker.open();
         httpServiceTracker = new HttpServiceTracker( this );
         httpServiceTracker.open();
 
@@ -187,18 +191,25 @@
             try
             {
                 Object plugin = pluginClass.newInstance();
-                if ( plugin instanceof BaseManagementPlugin )
+                if ( plugin instanceof OsgiManagerPlugin )
                 {
-                    ( ( BaseManagementPlugin ) plugin ).setBundleContext( bundleContext );
-                    ( ( BaseManagementPlugin ) plugin ).setLogger( log );
+                    ( ( OsgiManagerPlugin ) plugin ).activate( bundleContext );
                 }
-                if ( plugin instanceof Action )
+                if ( plugin instanceof AbstractWebConsolePlugin )
                 {
-                    bindOperation( ( Action ) plugin );
+                    AbstractWebConsolePlugin amp = ( AbstractWebConsolePlugin ) plugin;
+                    bindServlet( amp.getLabel(), amp );
                 }
-                if ( plugin instanceof Render )
+                else
                 {
-                    bindRender( ( Render ) plugin );
+                    if ( plugin instanceof Action )
+                    {
+                        bindOperation( ( Action ) plugin );
+                    }
+                    if ( plugin instanceof Render )
+                    {
+                        bindRender( ( Render ) plugin );
+                    }
                 }
             }
             catch ( Throwable t )
@@ -230,16 +241,33 @@
             rendersTracker = null;
         }
 
+        if ( pluginsTracker != null )
+        {
+            pluginsTracker.close();
+            pluginsTracker = null;
+        }
+
         if ( httpServiceTracker != null )
         {
             httpServiceTracker.close();
             httpServiceTracker = null;
         }
 
+        // deactivate any remaining plugins
+        for ( Iterator pi = plugins.values().iterator(); pi.hasNext(); )
+        {
+            Object plugin = pi.next();
+            if ( plugin instanceof OsgiManagerPlugin )
+            {
+                ( ( OsgiManagerPlugin ) plugin ).deactivate();
+            }
+        }
+
         // simply remove all operations, we should not be used anymore
         this.defaultRender = null;
+        this.plugins.clear();
+        this.labelMap.clear();
         this.operations.clear();
-        this.renders.clear();
 
         if ( log != null )
         {
@@ -263,38 +291,45 @@
         }
 
         // check whether we are not at .../{webManagerRoot}
-        if ( request.getRequestURI().endsWith( this.webManagerRoot ) )
+        if ( request.getPathInfo() == null )
         {
-            response.sendRedirect( request.getRequestURI() + "/" + this.defaultRender.getName() );
+            String path = request.getRequestURI();
+            if ( !path.endsWith( "/" ) )
+            {
+                path = path.concat( "/" );
+            }
+            path = path.concat( defaultRenderName );
+            response.sendRedirect( path );
             return;
         }
 
-        // otherwise we render the response
-        Render render = this.getRender( request );
-        if ( render == null )
+        String label = request.getPathInfo();
+        int slash = label.indexOf( "/", 1 );
+        if ( slash < 2 )
+        {
+            slash = label.length();
+        }
+
+        label = label.substring( 1, slash );
+        Servlet plugin = ( Servlet ) plugins.get( label );
+        if ( plugin != null )
+        {
+            req.setAttribute( ATTR_LABEL_MAP, labelMap );
+            plugin.service( req, res );
+        }
+        else
         {
             response.sendError( HttpServletResponse.SC_NOT_FOUND );
             return;
         }
 
-        String current = render.getName();
-        boolean disabled = false; // should take action==shutdown into
-        // account:
-        // Boolean.valueOf(request.getParameter("disabled")).booleanValue();
-
-        PrintWriter pw = Util.startHtml( response, render.getLabel() );
-        Util.navigation( pw, this.renders.values(), current, disabled );
-
-        render.render( request, response );
-
-        Util.endHhtml( pw );
     }
 
 
     protected boolean handleAction( HttpServletRequest req, HttpServletResponse resp ) throws IOException
     {
         // check action
-        String actionName = this.getParameter( req, Util.PARAM_ACTION );
+        String actionName = AbstractWebConsolePlugin.getParameter( req, Util.PARAM_ACTION );
         if ( actionName != null )
         {
             Action action = ( Action ) this.operations.get( actionName );
@@ -315,7 +350,8 @@
                 }
 
                 // maybe overwrite redirect
-                if ( PARAM_NO_REDIRECT_AFTER_ACTION.equals( getParameter( req, PARAM_NO_REDIRECT_AFTER_ACTION ) ) )
+                if ( PARAM_NO_REDIRECT_AFTER_ACTION.equals( AbstractWebConsolePlugin.getParameter( req,
+                    PARAM_NO_REDIRECT_AFTER_ACTION ) ) )
                 {
                     resp.setStatus( HttpServletResponse.SC_OK );
                     resp.setContentType( "text/html" );
@@ -341,98 +377,6 @@
     }
 
 
-    protected Render getRender( HttpServletRequest request )
-    {
-
-        String page = request.getRequestURI();
-
-        // remove trailing slashes
-        while ( page.endsWith( "/" ) )
-        {
-            page = page.substring( 0, page.length() - 1 );
-        }
-
-        // take last part of the name
-        int lastSlash = page.lastIndexOf( '/' );
-        if ( lastSlash >= 0 )
-        {
-            page = page.substring( lastSlash + 1 );
-        }
-
-        Render render = ( Render ) this.renders.get( page );
-        return ( render == null ) ? this.defaultRender : render;
-    }
-
-
-    private String getParameter( HttpServletRequest request, String name )
-    {
-        // just get the parameter if not a multipart/form-data POST
-        if ( !ServletFileUpload.isMultipartContent( new ServletRequestContext( request ) ) )
-        {
-            return request.getParameter( name );
-        }
-
-        // check, whether we alread have the parameters
-        Map params = ( Map ) request.getAttribute( Util.ATTR_FILEUPLOAD );
-        if ( params == null )
-        {
-            // parameters not read yet, read now
-            // Create a factory for disk-based file items
-            DiskFileItemFactory factory = new DiskFileItemFactory();
-            factory.setSizeThreshold( 256000 );
-
-            // Create a new file upload handler
-            ServletFileUpload upload = new ServletFileUpload( factory );
-            upload.setSizeMax( -1 );
-
-            // Parse the request
-            params = new HashMap();
-            try
-            {
-                List items = upload.parseRequest( request );
-                for ( Iterator fiter = items.iterator(); fiter.hasNext(); )
-                {
-                    FileItem fi = ( FileItem ) fiter.next();
-                    FileItem[] current = ( FileItem[] ) params.get( fi.getFieldName() );
-                    if ( current == null )
-                    {
-                        current = new FileItem[]
-                            { fi };
-                    }
-                    else
-                    {
-                        FileItem[] newCurrent = new FileItem[current.length + 1];
-                        System.arraycopy( current, 0, newCurrent, 0, current.length );
-                        newCurrent[current.length] = fi;
-                        current = newCurrent;
-                    }
-                    params.put( fi.getFieldName(), current );
-                }
-            }
-            catch ( FileUploadException fue )
-            {
-                // TODO: log
-            }
-            request.setAttribute( Util.ATTR_FILEUPLOAD, params );
-        }
-
-        FileItem[] param = ( FileItem[] ) params.get( name );
-        if ( param != null )
-        {
-            for ( int i = 0; i < param.length; i++ )
-            {
-                if ( param[i].isFormField() )
-                {
-                    return param[i].getString();
-                }
-            }
-        }
-
-        // no valid string parameter, fail
-        return null;
-    }
-
-
     BundleContext getBundleContext()
     {
         return bundleContext;
@@ -543,6 +487,50 @@
         }
     }
 
+    private static class PluginServiceTracker extends ServiceTracker
+    {
+
+        private final OsgiManager osgiManager;
+
+
+        PluginServiceTracker( OsgiManager osgiManager )
+        {
+            super( osgiManager.getBundleContext(), WebConsoleConstants.SERVICE_NAME, null );
+            this.osgiManager = osgiManager;
+        }
+
+
+        public Object addingService( ServiceReference reference )
+        {
+            Object label = reference.getProperty( WebConsoleConstants.PLUGIN_LABEL );
+            if ( label instanceof String )
+            {
+                Object operation = super.addingService( reference );
+                if ( operation instanceof Servlet )
+                {
+                    // TODO: check reference properties !!
+                    osgiManager.bindServlet( ( String ) label, ( Servlet ) operation );
+                }
+                return operation;
+            }
+
+            return null;
+        }
+
+
+        public void removedService( ServiceReference reference, Object service )
+        {
+            Object label = reference.getProperty( WebConsoleConstants.PLUGIN_LABEL );
+            if ( label instanceof String )
+            {
+                // TODO: check reference properties !!
+                osgiManager.unbindServlet( ( String ) label );
+            }
+
+            super.removedService( reference, service );
+        }
+    }
+
 
     protected synchronized void bindHttpService( HttpService httpService )
     {
@@ -585,48 +573,85 @@
     }
 
 
+    protected void bindServlet( String label, Servlet servlet )
+    {
+        try
+        {
+            servlet.init( getServletConfig() );
+            plugins.put( label, servlet );
+
+            if ( servlet instanceof GenericServlet )
+            {
+                String title = ( ( GenericServlet ) servlet ).getServletName();
+                if ( title != null )
+                {
+                    labelMap.put( label, title );
+                }
+            }
+
+            if ( this.defaultRender == null )
+            {
+                this.defaultRender = servlet;
+            }
+            else if ( label.equals( this.defaultRenderName ) )
+            {
+                this.defaultRender = servlet;
+            }
+        }
+        catch ( ServletException se )
+        {
+            // TODO: log
+        }
+    }
+
+
+    protected void unbindServlet( String label )
+    {
+        Servlet servlet = ( Servlet ) plugins.remove( label );
+        if ( servlet != null )
+        {
+            labelMap.remove( label );
+
+            if ( this.defaultRender == servlet )
+            {
+                if ( this.plugins.isEmpty() )
+                {
+                    this.defaultRender = null;
+                }
+                else
+                {
+                    this.defaultRender = ( Servlet ) plugins.values().iterator().next();
+                }
+            }
+
+            servlet.destroy();
+        }
+    }
+
+
     protected void bindOperation( Action operation )
     {
-        this.operations.put( operation.getName(), operation );
+        operations.put( operation.getName(), operation );
     }
 
 
     protected void unbindOperation( Action operation )
     {
-        this.operations.remove( operation.getName() );
+        operations.remove( operation.getName() );
     }
 
 
     protected void bindRender( Render render )
     {
-        this.renders.put( render.getName(), render );
-
-        if ( this.defaultRender == null )
-        {
-            this.defaultRender = render;
-        }
-        else if ( render.getName().equals( this.defaultRenderName ) )
-        {
-            this.defaultRender = render;
-        }
+        RenderBridge bridge = new RenderBridge( render );
+        bridge.activate( getBundleContext() );
+        bindServlet( render.getName(), bridge );
     }
 
 
     protected void unbindRender( Render render )
     {
-        this.renders.remove( render.getName() );
-
-        if ( this.defaultRender == render )
-        {
-            if ( this.renders.isEmpty() )
-            {
-                this.defaultRender = null;
-            }
-            else
-            {
-                this.defaultRender = ( Render ) renders.values().iterator().next();
-            }
-        }
+        unbindServlet( render.getName() );
     }
 
 
@@ -646,9 +671,9 @@
         configuration = config;
 
         defaultRenderName = ( String ) config.get( PROP_DEFAULT_RENDER );
-        if ( defaultRenderName != null && renders.get( defaultRenderName ) != null )
+        if ( defaultRenderName != null && plugins.get( defaultRenderName ) != null )
         {
-            defaultRender = ( Render ) renders.get( defaultRenderName );
+            defaultRender = ( Servlet ) plugins.get( defaultRenderName );
         }
 
         // get the web manager root path
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/RenderBridge.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/RenderBridge.java
new file mode 100644
index 0000000..2aae84f
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/RenderBridge.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.webconsole.internal.servlet;
+
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.felix.webconsole.AbstractWebConsolePlugin;
+import org.apache.felix.webconsole.Render;
+
+
+public class RenderBridge extends AbstractWebConsolePlugin
+{
+
+    /** Pseudo class version ID to keep the IDE quite. */
+    private static final long serialVersionUID = 1L;
+
+    private final Render render;
+
+
+    RenderBridge( Render render )
+    {
+        this.render = render;
+    }
+
+
+    public Render getRender()
+    {
+        return render;
+    }
+
+
+    public String getTitle()
+    {
+        return render.getLabel();
+    }
+
+
+    public String getLabel()
+    {
+        return render.getName();
+    }
+
+
+    protected void renderContent( HttpServletRequest request, HttpServletResponse response ) throws ServletException,
+        IOException
+    {
+        render.render( request, response );
+    }
+
+}
diff --git a/webconsole/src/main/resources/res/ui/admin.css b/webconsole/src/main/resources/res/ui/admin.css
index 984d9a0..fdb5084 100644
--- a/webconsole/src/main/resources/res/ui/admin.css
+++ b/webconsole/src/main/resources/res/ui/admin.css
@@ -43,7 +43,7 @@
     */
     margin: 0px;
     padding: 5px 0 0 8px;
-    font-size: 400%;
+    font-size: 300%;
     font-weight: bold;
     line-height: 120%;
     height: 95px;
diff --git a/webconsole/src/main/resources/res/ui/bundles.js b/webconsole/src/main/resources/res/ui/bundles.js
new file mode 100644
index 0000000..537577f
--- /dev/null
+++ b/webconsole/src/main/resources/res/ui/bundles.js
@@ -0,0 +1,230 @@
+/*
+ * 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.
+ */
+
+function render(/* int */ startlevel, /* Array of Bundle Object */ bundles)
+{
+
+    header();
+
+    installForm( startlevel );
+    
+    document.write( "<tr class='content'>" );
+    document.write( "<td colspan='7' class='content'>&nbsp;</th>" );
+    document.write( "</tr>" );
+
+    tableHeader();
+
+    if ( !bundles )
+    {
+        document.write( "<tr class='content'>" );
+        document.write( "<td class='content' colspan='6'>No Bundles installed currently</td>" );
+        document.write( "</tr>" );
+    }
+    else
+    {
+        for ( var i = 0; i < bundles.length; i++ )
+        {
+            bundle( bundles[i] );
+        }
+    }
+
+    document.write( "<tr class='content'>" );
+    document.write( "<td colspan='7' class='content'>&nbsp;</th>" );
+    document.write( "</tr>" );
+
+    installForm( startlevel );
+
+    footer();
+}
+
+
+function header()
+{
+    document.write( "<table class='content' cellpadding='0' cellspacing='0' width='100%'>" );
+}
+
+
+function tableHeader()
+{
+    document.write( "<tr class='content'>" );
+    document.write( "<th class='content'>ID</th>" );
+    document.write( "<th class='content' width='100%'>Name</th>" );
+    document.write( "<th class='content'>Status</th>" );
+    document.write( "<th class='content' colspan='4'>Actions</th>" );
+    document.write( "</tr>" );
+}
+
+
+function footer()
+{
+    document.write( "</table>" );
+}
+
+
+function bundle( /* Bundle */ bundle )
+{  
+    document.write( "<tr id='bundle" + bundle.bundleId + "'>" );
+    document.write( bundleInternal( bundle ) );
+    document.write( "</tr>" );
+    document.write( "<tr id='bundle" + bundle.bundleId + "_details'>" );
+    if (bundle.props)
+    {
+        document.write( bundleDetails( bundle.props ) );
+    }
+    document.write( "</tr>" );
+}
+
+   
+/* String */ function bundleInternal( /* Bundle */ bundle )
+{
+    var theBundle = "<td class='content right'>" + bundle.bundleId + "</td>";
+    theBundle += "<td class='content'><a href='javascript:showDetails(" + bundle.bundleId + ")'>" + bundle.name + "</a></td>";
+    theBundle += "<td class='content center'>" + bundle.state + "</td>";
+
+    // no buttons for system bundle
+    if ( bundle.bundleId == 0 )
+    {
+        theBundle += "<td class='content' colspan='4'>&nbsp;</td>";
+    }
+    else
+    {
+        theBundle += actionForm( bundle.hasStart, bundle.bundleId, "start", "Start" );
+        theBundle += actionForm( bundle.hasStop, bundle.bundleId, "stop", "Stop" );
+        theBundle += actionForm( bundle.hasUpdate, bundle.bundleId, "update", "Update" );
+        theBundle += actionForm( bundle.hasUninstall, bundle.bundleId, "uninstall", "Uninstall" );
+    }
+
+    return theBundle;
+}
+
+
+/* String */ function actionForm( /* boolean */ enabled, /* long */ bundleId, /* String */ action, /* String */ actionLabel )
+{
+    var theButton = "<td class='content' align='right'>";
+    theButton += "<input class='submit' type='button' value='" + actionLabel + "'" + ( enabled ? "" : "disabled" ) + " onClick='changeBundle(" + bundleId + ", \"" + action + "\");' />";
+    theButton += "</td>";
+    return theButton;
+}
+
+
+function installForm( /* int */ startLevel )
+{
+    document.write( "<form method='post' enctype='multipart/form-data'>" );
+    document.write( "<tr class='content'>" );
+    document.write( "<td class='content'>&nbsp;</td>" );
+    document.write( "<td class='content'>" );
+    document.write( "<input type='hidden' name='action' value='install' />" );
+    document.write( "<input class='input' type='file' name='bundlefile'>" );
+    document.write( " - Start <input class='checkradio' type='checkbox' name='bundlestart' value='start'>" );
+    document.write( " - Start Level <input class='input' type='input' name='bundlestartelevel' value='" + startLevel + "' width='4'>" );
+    document.write( "</td>" );
+    document.write( "<td class='content' align='right' colspan='5' noWrap>" );
+    document.write( "<input class='submit' style='width:auto' type='submit' value='Install or Update'>" );
+    document.write( "&nbsp;" );
+    document.write( "<input class='submit' style='width:auto' type='submit' value='Refresh Packages' onClick='this.form.action.value=\"refreshPackages\"; return true;'>" );
+    document.write( "</td>" );
+    document.write( "</tr>" );
+    document.write( "</form>" );
+}
+
+
+function changeBundle(/* long */ bundleId, /* String */ action)
+{
+    var parm = "bundles/" + bundleId + "?action=" + action;
+    sendRequest('POST', parm, bundleChanged);
+}
+
+    
+function bundleChanged(obj)
+{
+    var bundleId = obj.bundleId;
+    if (obj.state)
+    {
+        // has status, so draw the line
+        var span = document.getElementById('bundle' + bundleId);
+        if (span)
+        {
+            span.innerHTML = bundleInternal( obj );
+        }
+        
+        if (obj.props)
+        {
+            var span = document.getElementById('bundle' + bundleId + '_details');
+            if (span && span.innerHTML)
+            {
+                span.innerHTML = bundleDetails( obj.props );
+            }
+        }
+        
+    }
+    else
+    {
+        // no status, bundle has been uninstalled
+        var span = document.getElementById('bundle' + bundleId);
+        if (span)
+        {
+            span.parentNode.removeChild(span);
+        }
+        var span = document.getElementById('bundle' + bundleId + '_details');
+        if (span)
+        {
+            span.parentNode.removeChild(span);
+        }
+    }
+    
+    // reload --- should do better and only update the bundle
+    // document.location = document.location;
+}
+
+    
+function showDetails(bundleId) {
+    var span = document.getElementById('bundle' + bundleId + '_details');
+    if (span)
+    {
+        if (span.innerHTML)
+        {
+            span.innerHTML = '';
+        }
+        else
+        {
+            sendRequest('GET', "bundles/" + bundleId + ".json", displayBundleDetails);
+        }
+    }
+}
+
+
+function displayBundleDetails(obj) {
+    var span = document.getElementById('bundle' + obj.bundleId + '_details');
+    if (span)
+    {
+        span.innerHTML = bundleDetails( obj.props );
+    }
+}
+
+
+/* String */ function bundleDetails( props )
+{
+        var innerHtml = '<td class=\"content\">&nbsp;</td><td class=\"content\" colspan=\"6\"><table broder=\"0\">';
+        for (var i=0; i < props.length; i++)
+        {
+            innerHtml += '<tr><td valign=\"top\" noWrap>' + props[i].key + '</td><td valign=\"top\">' + props[i].value + '</td></tr>';
+        }
+        innerHtml += '</table></td>';
+        
+        return innerHtml;
+}
+